Assembly Guide
Applies to: x86-64 (System V ABI), ARM64 (AAPCS), NASM, GAS syntax
Core Principles
- Clarity Over Cleverness: Comment every instruction's purpose; assembly lacks self-documentation
- ABI Compliance: Follow calling conventions precisely for interoperability with C/system code
- Minimal Register Pressure: Preserve callee-saved registers, minimize spills to stack
- Correctness First: Get it working correctly, then profile, then optimize with SIMD
- Structured Layout: Use consistent label naming, section organization, and macro definitions
Guardrails
Architecture Selection
- Declare target architecture at the top of every file
- x86-64: default for Linux/macOS server and desktop workloads
- ARM64: default for Apple Silicon, mobile, and embedded Linux
- Never mix architecture-specific code without
%ifdef / .ifdef guards
Calling Conventions
- x86-64 System V ABI (Linux, macOS, BSD):
- Arguments:
rdi, rsi, rdx, rcx, r8, r9 (integer/pointer, in order)
- Floating-point arguments:
xmm0-xmm7
- Return value:
rax (integer), xmm0 (float)
- Caller-saved (volatile):
rax, rcx, rdx, rsi, rdi, r8-r11
- Callee-saved (non-volatile):
rbx, rbp, r12-r15
- Stack must be 16-byte aligned before
call instruction
- ARM64 AAPCS (Linux, macOS):
- Arguments:
x0-x7 (integer/pointer), d0-d7 (float)
- Return value:
x0 (integer), d0 (float)
- Callee-saved:
x19-x28, x29 (frame pointer), x30 (link register)
- Stack must be 16-byte aligned at all times
Register Usage
- Document which registers hold which logical values at function entry
- Never clobber callee-saved registers without saving and restoring them
- Use
rbp / x29 as frame pointer for debuggability (omit only in leaf functions)
- Reserve scratch registers for temporaries; name them in comments
- Zero-extend results when returning values smaller than 64 bits
Stack Management
- Always maintain 16-byte stack alignment on x86-64 and ARM64
- Allocate local variables by subtracting from
rsp / sp in the prologue
- Deallocate in the epilogue before
ret (never leave the stack dirty)
- Use red zone (128 bytes below
rsp) only in leaf functions on System V ABI
- Never write below the stack pointer outside the red zone
Documentation
- File header: purpose, target architecture, assembler syntax, author
- Function header: C-style prototype comment, argument register mapping, return value
- Inline comments: explain the why, not the what (avoid
; increment counter)
- Label naming:
module_function_sublabel (e.g., crypto_sha256_loop)
- Constants: use
equ / .equ directives with descriptive names
Key Patterns
x86-64 Function with Frame Pointer
; long compute(long x, long y, long z)
; Args: rdi = x, rsi = y, rdx = z
; Returns: rax = x * y + z
global compute
compute:
push rbp ; save frame pointer
mov rbp, rsp ; establish stack frame
mov rax, rdi ; rax = x
imul rax, rsi ; rax = x * y
add rax, rdx ; rax = x * y + z
pop rbp ; restore frame pointer
ret
ARM64 AAPCS Function
// int64_t multiply_add(int64_t a, int64_t b, int64_t c)
// Args: x0 = a, x1 = b, x2 = c | Returns: x0 = a * b + c
.global multiply_add
multiply_add:
stp x29, x30, [sp, #-16]! // save fp and lr
mov x29, sp // establish stack frame
mul x0, x0, x1 // x0 = a * b
add x0, x0, x2 // x0 = a * b + c
ldp x29, x30, [sp], #16 // restore fp and lr
ret
SIMD / SSE2 (4 floats per iteration)
; void add_f32(float *dst, const float *a, const float *b, size_t n)
; Args: rdi = dst, rsi = a, rdx = b, rcx = n
global add_f32
add_f32:
shr rcx, 2 ; n /= 4
.loop:
test rcx, rcx
jz .done
movups xmm0, [rsi] ; load 4 floats from a
addps xmm0, [rdx] ; add 4 floats from b
movups [rdi], xmm0 ; store result
add rsi, 16
add rdx, 16
add rdi, 16
dec rcx
jnz .loop
.done:
ret
Linux x86-64 Syscall Interface
; Syscall: rax = number, args in rdi/rsi/rdx/r10/r8/r9, return in rax
; Note: r10 replaces rcx (clobbered by syscall instruction)
SYS_WRITE equ 1
SYS_EXIT equ 60
section .data
msg db "Hello, world!", 10
msg_len equ $ - msg
section .text
global _start
_start:
mov rax, SYS_WRITE ; write(stdout, msg, msg_len)
mov rdi, 1 ; fd = STDOUT
lea rsi, [rel msg] ; RIP-relative for PIC
mov rdx, msg_len
syscall
mov rax, SYS_EXIT ; exit(0)
xor edi, edi
syscall
Position-Independent Code (PIC)
default rel ; all memory refs become RIP-relative
section .data
counter dq 0
section .text
global get_counter
get_counter:
mov rax, [counter] ; RIP-relative with default rel
ret
global increment_counter
increment_counter:
lock inc qword [counter] ; atomic increment (thread-safe)
mov rax, [counter]
ret
Debugging
GDB Commands
gdb ./program
(gdb) layout asm # show disassembly window
(gdb) layout regs # show registers window
(gdb) stepi # step one instruction
(gdb) nexti # step over call
(gdb) info registers # print all register values
(gdb) p/x $rax # print rax in hex
(gdb) x/4gx $rsp # examine 4 quad-words at stack pointer
(gdb) break *0x401000 # break at address
(gdb) display/i $pc # show current instruction after each step
(gdb) set disassembly-flavor intel
objdump & strace
objdump -d -M intel program # disassemble with Intel syntax
objdump -h program # show section headers
objdump -t program # show symbol table
objdump -r program.o # show relocations (PIC debugging)
strace ./program # trace all syscalls
strace -e trace=write,read ./program # filter specific syscalls
Tooling
Assemblers & Linkers
# NASM (Intel syntax)
nasm -f elf64 -g -F dwarf program.asm -o program.o # Linux
nasm -f macho64 program.asm -o program.o # macOS
# GAS (AT&T syntax, supports .intel_syntax)
as --64 -g program.s -o program.o
# LLVM
clang -c program.s -o program.o
# Linking
ld -o program program.o # bare metal (no libc)
gcc -o program program.o # with libc (C interop)
gcc -shared -o libfoo.so foo.o # shared library (requires PIC)
Verification
nm program.o # verify symbol visibility
nm -u program.o # check undefined references
readelf -S program.o # verify section layout
# In GDB: p/x $rsp & 0xf # should be 0x0 at call boundaries
References
For detailed patterns and code examples, see:
- references/patterns.md -- Prologue/epilogue, syscall examples, SIMD patterns
External References
1---2name: assembly-guide3description: Assembly language guardrails, patterns, and best practices for AI-assisted development. Use when working with assembly files (.asm, .s, .S), or when the user mentions Assembly/x86/ARM. Provides calling convention guidelines, register usage patterns, and debugging techniques specific to this project's coding standards.4license: MIT5---6
7# Assembly Guide
8
9> Applies to: x86-64 (System V ABI), ARM64 (AAPCS), NASM, GAS syntax
10
11## Core Principles
12
131. **Clarity Over Cleverness**: Comment every instruction's purpose; assembly lacks self-documentation
142. **ABI Compliance**: Follow calling conventions precisely for interoperability with C/system code
153. **Minimal Register Pressure**: Preserve callee-saved registers, minimize spills to stack
164. **Correctness First**: Get it working correctly, then profile, then optimize with SIMD
175. **Structured Layout**: Use consistent label naming, section organization, and macro definitions
18
19## Guardrails
20
21### Architecture Selection
22
23- Declare target architecture at the top of every file
24- x86-64: default for Linux/macOS server and desktop workloads
25- ARM64: default for Apple Silicon, mobile, and embedded Linux
26- Never mix architecture-specific code without `%ifdef` / `.ifdef` guards
27
28### Calling Conventions
29
30- **x86-64 System V ABI** (Linux, macOS, BSD):
31 - Arguments: `rdi`, `rsi`, `rdx`, `rcx`, `r8`, `r9` (integer/pointer, in order)
32 - Floating-point arguments: `xmm0`-`xmm7`
33 - Return value: `rax` (integer), `xmm0` (float)
34 - Caller-saved (volatile): `rax`, `rcx`, `rdx`, `rsi`, `rdi`, `r8`-`r11`
35 - Callee-saved (non-volatile): `rbx`, `rbp`, `r12`-`r15`
36 - Stack must be 16-byte aligned before `call` instruction
37- **ARM64 AAPCS** (Linux, macOS):
38 - Arguments: `x0`-`x7` (integer/pointer), `d0`-`d7` (float)
39 - Return value: `x0` (integer), `d0` (float)
40 - Callee-saved: `x19`-`x28`, `x29` (frame pointer), `x30` (link register)
41 - Stack must be 16-byte aligned at all times
42
43### Register Usage
44
45- Document which registers hold which logical values at function entry
46- Never clobber callee-saved registers without saving and restoring them
47- Use `rbp` / `x29` as frame pointer for debuggability (omit only in leaf functions)
48- Reserve scratch registers for temporaries; name them in comments
49- Zero-extend results when returning values smaller than 64 bits
50
51### Stack Management
52
53- Always maintain 16-byte stack alignment on x86-64 and ARM64
54- Allocate local variables by subtracting from `rsp` / `sp` in the prologue
55- Deallocate in the epilogue before `ret` (never leave the stack dirty)
56- Use red zone (128 bytes below `rsp`) only in leaf functions on System V ABI
57- Never write below the stack pointer outside the red zone
58
59### Documentation
60
61- File header: purpose, target architecture, assembler syntax, author
62- Function header: C-style prototype comment, argument register mapping, return value
63- Inline comments: explain the *why*, not the *what* (avoid `; increment counter`)
64- Label naming: `module_function_sublabel` (e.g., `crypto_sha256_loop`)
65- Constants: use `equ` / `.equ` directives with descriptive names
66
67## Key Patterns
68
69### x86-64 Function with Frame Pointer
70
71```nasm
72; long compute(long x, long y, long z)
73; Args: rdi = x, rsi = y, rdx = z
74; Returns: rax = x * y + z
75global compute
76compute:
77 push rbp ; save frame pointer
78 mov rbp, rsp ; establish stack frame
79 mov rax, rdi ; rax = x
80 imul rax, rsi ; rax = x * y
81 add rax, rdx ; rax = x * y + z
82 pop rbp ; restore frame pointer
83 ret
84```
85
86### ARM64 AAPCS Function
87
88```asm
89// int64_t multiply_add(int64_t a, int64_t b, int64_t c)
90// Args: x0 = a, x1 = b, x2 = c | Returns: x0 = a * b + c
91 .global multiply_add
92multiply_add:
93 stp x29, x30, [sp, #-16]! // save fp and lr
94 mov x29, sp // establish stack frame
95 mul x0, x0, x1 // x0 = a * b
96 add x0, x0, x2 // x0 = a * b + c
97 ldp x29, x30, [sp], #16 // restore fp and lr
98 ret
99```
100
101### SIMD / SSE2 (4 floats per iteration)
102
103```nasm
104; void add_f32(float *dst, const float *a, const float *b, size_t n)
105; Args: rdi = dst, rsi = a, rdx = b, rcx = n
106global add_f32
107add_f32:
108 shr rcx, 2 ; n /= 4
109.loop:
110 test rcx, rcx
111 jz .done
112 movups xmm0, [rsi] ; load 4 floats from a
113 addps xmm0, [rdx] ; add 4 floats from b
114 movups [rdi], xmm0 ; store result
115 add rsi, 16
116 add rdx, 16
117 add rdi, 16
118 dec rcx
119 jnz .loop
120.done:
121 ret
122```
123
124### Linux x86-64 Syscall Interface
125
126```nasm
127; Syscall: rax = number, args in rdi/rsi/rdx/r10/r8/r9, return in rax
128; Note: r10 replaces rcx (clobbered by syscall instruction)
129SYS_WRITE equ 1
130SYS_EXIT equ 60
131
132section .data
133 msg db "Hello, world!", 10
134 msg_len equ $ - msg
135
136section .text
137global _start
138_start:
139 mov rax, SYS_WRITE ; write(stdout, msg, msg_len)
140 mov rdi, 1 ; fd = STDOUT
141 lea rsi, [rel msg] ; RIP-relative for PIC
142 mov rdx, msg_len
143 syscall
144 mov rax, SYS_EXIT ; exit(0)
145 xor edi, edi
146 syscall
147```
148
149### Position-Independent Code (PIC)
150
151```nasm
152default rel ; all memory refs become RIP-relative
153
154section .data
155 counter dq 0
156
157section .text
158global get_counter
159get_counter:
160 mov rax, [counter] ; RIP-relative with default rel
161 ret
162
163global increment_counter
164increment_counter:
165 lock inc qword [counter] ; atomic increment (thread-safe)
166 mov rax, [counter]
167 ret
168```
169
170## Debugging
171
172### GDB Commands
173
174```bash
175gdb ./program
176(gdb) layout asm # show disassembly window
177(gdb) layout regs # show registers window
178(gdb) stepi # step one instruction
179(gdb) nexti # step over call
180(gdb) info registers # print all register values
181(gdb) p/x $rax # print rax in hex
182(gdb) x/4gx $rsp # examine 4 quad-words at stack pointer
183(gdb) break *0x401000 # break at address
184(gdb) display/i $pc # show current instruction after each step
185(gdb) set disassembly-flavor intel
186```
187
188### objdump & strace
189
190```bash
191objdump -d -M intel program # disassemble with Intel syntax
192objdump -h program # show section headers
193objdump -t program # show symbol table
194objdump -r program.o # show relocations (PIC debugging)
195
196strace ./program # trace all syscalls
197strace -e trace=write,read ./program # filter specific syscalls
198```
199
200## Tooling
201
202### Assemblers & Linkers
203
204```bash
205# NASM (Intel syntax)
206nasm -f elf64 -g -F dwarf program.asm -o program.o # Linux
207nasm -f macho64 program.asm -o program.o # macOS
208
209# GAS (AT&T syntax, supports .intel_syntax)
210as --64 -g program.s -o program.o
211
212# LLVM
213clang -c program.s -o program.o
214
215# Linking
216ld -o program program.o # bare metal (no libc)
217gcc -o program program.o # with libc (C interop)
218gcc -shared -o libfoo.so foo.o # shared library (requires PIC)
219```
220
221### Verification
222
223```bash
224nm program.o # verify symbol visibility
225nm -u program.o # check undefined references
226readelf -S program.o # verify section layout
227# In GDB: p/x $rsp & 0xf # should be 0x0 at call boundaries
228```
229
230## References
231
232For detailed patterns and code examples, see:
233
234- [references/patterns.md](references/patterns.md) -- Prologue/epilogue, syscall examples, SIMD patterns
235
236## External References
237
238- [x86-64 System V ABI Specification](https://gitlab.com/x86-psABIs/x86-64-ABI)
239- [ARM Architecture Reference Manual](https://developer.arm.com/documentation/ddi0487/latest)
240- [NASM Documentation](https://www.nasm.us/doc/)
241- [GAS Manual (GNU Assembler)](https://sourceware.org/binutils/docs/as/)
242- [Intel Intrinsics Guide (SSE/AVX)](https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html)
243- [Linux Syscall Table (x86-64)](https://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/)
244- [Agner Fog's Optimization Manuals](https://www.agner.org/optimize/)
245- [Felix Cloutier x86 Instruction Reference](https://www.felixcloutier.com/x86/)