Exercises — Calling conventions and ABI
5.1.10 · D4· Hardware › Instruction Set Architecture (ISA) › Calling conventions and ABI
Reference facts jo tumhe chahiye ho sakte hain (sab parent note se):
Level 1 — Recognition
Problem 1.1
Call foo(a, b, c, d) ke liye jahan charon arguments int hain, wo register naam batao jo har argument hold karta hai, order mein.
Recall Solution 1.1
Integer-argument order fixed hai: rdi, rsi, rdx, rcx, r8, r9. Humein sirf pehle chaar slots chahiye.
a→ rdi (1st slot)b→ rsi (2nd slot)c→ rdx (3rd slot)d→ rcx (4th slot)
YE KYU inhein aur doosron ko nahi? Convention ek fixed lookup table hai taaki callee — jo pehle compile hoti hai kisi bhi caller se milne se — jaanta ho exactly kahan padhna hai.
Problem 1.2
long h(void) ek single 64-bit integer return karta hai. Kaun sa register wo value caller tak wapas le jaata hai?
Recall Solution 1.2
Integer return register rax hai. Ek single 64-bit value poori rax mein fit hoti hai; rdx tabhi recruit hota hai jab value 128 bits wide ho.
Problem 1.3
Har register ko caller-saved ya callee-saved classify karo: rax, rbx, r10, r12, rdi.
Recall Solution 1.3
Dono lists se match karo:
rax→ caller-saved (return register, hamesha volatile)rbx→ callee-savedr10→ caller-savedr12→ callee-savedrdi→ caller-saved (ye ek argument register hai — callee ise overwrite karne ke liye free hai)
Level 2 — Application
Problem 2.1
Wo essential moves likho jo caller bar(7, 42) ke liye karta hai jahan dono args int hain, phir call. (Assume karo values literals hain.)
Recall Solution 2.1
Do args → slots rdi, rsi. Kyunki ye 32-bit int hain, hum 32-bit sub-registers edi/esi load karte hain (ek 32-bit register mein likhna poore 64-bit register mein free mein zero-extend ho jaata hai):
mov edi, 7 ; 1st arg → rdi
mov esi, 42 ; 2nd arg → rsi
call bar ; return addr push karo, jump karoProblem 2.2
Ek function long k(long a) ko internally r13 use karna hai. Usse kaun se do instructions add karne padte hain (aur kahan), aur kyun?
Recall Solution 2.2
r13 callee-saved hai, isliye k ko ise exactly waisa hi chhhodna hoga jaisa usne paya tha.
k:
push r13 ; entry par caller ka r13 save karo
... ; yahan r13 freely use karo
pop r13 ; return se pehle restore karo
retKYU: caller trust karta hai ki r13 call ke across unchanged hai. push/pop ek matched pair hain — entry par save karo, ret se thik pehle restore karo.
Problem 2.3
Alignment. Jab CPU call foo execute karta hai (jisne 8-byte return address push kiya), rsp value 0x7fff_ffff_e2a8 hold karta hai. Kya alignment invariant foo ki entry par satisfy hota hai?
Recall Solution 2.3
Invariant hai entry par. Low hex digits lo: ...a8 → byte value hai 0xa8 = 168.
Kyunki , invariant hold karta hai. Achha hai — foo ke andar SSE instructions jaise movaps fault nahi karenge.
Level 3 — Analysis
Problem 3.1
Yahan argument locations trace karo:
long f(long a, long b, long c, long d, long e, long g, long h);f ki entry par har argument kahan rehta hai?
Recall Solution 3.1
Saat long args hain, lekin sirf chhe integer registers hain.
a→ rdi,b→ rsi,c→ rdx,d→ rcx,e→ r8,g→ r9 (slots 1–6)h(7th) → stack par.
Extra args right-to-left push kiye jaate hain, isliye sabse leftmost overflow arg (h yahan) overflow region ke lowest stack address par rehta hai. Entry par, rsp return address point karta hai, isliye h uske thik upar baithta hai:
Problem 3.2
Neeche diagram dekho (Figure 1). call f run hone ke baad, stack contents high address se low tak list karo, given f ka 32-byte local frame hai.

Recall Solution 3.2
Figure 1 top (high address) → bottom (low address) padhna:
- Overflow argument
h(callse pehle caller dwara push kiya gaya) — sabse upar. - Return address (
callke khud push kiya gaya) — yahan rsp entry par point karta hai. - Local frame, 32 bytes (
fke andarsub rsp, 32) — sabse neeche; naya rsp iske bottom par point karta hai.
Dhyan do h return address ke upar rehta hai ([rsp+8] entry par), aur locals uske neeche rehte hain. Stack neeche ki taraf grow karta hai (lower addresses ki taraf) — orange arrow.
Problem 3.3
Ye buggy fragment dekho. Isme kaunsa silent bug hai?
compute:
mov rbx, rdi ; input ko rbx mein stash karo
imul rbx, rbx ; square karo
mov rax, rbx ; return value
retRecall Solution 3.3
rbx callee-saved hai. compute rbx mein likhta hai lekin entry par kabhi push nahi kiya aur ret se pehle kabhi pop nahi kiya. Isliye ye caller ke rbx ko destroyed karke return karta hai.
rax mein return value correct hai, aur yahi reason hai ki bug silent hai — sab theek lagta hai jab tak caller baad mein apna (ab-corrupted) rbx nahi padhta.
Fix: ya toh push rbx / pop rbx se bracket karo, ya r10/r11 jaisa caller-saved scratch register use karo aur saving se bilkul bacho.
Level 4 — Synthesis
Problem 4.1
Given:
double scale(double x, long n, double y);Har argument ko uska register assign karo. (Yaad rakho floats aur ints alag register banks use karte hain.)
Recall Solution 4.1
Integer args aur floating-point args do independent lists par count hote hain:
- Floats xmm0, xmm1, xmm2, … fill karte hain
- Integers rdi, rsi, rdx, … fill karte hain
Signature left to right walk karo, har baar sahi bank se dispense karo:
x(double) → xmm0 (1st float)n(long) → rdi (1st integer)y(double) → xmm1 (2nd float)
Return type double hai, isliye result xmm0 mein wapas aata hai.
Key insight: do doubles ke beech sandwich kiya ek long doosre double ko xmm1 se xmm2 par nahi push karta — banks alag count hote hain.
Problem 4.2
Ek function long depth3() ek aur function ko sequence mein teen baar call karta hai, aur calls ke beech use ek running counter alive rakhna hai. Kya counter caller-saved ya callee-saved register mein rehna chahiye? Justify karo with har choice ke total save/restore operations ki sankhya.
Recall Solution 4.2
Counter ko teen calls ke across survive karna hai. Ek caller-saved register mein value har callee dwara clobber ho jaati, isliye depth3 ko ise har call ke around save-and-restore karna padta: memory operations.
Ise ek callee-saved register mein daalo. Tab:
depth3ise entry par ek baar save karta hai, exit par ek baar restore karta hai: total operations.- Teen callees har baar ise free mein preserve karne ka promise karte hain.
Cost comparison: (caller-saved) vs (callee-saved). callee-saved choose karo — yahi exact reason hai ki ABI registers ko do classes mein split karta hai: jo values calls ke across rehti hain wo callee-saved registers mein honi chahiye.
Level 5 — Mastery
Problem 5.1
Tum ek leaf helper hand-assembly mein likhte ho jo entry par exactly ek 8-byte register push karta hai, kuch kaam karta hai, phir printf ko nested call karta hai. Ye printf ke andar ek movaps instruction par segfault karta hai. Cause diagnose karo aur ek-instruction fix do.
Recall Solution 5.1
Alignment trace karo. Maano tumhari entry par (invariant).
- Ek register
pushkaro: rsp 8 se girta hai → ab . - Tum
call printfexecute karte ho: wo ek 8-byte return address push karta hai → printf ki entry par ... ruko — recompute karo: . Wo fine hota.
Asli trap: invariant jis par printf rely karta hai ye hai ki rsp uski entry par ho — jo require karta hai ki rsp tumhare call printf se immediately pehle ho. Ek push ke baad, rsp hai, isliye call printf ko par land karta hai ✓. Toh ek single push actually safe hai.
Segfault ka matlab hai tumne even number push kiya jisne tumhe par wapas la diya call se pehle (printf ko par enter karwa ke — uske apne SSE spills ke liye misaligned). Concretely, agar tumne do registers push kiye (ya sub rsp, 16 aur ek stray 8), tum ise tod dete ho.
Ek-instruction fix: call printf se pehle sub rsp, 8 insert karo (ya ek extra dummy 8-byte value push karo) taaki call site par rsp ho; phir baad mein add rsp, 8. Ye woh invariant restore karta hai jo printf ko chahiye.
Problem 5.2
Do .o files alag compilers dwara compile kiye gaye hain jo identical SysV calling convention share karte hain. Phir bhi unhe link karke chalane se silent numeric corruption hoti hai jab ek long double doosre ko pass karta hai. Explain karo ki calling convention identical hone se tum kyun nahi bache, aur fault hone wali layer ka naam batao.
Recall Solution 5.2
Calling convention ka jawab hai "argument kaun se slot mein jaata hai?" — lekin nahi "long double kitne bytes hai?".
Maano compiler A long double ko 80-bit (x87 extended) padded to 16 bytes treat karta hai, jabki compiler B ise 128-bit quad precision treat karta hai. Bhale hi dono agree karte hon ise same jagah pass karne ke liye (stack par, 16-byte aligned), un 16 bytes ka bit-level meaning differ karta hai. B, A ke 80-bit-plus-padding pattern ko ek genuine 128-bit float padhta hai → garbage.
Fault layer: ABI, specifically uska data-type size aur representation clause — calling convention ka ek strict superset. (Parent ka steel-man dekho: calling convention ⊂ ABI.) Isliye "same calling convention" wahi nahi hai jaise "ABI-compatible."
Problem 5.3
Design task. Tumhe ek struct Pair { long a; long b; } by value ek function ko pass karna hai. ABI kehta hai small aggregates (≤ 16 bytes, sab integer fields) registers mein, field by field pass kiye jaate hain. void use(Pair p) ke liye a aur b kahan rehte hain? Phir jawab do: void use2(long z, Pair p) ke liye ye kahan jaate hain?
Recall Solution 5.3
Pair 16 bytes hai, dono fields integer hain, isliye ye pointer se pass nahi hota — ye do integer register slots mein flatten hota hai.
use(Pair p):p.a→ rdi (1st integer slot),p.b→ rsi (2nd integer slot). Return-wise kuch nahi; struct pehle do integer registers occupy karta hai.use2(long z, Pair p):zpehla slot consume karta hai → rdi. Phir flattened struct agle do slots consume karta hai:p.a→ rsi,p.b→ rdx.
Key idea: register slots source order mein consume hote hain, aur ek aggregate utne consecutive slots mein expand hota hai jitne uske eightbytes hain. Isliye struct-by-value utna hi sasta ho sakta hai jitna do loose longs pass karna — koi memory round-trip nahi.
Recall Feynman recap
Question ::: Wo kaun sa ek fact hai jo zyaadatar exercises ek saath solve kar deta hai? Answer ::: ABI ek fixed lookup table hai jo tum source order mein walk karte ho — integers rdi,rsi,rdx,rcx,r8,r9 se dispense hote hain; floats xmm0–7 se; overflow stack par jaata hai; answer rax mein (ya xmm0 mein) return hota hai; aur callee-saved registers (rbx,rbp,r12–r15) untouched wapas dene padte hain. Har problem bas us table ko apply karna hai plus alignment rule.
Related deep material: x86-64 vs ARM64 AAPCS, Recursion and Activation Records, System Calls and Syscall Interface, Name Mangling in C++, Linkers and Object File Format.