<add>: push {r7} sub sp, #12 add r7, sp, #0 str r0, [r7, #4] ; spill a str r1, [r7, #0] ; spill b ldr r2, [r7, #4] ; reload a ldr r3, [r7, #0] ; reload b add r3, r2 mov r0, r3 ; return value adds r7, #12 mov sp, r7 pop {r7} bx lr <main>: push {r7, lr} sub sp, #8 add r7, sp, #0 movs r3, #5 str r3, [r7, #4] ; x = 5 movs r1, #20 ; b movs r0, #10 ; a bl add str r0, [r7, #0] ; result = R0 ldr r3, [r7, #4] adds r3, #1 str r3, [r7, #4] ; x = x + 1 movs r3, #0 mov r0, r3 adds r7, #8 mov sp, r7 pop {r7, pc}
<add>: add r0, r1 ; a + b, straight into R0 bx lr <main>: movs r0, #0 ; return 0 bx lr ; No BL. No LR write. No stack. ; No 10, no 20, no 30. ; add() is still emitted only because ; it has external linkage and something ; else might call it. ; -O1 gives the identical result.
<add>: add r0, r1 bx lr <main>: push {r3, lr} ; r3 only pads SP to 8 bytes ldr r3, =in_a ldr r0, [r3, #0] ; a = in_a ldr r1, [r3, #4] ; b = in_b ldr r3, =out ; kept live ACROSS the call (!) bl add movs r2, #6 ; x = x + 1 already folded str r0, [r3, #0] ; out = result movs r0, #0 ; return 0 (before 2nd store) str r2, [r3, #0] ; out = x pop {r3, pc} ; return via epilogue, not BX LR
These are not drawings. The same add.c is compiled with arm-none-eabi-gcc 14.2 for Cortex-M4, the machine code runs in the
Unicorn CPU emulator, and every register, stack word and PC value is read back after each instruction.
Yellow = instruction just executed, blue = PC, red = state changed, grey stack rows = dead frames.
Reproduce it: git clone https://github.com/zuwasi/cortex-m-call-anatomy, pip install -r requirements.txt,
python callviz.py --compare O0 O2 forced. Space plays and pauses, the arrow keys step one instruction at a time.
Standalone animation page.