Calling Conventions & ABI
The contract between separately compiled code: argument passing, stack frames, saved registers, mangled names, and what breaking it costs.
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.
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.
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.
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.
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.
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.
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.