Module 3 Exercises — A64 Essentials: Data, Memory, and Control Flow

Back to the Course 3 syllabus. Read first: Smith 2, 4, 5 (macOS adaptations in the Module 0 notes).

Work in course3/m3/ of the labs repo. From here on, every routine gets a tiny test driver (assembly _main or a C harness) plus verification in lldb — make “step it in the debugger once” a habit even when the test passes.

Exercises

Exercise 3.1 — MOV/shift predict-verify. For each line, predict the full 64-bit register value on paper (Smith 2’s MOV/MOVK/MOVN and operand-shift rules), then verify in lldb:

Sequence Predicted x0 Observed
mov x0, #0x1234 then movk x0, #0xABCD, lsl #16
movn x0, #0
mov w0, #-1 (what happens to bits 63:32?)
mov x0, #1; lsl x0, x0, #63; asr x0, x0, #60
ror of 0x8000000000000001 by 1

Exercise 3.2 — Case converter. toupper over a NUL-terminated string in place: loop with byte loads/stores (ldrb/strb, post-indexed), range test, conditional modify. Two versions: branchy, and branchless with CSEL. Verify on a mixed string; single-step the branchless one and watch the flags.

Exercise 3.3 — Reverse in place. Reverse a byte string in place with two pointers walking inward (pre/post-indexed addressing doing the work). Then a second variant that reverses a word-aligned array of 64-bit integers. Note in notes.md which addressing modes (Smith 5) each variant exercised.

Exercise 3.4 — Integer → decimal ASCII. Smith 5’s design pattern, done from scratch: convert an unsigned 64-bit integer to a decimal string (repeated udiv/msub by 10, digits emitted in reverse, then the Exercise 3.3 reversal reused). Print via _puts. Edge cases: 0 and UINT64_MAX. This routine gets reused as the output path for later benchmarks.

Exercise 3.5 — Bit census. Population count three ways: (a) the naive shift-and-test loop; (b) the Kernighan x &= x-1 loop; (c) the bit-parallel SWAR ladder (masks 0x5555…, 0x3333…, …). Verify all three agree on random values. Predict which is fastest for sparse vs. dense bit patterns, then measure with the Module 2 harness.

Exercise 3.6 — Min/max scan. One pass over an array of signed 64-bit integers returning both min and max, using compares + CSEL only (no branches in the loop body except the loop branch). Verify against a C reference. Note the pattern — it is the scalar baseline the NEON version beats in Module 5.

Exercise 3.7 — Endianness, observed. Store 0x0102030405060708 to memory, then read it back with ldrb at each of the 8 byte offsets. Tabulate offset → byte in notes.md and reconcile with H&H 6.6 and Smith 2’s little-endian discussion. Then use rev/rev16/rev32 and predict each result before checking.

Byte offset Predicted byte Observed
+0
+7