If the author doesn't like this they shouldn't look into MIPS because it goes well beyond that. You see, MIPS has a special "R0" register that's always 0 (AARCH 64 does as well by the way) so you can always use it as a placeholder in other instructions.
As such, there's no real MOVE instruction, it's just an assembler mnemonic that assembles down to `OR $target, $src, $R0`. NOP? It's by convention `SLL $R0, $R0, 0` (which has the nice property of being an instruction encoding as "0x00000000"). You want to negate a number? `SUB $target, $R0, $src".
Since all instructions are 32bit wide you can't load a 32bit immediate value in a single instruction, instead the assembler's "LI" mnemonic generates a pair of instructions (LUI/ORI) for large immediate values (ARM prefers PC-relative loads).
You have a whole bunch of mnemonics in MIPS that are just aliases around other instructions. I always thought it was pretty clever.
In summary you can have this assembler listing:
1:
sll $0, $0, 0
or $t0, $t1, $0
li $t0, 0xabcdef
sub $t0, $0, $t1
j 1b
That will disassemble to: nop
move t0,t1
lui t0,0xab
ori t0,t0,0xcdef
j 0x0
neg t0,t1
The only operation here that I would qualify as "high level" is
the reordering of the "neg" instruction into the delay slot (note
that j is no longer the last instruction). Everything else is
very straightforward substitution and if the assembler didn't
support these mnemonics we could implement them with very trivial
macros.Note that even x86 assemblers do that to some extent, for instance "nop" assembles down to an instruction with no side effect (typically `xchg eax, eax`). Furthermore there are a bunch of mnemonics for the same encoding, for instance JAE (jump if above or equal), JNB (jump if not below) and JNC (jump if not carry). Overall instruction encoding is also massively more complicated in x86 (and even more for amd64) so the assembler needs to handle many more corner cases than the simple substitutions of ARM and MIPS. As a brain teaser, consider the following similar looking amd64 instructions that load the 32bit value pointed at by a register (the only difference is that the first one dereferences the pointer in %rax, the second in %r12):
mov %eax, (%rax) ; assembles to 89 00
mov %eax, (%r12) ; assembles to 41 89 04 24
I can't even be bothered to walk you through this but basically
it has to do with the fact that %r12 happens to be encoded as
%rsp + 8 (because registers r8 to r15 are effectively a hack
since x86 only supported 8 GPRs) and %rsp has special semantics
in this addressing mode which mandate a different, longer
encoding otherwise you end up with an ambiguous instruction.Yeah, I think in retrospect we can give ARM a pass for their ROR shenanigans.