The present-day face of Episode 8's "carry it over on paper tape" — making an egg with no chicken
In Episode 8 we saw that the unborn PDP-7 couldn't make programs by itself, so they were made on the GE-635 and carried over on paper tape. "Build it elsewhere and carry it over" — this technique is not merely still in use; it's the foundation for bringing up every new model. New CPUs, smartphones, embedded microcontrollers — each starts from a state with "no chicken" and is brought up by baking an egg for that model on another machine you have on hand (a PC). This is cross-compiling. It solves chicken-and-egg by shifting which machine does what.
To speak precisely about cross-compiling, we separate three roles (GNU's terminology).
build: the machine that builds the compiler itself.
host: the machine on which that compiler runs.
target: the machine on which the code that compiler emits runs (this only matters when you're making a cross-compiler).
How these three match or differ changes the name. Switch between them in the figure below.
When you've just built a new CPU, nothing is running on that target yet. No OS, no compiler, no assembler. The same as the unborn PDP-7 of Episode 8 — native (building for the target on the target) is impossible to begin with. So you bake the target's compiler and OS on a build machine that already runs (the PC on your hand). Building for ARM or RISC-V on an x86 PC, or for an embedded microcontroller that can't even run an OS — all of it uses this method.
Twist it one more turn, and build, host, and target can all three be different. This is called a Canadian Cross. For example — you make a compiler that is built on a fast Linux machine (build), runs on Windows (host), and emits code for ARM (target). The user runs it on Windows, and the product runs on ARM.
The essence of cross-compiling is exactly the same as Episode 8 — "you don't have to make the target on the target. Just borrow a build machine that already runs." Even a new model with no chicken can get an egg from someone else's chicken. This is one and the same skeleton as Episode 3's "borrow an existing cc to ignite," Episode 6's "carry O-code over," and Episode 8's "carry it over on paper tape." By shifting the roles (build/host/target), you take a chicken-and-egg that can't be solved inside this machine and move it to another (already-running) machine to solve it.
Cross-compiling is a means of bringing things up, not the goal. Once you can load the toolchain and OS baked on the build machine onto the target and boot it, eventually the target itself can build for the target — native self-hosting. This is exactly the same plot as Episode 2's Linux v0.11 (leaving the delivery room, MINIX, to stand on its own) and Episode 8's PDP-7 (graduating from GECOS). In the figure below, follow the flow of a new architecture being brought up from "no chicken" to "independence."
(1) Cross-compiling doesn't solve chicken-and-egg. As in Episode 8, it merely stops solving it inside this machine and moves it to another (already-running) machine. The chain is finite, and traced back it bottoms out at the first machine (Episodes 9 & 10). (2) Cross-compiling in practice is quietly hard — assembling the target's headers and libraries (the sysroot), figuring out how to provide libc, testing on an emulator like QEMU when you don't have the real hardware, and so on.
(3) build/host/target is GNU's terminology; other contexts (Rust, for instance) may name or divide things a little differently. (4) The origin of "Canadian Cross" is a widely-told anecdote, and the details have several versions. The point is the structure that "all three machines can be different."
Cross-compiling is the modern edition of Episode 8's "build it elsewhere and carry it over." The three roles — build (the machine that builds), host (the machine the compiler runs on), target (the machine the emitted code runs on) — and how they match or differ determine native (all the same), cross (only target differs), and Canadian Cross (all three differ). A new CPU has nothing on the target at first (no chicken), so native is impossible. So you bake for the target on a build machine that already runs.
The crux is identical to Episode 8 — "you don't have to make the target on the target; just borrow a running build machine." One skeleton with Episode 3 (an existing cc), Episode 6 (carrying O-code), and Episode 8 (paper tape), it solves chicken-and-egg by shifting the roles and moving the problem to another machine. And the target you brought up eventually reaches native self-hosting — the same plot as Episode 2's Linux v0.11 and Episode 8's PDP-7 graduating from GECOS. Cross-compiling doesn't make chicken-and-egg vanish, but with a finite chain, it was always the technique of birthing a new model from an "already-running machine."
Print / save as PDF: ⌘+P (Ctrl+P on Windows). On screen, Figure 1's buttons switch between native / cross / Canadian Cross, and Figure 2's slider follows the bring-up of a new architecture. "See the answer" opens each solution.