Backend

Calling Conventions & ABI

The contract between separately compiled code: argument passing, stack frames, saved registers, mangled names, and what breaking it costs.

Calling Conventions
▶ lab

Where arguments go, where the result comes back, who is obliged to preserve what, and how the stack must be aligned at the instruction before a call. The part engineers get wrong: moving values into the argument registers is a parallel copy, and emitting the moves in source order destroys an argument.

Q · How do a caller and a callee that were compiled separately agree on where the arguments are?
Stack Frame Layout
▶ lab

What a prologue actually builds: a saved frame pointer, space for spills and locals, and a return address it did not put there. Omitting the frame pointer buys one register and costs a profiler its stack walk.

Q · What is in a stack frame, who puts it there, and what does `-fomit-frame-pointer` actually cost me?
What an ABI Actually Is

A calling convention plus object layout plus symbol naming plus everything else two separately compiled binaries must agree on. Breaking an ABI does not produce a link error — it produces a field read from the wrong offset, and an answer that is quietly wrong.

Q · What is an ABI, and why does breaking one corrupt data instead of failing to build?
Name Mangling

A linker symbol table maps names to addresses and knows nothing about types, so `foo(int)` and `foo(double)` must arrive as different names. The Itanium ABI spells them `_Z3fooi` and `_Z3food`. C mangles nothing, which is the entire reason `extern "C"` exists.

Q · Why is my symbol called `_ZN3foo3barEid`, and why does `extern "C"` fix my link error?
ABI Stability

Why adding one private field to a class in a shared library breaks every program already compiled against it, what pimpl and reserved padding actually buy, and why Rust deliberately refuses to have a stable ABI at all.

Q · Why can I not add a field to a class in my shared library without rebuilding everything that uses it?
Cross-Compilation

Building on one machine for a different one. The compiler is the easy part: what makes it work is a sysroot containing the target's headers and libraries, because a compiler that reads the host's headers produces a binary for a machine that does not exist.

Q · Why does building for another platform need more than a compiler that can emit its instructions?
Target Triples

The string that names a platform: `x86_64-unknown-linux-gnu`, `aarch64-apple-darwin`, `wasm32-unknown-unknown`. Four fields — architecture, vendor, OS, ABI — and each one changes a different part of the compiler.

Q · What does each field of `x86_64-unknown-linux-gnu` actually change in the compiler?