A History of UNIX That Clicks / Bonus 3Build it elsewhere and carry it over — the modern edition

The present-day face of Episode 8's "carry it over on paper tape" — making an egg with no chicken

Cross-Compiling — Making an Egg with No Chicken A brand-new CPU, nothing running on it yet. It has no OS and no compiler (there's no chicken). So how do you prepare the first program? — Bake an egg for that CPU on another machine that already runs. The same skeleton as Episode 8's GE-635 → PDP-7, in a modern edition.

Prerequisites: Episode 8 (cross-assembly) & Episode 3 (ignition) The reveal: shift build / host / target apart

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.

01Three machines — build / host / target

To speak precisely about cross-compiling, we separate three roles (GNU's terminology).

The three roles

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.

Figure 1: build / host / target. Same machine, same color. native = all three the same. cross = only target differs. Canadian Cross = all three differ.

02Why cross — the target has no chicken

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.

# On an x86 PC (build=host) on your desk, cross-compile for ARM (target) $ arm-linux-gnueabihf-gcc -o hello hello.c $ file hello hello: ELF 32-bit ... ARM # won't run on x86. Carry it over to an ARM machine to run it

03Canadian Cross — all three differ

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.

Connecting voice — why "Canadian"? It's a strange name, but the origin is a political joke. At the time this scheme was being sorted out, Canada had three national political parties, and the name plays on that "three of a kind = Canadian (Cross)." One of those developer in-jokes that occasionally sneak into technical vocabulary. It's enough to think of it as taking Episode 8's paper tape, which involved "two machines (the GE-635 and PDP-7)," and bumping it up to three.
The crux this time — solving chicken-and-egg by "shifting"

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.

◇ ◇ ◇

04Eventual independence — the target becomes native

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 targetnative 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."

Figure 2: bringing up a new architecture. The target has nothing at first (no chicken). Cross-compile the toolchain and OS on the build machine and load them, boot, and eventually the target self-hosts natively.
The honest line — cross-compiling isn't magic

(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."

Practice problems (solvable with this bonus alone)
  1. Define build, host, and target in one phrase each.
    See the answer
    build = the machine that builds the compiler; host = the machine on which that compiler runs; target = the machine on which the code that compiler emits runs.
  2. Explain the difference between native, cross, and Canadian Cross in terms of how the triple matches.
    See the answer
    native = build=host=target (all the same). cross = build=host, with only target different. Canadian Cross = build, host, and target all three different.
  3. Why can't native be used when bringing up a new CPU? What in Episode 8 is this the same as?
    See the answer
    Because the target has no compiler and no OS yet (no chicken), so you can't build for the target on the target. It's the same situation as the unborn PDP-7 (Episode 8), and you have no choice but to borrow an existing build machine.
  4. What eventually happens to a target brought up by cross-compiling? Which episode of the main series is this the same plot as?
    See the answer
    Eventually the target itself reaches native self-hosting, able to build for the target. The same as Episode 2's Linux v0.11 (leaving the delivery room to stand on its own) and Episode 8's PDP-7 (graduating from GECOS).

SummaryShift the roles, and you can bake an egg even with no chicken

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."

This document is Bonus 3 of the series "A History of UNIX That Clicks." Technology: in GNU's terminology, build is the machine that runs the compilation work, host is the machine on which the produced binary (the compiler) runs, and target is the machine on which the code that compiler produces runs, when making a cross-compiler. build=host=target is called native; build=host with a different target is called cross; and build, host, and target all being different (including build≠host) is called Canadian Cross. The name Canadian Cross is said to come from Canada having three national political parties at the time this was being sorted out (an anecdote). When bringing up a new architecture, because the target has no operating environment, you cross-compile the target's toolchain, kernel, and userland on an existing build machine, and after booting on the target, eventually transition to native self-hosting. Cross-compiling doesn't solve chicken-and-egg but moves it to another already-running machine; the chain is finite and bottoms out at the first machine (Episodes 9 & 10). There are practical challenges such as the sysroot, libc, and verification with an emulator (QEMU and the like). The naming and divisions differ by context. — To print, use your browser's "Print" and "Save as PDF" (in the print version, the figures' controls and the answers are frozen / hidden).

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.