Module 4 Exercises — Functions, the Stack, and the C Boundary

Back to the Course 3 syllabus. Read first: Smith 6, 7, 9; Apple’s Writing ARM64 code for Apple platforms (the ABI divergences section).

Work in course3/m4/ of the labs repo. Mixed C/assembly builds: clang harness.c routine.s -o test — the driver handles both.

NoteExercises in this set
ImportantThe Apple AAPCS64 divergences that bite
  • x18 is reserved. Never use it, even scratch.
  • Variadic functions take all variadic arguments on the stack, 8-byte slots — unlike Linux, where the first eight still ride in registers. This is the #1 “printf from assembly works on the Pi but not the Mac” gotcha, exercised below.
  • SP must be 16-byte aligned at every external call (hardware-enforced on access via SP).
  • No stable raw-syscall ABI — the libc boundary (_write, _exit, …) is the OS interface.

Exercises

Exercise 4.1 — Recursion with real frames. Recursive factorial: full prologue/epilogue (stp x29, x30, frame pointer chained), argument preserved across the recursive call in a callee-saved register or on the stack. In lldb, break at maximum depth for fact(6) and: (a) run bt — confirm 6 frames; (b) dump the raw stack with memory read and identify each frame’s saved x29/x30 pair by hand. Deliverable: the annotated stack dump in notes.md, each frame boundary marked.

Exercise 4.2 — Assembly under a C harness. Implement size_t my_strlen(const char *) in assembly, AAPCS64-clean. Drive it from a C harness that checks correctness against strlen over a test set, then benchmarks both (Module 2 discipline) on a ~1 MB string. Record the ratio — then note in notes.md why libc wins (it vectorizes; Module 5 closes the gap).

Implementation ns per 1 MB scan Ratio
my_strlen (byte loop)
libc strlen 1.0×

Exercise 4.3 — Calling back into C. Write an assembly routine apply(long *arr, size_t n, long (*f)(long)) that maps f over an array in place, calling through BLR with correct save/restore of its own state around each call. Drive it from C with two different callbacks. Single-step one BLR round trip in lldb and watch lr/x30 change hands.

Exercise 4.4 — The variadic gotcha, demonstrated. Call printf("%s %ld\n", s, n) from assembly twice: (a) the Linux way (arguments in x0x2), (b) the Apple way (x0 = format, variadic arguments in 8-byte stack slots). Run both on the Mac; record what (a) actually prints and explain it via the callout above. This exercise is the reason Smith’s Linux examples “almost” work here.

Variant Output Why
Register-passed variadics
Stack-passed variadics

Exercise 4.5 — Walking the frame chain. With Exercise 4.1’s factorial paused mid-recursion, reconstruct the backtrace manually: start at x29, follow the saved-x29 chain, and list each return address; symbolicate with image lookup -a. Confirm your hand-walked list matches bt. Deliverable: the walk transcript — this is exactly what a debugger, a profiler, and a crash reporter do, and what Course 2’s hard-fault handler will do on the Cortex-M.

Exercise 4.6 — Leaf vs. non-leaf cost. Take Exercise 3.6’s min/max scan (a leaf) and wrap it in a non-leaf variant that spills/reloads a full callee-saved set on every call. Benchmark both called 10M times on a tiny array. The measured delta is the AAPCS’s price tag — worth knowing when Course 2 asks “why is my per-sample ISR budget gone?”