Loops in assembly
Here’s just about the simplest loop we could implement in C.
/**
* A minimal loop in C
* Can't get much simpler than this. This loop has no body!
*
* CS / CMPE 2210
* Computer Organization
* Clayton Cafiero <cbcafier@uvm.edu>
* 2025-09-09
*/
int main(void) {
for (unsigned int i = 65535; i > 0; --i);
// Why did I choose 65,535?
return 0;
}When we compile (on ARM, without compiler optimization) this is what we get (with my annotations):
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 15, 0 sdk_version 15, 5
.globl _main ; -- Begin function main
.p2align 2 ; 2 ** 2 = 4 bytes
_main: ; @main
.cfi_startproc ; for debugging (can ignore)
; (just fyi, "cfi" is call frame info)
; %bb.0: ; annotation provided by compiler
; OVERHEAD and INITIALIZATION
sub sp, sp, #16 ; initialize 16-byte stack frame
.cfi_def_cfa_offset 16 ; for debugging (can ignore)
str wzr, [sp, #12] ; store 32-bit zero in [sp, #12] (padding)
; (that's offset +12 in stack frame)
mov w8, #65535 ; =0xffff move 65,535 to register w8
str w8, [sp, #8] ; store value in [sp, #8] (loop var, i)
b LBB0_1 ; branch (goto) LBB0_1
; LOOP START
;
; LBB0_1, LBB0_2, LBB0_3, and LBB0_4 are just labels. They're not executed.
; On ARM architecture: L = label, BB = basic block (single entrance, single exit)
; 0 indicates the function (in this case main), and the first basic block within
; main, hence LBB0_1. Similarly for the other blocks.
;
LBB0_1: ; =>This Inner Loop Header: Depth=1
ldr w8, [sp, #8] ; load i into register w8
subs w8, w8, #0 ; subtract: i - 0, set NZCV flags
; (this is just a fancy way of writing
; w8 to itself, and setting flags)
b.ls LBB0_4 ; exit to LBB0_4 if i <= 0 (unsigned compare)
; b.ls means lower or same in unsigned
; comparison terms. It branches if C=0
; (borrow) or Z=1 (equal).
b LBB0_2 ; otherwise branch to LBB0_2 (continue loop)
LBB0_2: ; in Loop: Header=BB0_1 Depth=1
b LBB0_3 ; no loop body, just skip to loop control
; branch (go to) LBB0_3
LBB0_3: ; in Loop: Header=BB0_1 Depth=1
ldr w8, [sp, #8] ; load counter (i) at [sp] into w8
subs w8, w8, #1 ; decrement counter, set NZCV flags
str w8, [sp, #8] ; write counter to [sp, #8]
b LBB0_1 ; branch (go to) LBB0_1
; LOOP END
LBB0_4:
mov w0, #0 ; =0x0 write 0 to register w0
; (set return = 0, per AArch64 convention)
add sp, sp, #16 ; pop stack frame (restore stack pointer)
ret ; return (with contents of w0)
.cfi_endproc ; main() ends here
; -- End function
.subsections_via_symbols
© 2025 Clayton Cafiero.
No generative AI was used in writing this material. This was written the old-fashioned way.