Code Generation, Linking & JITs
The last mile: optimized IR becomes real instructions with real registers, separate files get stitched into one executable, and JIT compilers do the whole pipeline live while your program runs.
Codegen makes two hard choices. Instruction selection: which of the CPU's instructions implement each IR operation (x86 can often fold load-add-store into one instruction; ARM can't). Register allocation: the IR pretends registers are infinite, hardware has ~16 — graph coloring assigns the busiest values to registers and 'spills' the rest to stack memory. Register pressure is a real performance force: too many live variables means spills, means memory traffic.
Static linking copies library code into your binary (bigger, self-contained — Go's default). Dynamic linking loads shared libraries (.so/.dll) at startup, so all programs share one libc in memory and library fixes arrive without recompiling — at the cost of 'DLL hell' version mismatches. Every deployment headache about glibc versions traces here.
Where to go deeper: 'Crafting Interpreters' by Robert Nystrom (free online — you build two complete languages), then LLVM's Kaleidoscope tutorial for a real back end. You already have the map; those fill in the territory.