Calling conventions and ABI
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:
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:
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:
where ret ≡ pop 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 mainWhy 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)
retWhy [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
retWhy 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?
How does an ABI differ from a calling convention?
ABI vs API?
In SysV AMD64, which registers pass the first 6 integer args?
Where is the integer return value placed (SysV AMD64)?
Caller-saved vs callee-saved?
Name the callee-saved registers in SysV AMD64.
What does the call instruction do at the primitive level?
What does ret do?
Stack alignment rule at function entry (SysV AMD64)?
Why split registers into caller/callee-saved instead of one rule?
Where does the 7th integer argument go?
Connections
- Stack and Stack Frames
- Registers and Register File
- System Calls and Syscall Interface
- Linkers and Object File Format
- Recursion and Activation Records
- x86-64 vs ARM64 AAPCS
- Name Mangling in C++
Concept Map
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.