5.1.10 · D3 · HinglishInstruction Set Architecture (ISA)

Worked examplesCalling conventions and ABI

2,938 words13 min read↑ Read in English

5.1.10 · D3 · Hardware › Instruction Set Architecture (ISA) › Calling conventions and ABI

Hum System V AMD64 ABI (Linux/macOS x86-64) ko concrete model ke roop mein use karte hain, bilkul waise hi jaise parent ne kiya tha. Kuch bhi shuru karne se pehle, ek refresher taaki koi bhi symbol unexplained na rahe:


Scenario matrix

Har calling-convention problem inhi cells mein se ek hoti hai. Neeche ke examples ko us cell ke saath label kiya gaya hai jo woh cover karte hain.

# Case class Kya mushkil hai Example
A Kam args, koi locals nahi Baseline — kuch bhi spill nahi, kuch bhi saved nahi Ex 1
B 6 se zyada int args Args 7+ stack par spill hote hain, right-to-left Ex 2
C Mixed int + float args Do alag counters: GP regs aur XMM regs Ex 3
D Callee-saved discipline Non-volatile register ko push/pop karna zaroori Ex 4
E Recursion / nested frames Har call apna khud ka frame banata hai; return chain Ex 5
F Alignment (degenerate stack) Ek extra push movaps tod deta hai; padding fix Ex 6
G Zero args, void return Degenerate: koi arg regs nahi, rax undefined Ex 7
H Small struct by value Struct ≤ 16 B do registers mein split hoti hai Ex 8
I Word problem (real bug) ABI mismatch se silent corruption hoti hai Ex 9
J Exam twist Mid-call mein kaunse register mein value hai? Ex 10

Ex 1 — Cell A: baseline, sab kuch registers mein

Forecast: aage padhne se pehle teen registers guess karo.

  1. Args ko ordered list se match karo. a→rdi, b→rsi, c→rdx. Yeh step kyun? Order rdi, rsi, rdx, … convention se fixed hai taaki callee — jo kisi bhi caller ko jaane bina likhi gayi hai — hamesha ek hi boxes mein dekhe.
  2. Return register mein compute karo. add3 pehle a+b phir +c ko rax (iske 32-bit half eax) mein daalta hai. Yeh step kyun? Caller sirf rax ko integer return ke liye read karta hai; answer kahin aur daalne ka matlab hai caller garbage read karega.
  3. Koi stack frame nahi. 3 args ≤ 6, koi locals nahi ⇒ kuch bhi spill nahi hota, rsp kabhi nahi hilda. Yeh step kyun? Memory touch karna registers se slower hai; convention is tarah design ki gayi hai ki chhoti functions kabhi nahi karti.

Verify: . Caller eax read karta hai aur 60 paata hai. ✓


Ex 2 — Cell B: saatwan argument stack par spill hota hai

Forecast: hamare paas sirf 6 int registers hain — argument #7 kahan jaega?

  1. Chhe registers fill karo. a→rdi, b→rsi, c→rdx, d→rcx, e→r8, f→r9. Yeh step kyun? Isse 6 ka integer-register budget exhaust ho jaata hai.
  2. Overflow args right-to-left push karo. Caller g (sirf ek overflow) ko call se pehle stack par push karta hai. Yeh step kyun? Right-to-left pushing ka matlab hai leftmost overflow argument sabse chhote address par khatam hota hai — ek fixed layout jise callee index kar sakta hai.
  3. g ko callee ke andar locate karo. Entry par, rsp return address (8 bytes, call dwara push kiya gaya) par point karta hai. g iske bilkul upar baith ta hai, isliye callee [rsp+8] se read karta hai. Yeh step kyun? Callee ko us return address ka hisaab lagana hoga jo call ne iske aur push kiye gaye argument ke beech insert kiya.

Stack figure dekho — return address chalk-blue box hai, g pink box hai iske bilkul upar.

Figure — Calling conventions and ABI

Verify: function g = 7 return karta hai, [rsp+8] se read karke, rax mein rakha. ✓


Ex 3 — Cell C: mixed integer aur floating-point arguments

Forecast: kya ints aur doubles ek counter share karte hain, ya do?

  1. Do independent counters chalao. Integers rdi, rsi, … consume karte hain; doubles xmm0, xmm1, … consume karte hain. Yeh interleave nahi hote ek list mein. Yeh step kyun? Integer aur float values physically alag register files mein rehti hain, isliye ABI do alag cursors rakhta hai.
  2. Assign karo. a→rdi (1st int), x→xmm0 (1st float), b→rsi (2nd int), y→xmm1 (2nd float). Yeh step kyun? Har argument sirf apna counter advance karta hai.
  3. Return. Ek double return xmm0 mein wapas aata hai, rax mein nahi. Yeh step kyun? Float results bhi float register file mein travel karte hain.

Verify: counters 2 ints (rdi, rsi) aur 2 floats (xmm0, xmm1) par khatam hote hain — signature mein 2 ints + 2 doubles se match karta hai. ✓


Ex 4 — Cell D: callee-saved discipline

Forecast: rbx callee-saved hai — yeh g ko kya karne par majboor karta hai?

  1. Entry par save karo. push rbx ise use karne se pehle. Yeh step kyun? rbx non-volatile hai: caller trust karta hai ki uski value call ke baad bachi rahegi. Ise use karne ke liye, g ko pehle caller ki copy stack par rakhni hogi.
  2. Kaam karo. rbx accumulate karta hai. Yeh step kyun? Yeh exactly hai, pehle integers ka sum.
  3. Exit par restore karo. rbx→rax move karo (return), phir pop rbx caller ko uski value wapas dene ke liye. Yeh step kyun? Promise todna caller ko silently corrupt karta hai — parent ka Example 3 warning.

Verify: x=5 ke liye, . rax mein return = 10, rbx restored. ✓


Ex 5 — Cell E: recursion, nested frames

Forecast: sabse gehre point par stack par kitne return addresses hain?

  1. Har call ek frame banata hai. fac(4) calls fac(3) calls fac(2) calls fac(1). Dekho Recursion and Activation Records. Yeh step kyun? Har invocation ko apna khud ka n aur apna khud ka return address chahiye; stack (neeche grow karte hue) har call ko fresh space deta hai jo collide nahi kar sakti.
  2. Return addresses count karo. Sabse gehre point par (fac(1)), chaar calls execute ho chuke hain, isliye chaar return addresses stacked hain. Yeh step kyun? call exactly ek return address push karta hai har invocation per; abhi tak kuch bhi return nahi hua hai.
  3. Unwind karo, multiply karte hue. fac(1)=1, phir 2·1=2, 3·2=6, 4·6=24, har result rax ke through wapas flow karta hai. Yeh step kyun? Har ret apna return address pop karta hai aur apna rax caller ke n * … tak pohoncha ta hai.

Figure dekho — har nested frame ek chalk box hai; arrows rax ko wapas climb karte dikhate hain.

Figure — Calling conventions and ABI

Verify: , rax mein return hota hai. ✓


Ex 6 — Cell F: alignment trap (degenerate stack)

Forecast: push ki gayi bytes count karo aur alignment rule check karo.

  1. rsp mod 16 track karo. g ki entry par, rsp \equiv 8 \pmod{16} (golden rule: call ne 16-aligned stack par 8 bytes push kiye). Yeh step kyun? Alignment ek running invariant hai; tum ise byte by byte audit karte ho.
  2. push rbx ise shift karta hai. push rbx ke baad, rsp \equiv 8 - 8 = 0 \pmod{16}. Yeh step kyun? Har 8-byte push rsp se 8 subtract karta hai, low nibble ko 0 aur 8 ke beech flip karta hai.
  3. Inner call ise tod deta hai. Ab helper call karne se ek aur 8-byte return address push hoti hai: helper enters with rsp \equiv 0 - 8 = 8 \pmod{16} — lekin helper ki apni body phir movaps chahti hai 16-byte boundaries par aur rsp ko 8 se off paati hai. Ek extra sub rsp, 8 (ya ek doosra push) rsp \equiv 8 ko uski entry par restore karta hai taaki uski aligned locals 16 ke multiples par land karein. Yeh step kyun? movaps misalignment par hard fault karta hai; compiler frames ko exactly ek 8-byte gap se pad karta hai invariant ko maintain karne ke liye.

Verify: entry 8; ek push ke baad 0; fix sub rsp,8 deta hai 0-8 \equiv 8 \pmod{16}, required entry value restore karta hai. ✓


Ex 7 — Cell G: zero args, void return (degenerate)

Forecast: trick question — zero args ke liye kitne arg registers?

  1. Koi argument registers touch nahi hote. Zero args ⇒ rdi … r9 mein jo bhi caller ka tha woh hi rahega. Yeh step kyun? Convention registers sirf argument ke hisaab se reserve karta hai; koi argument nahi to kuch bhi reserve nahi karta.
  2. rax undefined hai. Ek void return ka matlab caller ko rax read nahi karna chahiye — isme kuch bhi ho sakta hai, aur beep ne ise clobber kiya ho sakta hai (yeh caller-saved hai). Yeh step kyun? Koi return value nahi matlab rax ke baare mein koi promise nahi; yahan koi value assume karna ek classic bug hai.

Verify: contract check — args used = 0, rax unspecified. ✓ (compute karne ke liye kuch numeric nahi)


Ex 8 — Cell H: small struct passed by value

Forecast: kya 16-byte struct stack par jaati hai, ya registers mein?

  1. Struct classify karo. Yeh exactly 16 bytes ke integer fields hai ⇒ yeh do integer registers mein fit hoti hai. Yeh step kyun? SysV AMD64 ek chhoti (≤ 16 B) struct ko field-by-field registers mein split karta hai memory par push karne ke bajaaye — faster.
  2. Do eightbytes assign karo. p.x→rdi, p.y→rsi. Yeh step kyun? Pehle 8 bytes pehle free GP register mein jaate hain, agle 8 doosre mein.
  3. Scalar return karo. sumP x+y ko rax mein compute karta hai. Yeh step kyun? long result rax mein travel karta hai kisi bhi integer ki tarah.

Verify: rax mein; struct ne rdi, rsi consume kiya (2 registers = 16 bytes / 8). ✓


Ex 9 — Cell I: real-world word problem (silent ABI bug)

Forecast: agar calling convention match karti hai, to kyun toot ta hai?

  1. Mismatch isolate karo. Calling convention (kaun se registers, kaun kya save karta hai) identical hai — isliye linking koi error nahi dhundta. Yeh step kyun? Linkers symbol names match karte hain, data-layout agreements nahi. Dekho Name Mangling in C++ ki kaise names akele type differences chhupa sakte hain.
  2. Asli toot a contract naam do. Data type size/alignment ABI ka hissa hai, calling convention ka nahi. Ek side 10 bytes read karta hai jahan doosre ne 16 likhe. Yeh step kyun? Parent ka steel-man: calling convention ⊂ ABI. Do objects ek convention share kar sakte hain phir bhi long double width par disagree kar sakte hain.
  3. Consequence. Bytes galat read hote hain → silent numeric corruption, koi crash nahi, koi linker warning nahi. Yeh step kyun? Link time par koi bhi byte layout check nahi karta, isliye failure sirf wrong output mein surface hoti hai.

Verify: contract check — convention identical, sizeof(long double) differ karta hai (10 vs 16 bytes) ⇒ ABI mismatch. ✓


Ex 10 — Cell J: exam twist ("mid-call mein kaun sa register?")

Forecast: rcx preserve karna kiska kaam tha?

  1. rcx classify karo. rcx caller-saved (volatile) hai: callee ise freely clobber kar sakta hai. Yeh step kyun? Parent ki list: caller-saved = rax, rcx, rdx, rsi, rdi, r8–r11.
  2. Responsibility locate karo. Kyunki rcx caller-saved hai, A (caller) ko ise call se pehle save karna hoga agar baad mein chahiye — B kuch nahi deta. Yeh step kyun? Split ka poora point yahi hai ki wasted saves avoid ho sakein; volatile registers caller ki problem hain.
  3. Fix. A ko call B se pehle push rcx aur baad mein pop rcx karna chahiye — ya value ko callee-saved register (rbx, r12–r15) mein rakhna chahiye. Yeh step kyun? Dono mein se koi bhi strategy value survive kara deti hai: save-around-call, ya use jagah park karo jahan callee preserve karne ka promise karta hai.

Verify: rcx ∈ caller-saved set ⇒ ise preserve karna caller ki duty hai. A ne nahi kiya ⇒ correct diagnosis. ✓


Recall Har example kaun sa cell tha?

Ex1 A · Ex2 B · Ex3 C · Ex4 D · Ex5 E · Ex6 F · Ex7 G · Ex8 H · Ex9 I · Ex10 J.

Recall Quick self-test

7th integer argument kahan rehta hai? ::: Stack par [rsp+8] par entry par ([rsp] par return address hai). Ek double argument aur ek int argument — kya yeh ek register counter share karte hain? ::: Nahi: ints rdi… use karte hain, floats xmm0… use karte hain, do alag counters. void function — kya rax meaningful hai? ::: Nahi, undefined; kabhi read mat karo. rcx ek call ke across clobber hua — kiski galti? ::: Caller ki; rcx caller-saved hai, isliye caller ko ise preserve karna hoga. Same calling convention lekin long double size differ kare — kya toot ta hai? ::: ABI (data sizing), silent corruption karti hai; linker ise pakad nahi paega.

Dekho bhi: x86-64 vs ARM64 AAPCS ki ARM par yeh cells kaise change hoti hain, aur System Calls and Syscall Interface argument passing ke kernel-boundary variant ke liye.