Asm is simple enough that "mental execution" is far easier, if more tedious, than in HLLs, especially those with lots of hidden side-effects. The concept of a function doesn't really exist (and this is even more true when working with RISCs that don't have implicit stack management instructions), and although there are instructions that make it more convenient to do HLL-style call and return, it's just as easy to write a "function" that returns to its caller's caller (or further), switches to a different task or thread, etc. If you're going to learn Asm, then IMHO you should try to exploit this freedom in control flow and leverage the rest of the machine's ability, since merely being a human compiler is not particularly enlightening nor useful.
I agree entirely, great insight! I'd like to add that assembly is best enjoyed in a suitable environment for it, where "APIs" are just memory writes and interrupts. Game programming for the C64 is way more fun than dealing with linux syscalls, for example. A lower level interface enables all the fun assembler tricks, and limited resources require you to be clever.
Then you goto hell…
> Asm is simple enough that "mental execution" is far easier, if more tedious, than in HLLs
Ya totally I can also keep 32 registers, a memory file, and stack pointer all in my head at once ...fellow human... (In 2026 I might actually be an LLM in which I really can keep all that context in my "head"!)
> Asm is simple enough
The general conceptual model of "asm" is simple.
Some instruction sets and architectures are hideous, though.
> merely being a human compiler is not particularly enlightening nor useful.
I don't think I can agree with that. At least it teaches you what the compiler is doing. And abiding by conventions (HLL-esque control flow, but also things like "put the return value in r0" and "put constant pools after the function") can definitely make it easier to make sense of the code. (Although you might share a constant pool across a module or something, if the instructions reach far enough.)
Not to say that you can't do interesting things, and can't ever beat the compiler. One of the things I most enjoyed discovering, in mid-00s era THUMB (i.e. 16-bit ARM) code, is that the compiler was implementing switch statements with tables of 32-bit constants that it would load into an indirect jump. I didn't get around to it, but I figured I could mechanically replace these with a computed jump into a "table" of 16-bit unconditional branches (except for very long functions, but this helped bring the branch distances under thresholds).