A History of UNIX That ClicksEpisode 5 / The Origin of Self-Hosting

The ancestor of the cc that ignited gcc — how did the first C compiler build itself?

The Birth of the C Compiler — Bootstrapping Itself A C compiler written in C can't be built without a C compiler. This is the deepest point of Episode 3's ring. In a world where C did not yet exist, what built the first C compiler? The answer: it was "grown gradually out of B."

Tools you'll need: Episode 3 (gcc's 3-stage bootstrap) The reveal: extend the language, then recompile

In Episode 3 we said "the first gcc was ignited by the machine's existing cc," and promised to descend toward that cc's ancestor. That ancestor is the first C compiler, written in the early 1970s by Dennis Ritchie. But here the ring reaches its very deepest point — the C compiler is written in C, yet there is not a single C anywhere in the world yet. Even an "existing C compiler" to borrow doesn't exist. So how? The answer is that he didn't write C outright — he grew C little by little out of a language called B, which already existed.

01C wasn't designed — it "grew" out of B

C is not a language designed from a blank page. Its family tree is like this — BCPL → (Ken Thompson made) B → (Dennis Ritchie made) C. When the PDP-11 arrived in 1970, B's nature — "typeless, word-oriented" — didn't fit the byte-oriented PDP-11. So Ritchie began adding types (char, int, etc.) to B. This transitional language was called NB (New B), but it was so short-lived that no proper specification of it survives. As B sprouted types and was reworked to emit machine code, it had become C.

/* language family tree (roughly) */ BCPL ──▶ B // Thompson, typeless / word-oriented (PDP-7) B ──▶ NB // Ritchie, adds types (to fit the PDP-11's bytes) NB ──▶ C // to machine-code output. 1973, the Unix kernel rewritten in C

02The chicken and egg of self-hosting — the deepest point

Episode 3's gcc got by with "lend me one existing cc." There was someone to borrow from. But the first C compiler had no one to borrow from — because C did not yet exist. This is the very bottom of the chicken-and-egg of self-hosting.

This episode's crux — don't try to build "all of C" from "nothing"

It looks unsolvable because you imagine "building a complete C compiler, all at once, from nothing." In reality that's not how it goes. Start from a small language that already works (B), add one feature to the compiler and recompile, and widen the language it can handle little by little. Each stage can be built with just the compiler from the previous stage. You don't make C from nothing — you continuously deform B into C.

◇ ◇ ◇

03The reveal — extend the language, then recompile

Let's see this "way of growing" as a staircase. Advance the slider in the figure below. At each step, the language the compiler is written in and the language the compiler can compile climb upward, chasing each other. The moment the two meet at C is the moment self-hosting is achieved.

Figure 1: The staircase of language extension. Add types, machine-code output, structs… to the B compiler and recompile, widening the language it handles up to C. The seed at the base (B / assembly) exits last.
The self-growing loop (each time you add one feature)

Add a new feature (types, structs…) to the compiler. ② Recompile with the current compiler. ③ Now the new feature can also be used in the compiler's own source code. ④ Go back to ①.

Each turn of this small ring makes both the language and the compiler one step richer. "Rewrite yourself using the feature you just built" — the snowballing growth unique to self-hosting.

04The seed exits — the C↔C ring starts to run on its own

Eventually, the C compiler reaches the point where it is written in C and can compile C (its own source). Once that happens, the B seed used for ignition is no longer needed. Same as discarding stage1 in Episode 3 — you remove the scaffold once it's done its job. In 1973, Ritchie and Thompson used this C to rewrite the Unix kernel itself. In the figure below, cut the seed and watch the moment the C↔C ring begins to run on its own.

Figure 2: The seed exits. At first B (the seed) ignites the C compiler. Once self-hosting is reached, you can cut ties with B and the C↔C ring keeps turning entirely on its own.
Right now B (the seed) is igniting the C compiler. It still depends on B.
This episode's place — Episode 0's ring was born here

The self-hosting ring we saw in Episode 0: "gccgccgcc …" — its very first turn was this C compiler of Ritchie's. The gcc ring, and the ring of the compiler you use today, are all, traced back, a continuation of the same ring ignited here. The moment C became a language that "can compile itself" was the starting point of everything that followed.

05And yet the ring continues further down

"Grown out of B" solved C's chicken and egg. But of course the next question waits — then what built that B? B, too, did not simply spring into existence. B came from BCPL, and ran on top of the PDP-7's assembly. The hand outside the ring is still further down. Next time: B and BCPL, and "intermediate code (O-code)."

The honest line — untangling "written in B" from "written in assembly"

(1) The first C compiler is sometimes introduced as "written in assembly," but more precisely — the C compiler evolved continuously from the B compiler, becoming C as Ritchie added types to the B compiler and reworked it toward machine-code output. That foundation (the B implementation) is supported by PDP-11 assembly and threaded code. "Grown out of B" and "assembly underneath" are both true at once.

(2) The self-growing loop of "add a feature and recompile" is a real technique, but the actual work of 1971–73 was far more involved; here we idealize the skeleton. (3) Even under self-hosting, assembly remains in things like the runtime library and startup. It does not mean "everything is completed in C alone." (4) NB was short-lived and poorly documented, and there are various accounts of the details of B→C.

Practice problems (solvable with just this episode)
  1. Arrange C's family tree (three languages) from oldest to newest.
    Show the answer
    BCPL → B → C. B is Thompson's, C is Ritchie's. The language midway through adding types to B was NB (New B).
  2. Why can't you "build the first C compiler with an existing C compiler"?
    Show the answer
    Because not a single C existed anywhere in the world yet, so there was no existing C compiler to borrow. Episode 3's gcc could borrow an existing cc, but at the origin there is none.
  3. So how was the first C compiler actually built? In one sentence.
    Show the answer
    By adding features (types, machine-code output, etc.) little by little to the already-existing B compiler and recompiling, continuously widening the language it handled up to C. Not made from nothing — B was deformed into C.
  4. After self-hosting is reached, what happens to the B seed used for ignition?
    Show the answer
    It becomes unnecessary and exits (same as discarding stage1 in Episode 3). From then on the C compiler is written in C and can reproduce itself = the C↔C ring runs on its own.

SummaryNot made from nothing — B was continuously deformed into C

The ancestor of the cc that ignited gcc is Ritchie's first C compiler. Here the chicken-and-egg of self-hosting reaches its deepest point — the C compiler is written in C, yet there is no C compiler anywhere in the world to borrow. It was solved by giving up on "making complete C from nothing." Following the family tree BCPL→B→C, he added types, machine-code output, and structs to the already-working B compiler, recompiled each time, and continuously deformed the language up to C.

Turning the self-growing loop of "rewrite yourself with the feature you just built," the C compiler reached the point of being written in C and compiling C, and the B seed for ignition exited (same as discarding stage1). In 1973, the Unix kernel was rewritten in this C. The self-hosting ring we saw in Episode 0 was ignited here. — But the ring still continues down. What built that B? Next time: B and BCPL, and the intermediate code O-code.

This document is Episode 5 of the series "A History of UNIX That Clicks." Historical notes: C developed as BCPL → B (Ken Thompson) → C (Dennis Ritchie). The PDP-11 was introduced in 1970; threaded code was used in porting B, and B was bootstrapped with the assembler, dc, and B itself written in B. Around 1971 Ritchie began changing the compiler toward machine-code generation while introducing data types on variables (this transitional language being New B, NB), which developed continuously into C. NB was short-lived and no complete description survives. The skeleton of modern C came together in early 1973, and the PDP-11 Unix kernel was rewritten in C that summer. Self-hosting (the state in which a compiler written in one's own language can compile itself) is a standard technique: prepare an initial seed compiler in another language (assembly or an existing language), then extend the language and recompile to reach it; once reached, the seed can be retired. There is also the summary "the first C compiler was made in assembly," but the reality is a continuous evolution from the B compiler, with PDP-11 assembly at its foundation. Even after self-hosting, assembly remains in things like runtime processing. — To print, use your browser's "Print" and "Save as PDF" (in the printed version, the figure controls and answers are frozen / hidden).

Print / save as PDF: ⌘+P (Ctrl+P on Windows). On screen, use Figure 1's slider for the staircase of language extension, and Figure 2's button to try the seed (B) exiting and the C↔C loop running on its own. "Show the answer" reveals each solution.