Master the fundamental concepts of arm assembly (aarch64) through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksYou now ship to x86 servers and AArch64 laptops/phones. Rosetta 2 translated x86 Mac apps to arm64; AWS moved fleets to Graviton. Porting bugs often come from ABI differences, not syntax sugar.
| Topic | x86-64 Linux | AArch64 Linux |
|---|---|---|
| Args | rdi, rsi, rdx, rcx, r8, r9 | x0-x7 |
| Return | rax | x0 |
| Syscall insn | syscall | svc #0 |
| Syscall # reg | rax | x8 |
| Memory math | allowed in ALU ops | load/store only |
nasmLoading…
asmLoading…
x86 uses variable-length encodings; AArch64 uses fixed 32-bit words. x86 has fewer GPRs but memory operands; ARM has 31 GPRs and explicit loads.
For this exercise, you will implement the same integer sum in both ISAs and document divergences. This task asks you to pair each instruction with its ABI role, because dual-ISA fluency is now baseline for systems engineers maintaining cross-platform runtimes.
Keep the relevant man page, ABI doc, or Rust reference chapter open while you work. When your output disagrees with the reference implementation on the same machine, the mismatch is usually an alignment rule, an off-by-one terminator, or a register slot you misread in GDB.
The clearest way to see CISC vs RISC is to look at the machine code. Write a tiny dual assembler that turns each three-address instruction into real x86-64 and AArch64 machine code, then totals the instruction counts and code size. You will see the three trade-offs directly: x86's two-operand form (it needs an extra mov), its variable-length encodings (1 to 6 bytes here), and AArch64's one fixed 4-byte instruction.
One instruction per line (commas optional). Registers are v0..v7.
cLoading…
| v | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| x86-64 | rax | rcx | rdx | rbx | rsi | rdi | r8 | r9 |
| arm64 | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 |
x86 register numbers are rax 0, rcx 1, rdx 2, rbx 3, rsi 6, rdi 7, r8 8 and r9 9.
AArch64 is always one instruction: add|sub|and|orr|eor xD, xA, xB = BASE | B<<16 | A<<5 | D, with BASE 8b000000, cb000000, 8a000000, aa000000 and ca000000. mov xD, xA = aa0003e0 | A<<16 | D, movz xD, #IMM = d2800000 | IMM<<5 | D, and ret = d65f03c0. The words are stored little-endian.
x86-64 register-register ops use the r/m64, r64 form: REX.W byte 48 | R<<2 | B, then the opcode (add 01, sub 29, and 21, or 09, xor 31, mov 89), then ModRM c0 | (src&7)<<3 | (dst&7). R is set if src ≥ 8, and B if dst ≥ 8. For vD = vA op vB:
op D, B.op D, A.sub: neg D ; add D, A (neg = REX.W(+B), f7, ModRM d8 | (D&7)).mov D, A ; op D, B.li is mov r32, imm32 (it zero-extends): an optional 41 for r8/r9, then b8+(reg&7) and the 4-byte immediate, little-endian. ret is c3. mov vD, vD emits nothing on either side.
cLoading…
op a, b, c), with the operands exactly as they were given.;, and li prints its immediate in decimal. Byte counts say byte when the count is 1. x86-64: (nothing) and arm64: (nothing), and counts nothing.total: x86-64 N instructions, B bytes; arm64 N instructions, B bytes.error: unknown instruction OP;error: expected OP vD, vA, vB;error: expected mov vD, vA;error: expected li vD, IMM (0..65535).Input:
cLoading…
Output:
cLoading…
Hidden tests cover every lowering case (including neg), r8/r9 (REX.R and REX.B), li with hex immediates, self-moves, and malformed lines.