5.1.10Instruction Set Architecture (ISA)

Calling conventions and ABI

2,084 words9 min readdifficulty · medium3 backlinks

WHAT is an ABI vs a Calling Convention?


HOW arguments and return values travel

Using the System V AMD64 ABI (Linux/macOS x86-64) as our concrete model:


The Stack, and the "who saves what" problem


DERIVATION: building a function call from first principles

We have no call semantics — only: registers, memory, a stack pointer rsp, and an instruction pointer rip. Let's invent the convention.

Step 1 — How does the callee know where to return? Why this step? The callee finishes and must resume the caller's next instruction. So the caller must record the return address somewhere the callee can find it. Decision: push it onto the stack. The call instruction does exactly this: call f    push rip_next; jmp f\texttt{call } f \;\equiv\; \texttt{push rip\_next};\ \texttt{jmp } f

Step 2 — Where are the arguments? Why this step? Callee code is written before it knows any specific caller, so the location must be fixed by convention, not by the caller's whim. Decision: rdi, rsi, … in order.

Step 3 — How does the callee get its own scratch/local space? Why this step? Locals need memory that survives the function and can't collide with the caller's frame. Decision: subtract from rsp to carve a stack frame downward: rsprsp(frame size)\texttt{rsp} \leftarrow \texttt{rsp} - (\text{frame size})

Step 4 — Returning the answer and cleaning up. Why this step? The caller expects the result in a fixed place (rax) and expects the stack restored. Decision: mov rax, result; add rsp, frame; ret\texttt{mov rax, result};\ \texttt{add rsp, frame};\ \texttt{ret} where retpop rip.

That's the entire convention — derived, not memorized.


Worked Example 1 — reading a call in assembly

Source:

int add3(int a, int b, int c) { return a + b + c; }
int main() { return add3(10, 20, 30); }

Assembly for the call:

main:
    mov edi, 10      ; a → rdi   (Why? 1st int arg)
    mov esi, 20      ; b → rsi   (Why? 2nd int arg)
    mov edx, 30      ; c → rdx   (Why? 3rd int arg)
    call add3        ; Why? pushes return addr, jumps
    ret              ; result already in eax, return it
add3:
    lea eax, [rdi+rsi]   ; Why? add first two, put in return reg rax
    add eax, edx         ; Why? add third
    ret                  ; Why? pop return address, resume main

Why no stack use? Only 3 args (≤ 6) and no locals — everything lives in registers. Fast.

Worked Example 2 — argument spilling to the stack

long f(long a,long b,long c,long d,long e,long f,long g) { return g; }

Why this matters: 7 args > 6 registers. Args 1–6 → rdi…r9; arg g (7th) is passed on the stack.

; caller pushes g first (right-to-left)
mov  rdi,...  ; a ... through r9 for f
mov  rax,[rsp+8]  ; callee reads g from stack (above return addr)
ret

Why [rsp+8]? At entry rsp points at the return address; the 7th arg sits just above it.

Worked Example 3 — callee-saved register discipline

long g(long x){ long s=0; for(long i=0;i<x;i++) s+=i; return s; }
g:
    push rbx          ; Why? we WANT to use rbx (callee-saved) → must save it
    xor  rbx, rbx     ; s = 0
    ...
    mov  rax, rbx     ; return s
    pop  rbx          ; Why? restore the caller's rbx before returning
    ret

Why bother? rbx is callee-saved: the caller trusts it's unchanged after the call. Breaking this promise corrupts the caller silently.


Recall Feynman: explain to a 12-year-old

Two kids build one Lego spaceship, but each builds a different half in a different room. They can only connect if they agree beforehand: "The connector goes on the 3rd bump from the left." A calling convention is that agreement for computer functions — which slot the numbers go in, and which Lego pieces you promise to hand back unbroken. The ABI is the full rulebook, including how big each brick is. If one kid uses a different rulebook, the ship falls apart even though both halves are "correct."


Flashcards

What is a calling convention?
The contract for how a function call is done in machine code — argument passing, return value location, register preservation, stack layout.
How does an ABI differ from a calling convention?
ABI is the full binary contract (calling convention + data sizes/alignment + struct layout + object format + syscall interface); calling convention is one part of it.
ABI vs API?
API is source-level (names/signatures); ABI is binary-level (survives compilation, lets separately compiled objects link).
In SysV AMD64, which registers pass the first 6 integer args?
rdi, rsi, rdx, rcx, r8, r9.
Where is the integer return value placed (SysV AMD64)?
rax (rdx:rax for 128-bit).
Caller-saved vs callee-saved?
Caller-saved (volatile): callee may clobber, caller saves if needed. Callee-saved (non-volatile): callee must restore before returning.
Name the callee-saved registers in SysV AMD64.
rbx, rbp, r12, r13, r14, r15, rsp.
What does the call instruction do at the primitive level?
push return address (next rip) onto stack, then jmp to target.
What does ret do?
pop the return address off the stack into rip (resume caller).
Stack alignment rule at function entry (SysV AMD64)?
rsp ≡ 8 (mod 16) at entry, because rsp must be 16-byte aligned before the call.
Why split registers into caller/callee-saved instead of one rule?
Minimizes total save/restore memory traffic — each side saves only registers actually at risk.
Where does the 7th integer argument go?
On the stack, just above the return address; callee reads it at [rsp+8] on entry.

Connections

Concept Map

subset of

adds sizes layout syscalls

survives compilation as

enables

instance of

first 6 args in

extras spill to

result via

defines

defines

complements

Calling convention

ABI

API source level

Arg registers rdi rsi rdx rcx r8 r9

Stack overflow args

Return value in rax

Caller-saved volatile

Callee-saved

Cross-compiler linking

System V AMD64 ABI

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Socho do log alag-alag rooms mein program ke do hisse likh rahe hain — ek banda f() function likhta hai, doosra usko call karne wala code. Dono ki machine code tab hi jud paayegi jab pehle se agreement ho: arguments kahan rakhne hain, answer kahan wapas aayega, aur kaunsa register kisko sambhaal ke rakhna hai. Yahi agreement calling convention hai. CPU ko khud pata nahi hota ki "argument" kya hota hai — ye sab sirf convention se bana hua concept hai.

ABI iska bada version hai: calling convention + data types ke sizes + struct layout + object file format + system call numbers. Isi liye GCC ka .o aur Clang ka .o ek saath link ho jaate hain — kyunki dono same ABI follow karte hain. API source-level hota hai (function ke naam), ABI binary-level (compile hone ke baad jo bacha).

SysV AMD64 (Linux x86-64) mein pehle 6 integer args jaate hain rdi, rsi, rdx, rcx, r8, r9 mein, aur return value aati hai rax mein. 6 se zyada args stack pe chale jaate hain. Yaad rakhne ke liye mnemonic: "Diane's silk dress cost 8.99". Registers do type ke: caller-saved (callee bina permission ke bigaad sakta hai) aur callee-saved (callee use kare to wapas theek karke dena padega). Ye split isliye hai taaki dono side sirf zaruri register hi save/restore kare — memory traffic kam.

Sabse important cheez: call = return address stack pe push karke jump; ret = wahi address pop karke wapas. Aur stack ko 16-byte aligned rakhna zaroori hai warna SSE instructions pe segfault milega. Ek baar ye first-principles se samajh lo, to koi bhi assembly padhna aasaan ho jaata hai.

Go deeper — visual, from zero

Test yourself — Instruction Set Architecture (ISA)

Connections