Phase 31
Compiler Bootstrap
Run a C compiler natively inside the OS. A C source file written at the shell prompt (using the editor from Phase 26) can be compiled and executed without leaving the OS. The ultimate target is self-hosting: the compiler compiles itself.
Learning goal
Understand what "bootstrapping" means: deriving a tool from itself.
Category
productivity
Components touched
Builds on
Unlocked by this phase
Why this phase matters
Run a C compiler natively inside the OS. A C source file written at the shell prompt (using the editor from Phase 26) can be compiled and executed without leaving the OS. The ultimate target is self-hosting: the compiler compiles itself.
How it works in m3OS
Phase 31 adds a native C compiler (TCC — Tiny C Compiler) to m3OS. A C source file written with the text editor can be compiled and executed without leaving the OS. The ultimate milestone is self-hosting: TCC compiles itself inside the OS, producing a working compiler binary.
How it builds on earlier phases
This phase builds on Telnet Server and turns that foundation into Compiler Bootstrap.
Implementation outline
- On the host: build TCC from source with
./configure --prefix=/usr --cc=x86_64-linux-musl-gcc. - Verify the resulting binary is a static x86-64 ELF linked against musl.
- Add TCC binary, musl
libc.a, and the musl headers to the disk image build in xtask. - Boot the OS and verify
tcc --versionprints the expected string. - Write a
hello.cinside the OS using the editor, compile it, and run it. - Compile a slightly larger program (e.g., a fibonacci calculator) to stress the heap.
- Attempt the self-hosting milestone:
tcc /usr/src/tcc/tcc.c -o /tmp/tcc2. - Verify the self-compiled TCC produces identical output for
hello.c.
Task progress snapshot
39 of 59 tracked tasks are complete.
Documentation targets
- Explain what “bootstrapping” means and why it is a meaningful milestone.
- Document what TCC needs from the OS and how each syscall maps to OS functionality.
- Explain the musl libc build process and what headers/libs end up in the image.
- Document Path B and when it is the right choice vs. Path A.
- Write a short essay on the history of compiler bootstrapping (Ken Thompson’s “Trusting Trust” lecture is the canonical reference).
Deferred follow-on work
- GCC or Clang as the native compiler
- Dynamic linking and shared libraries
- Debugger support (
gdborlldb) - Multi-stage bootstrap to remove host-compiled binaries from the chain entirely
Source materials
Important code
Code spotlight cards
Code spotlight
Obtain TCC source
Obtain TCC source. Why it matters: pinning the exact TCC version/commit ensures the compiler inside the OS is reproducible and known-good. Focus on `build_tcc` in `xtask/src/main.rs` while you trace the snippet.
/// Phase 31: Cross-compile TCC for x86-64 Linux with musl (static binary).
///
/// Strategy: clone TCC source from repo.or.cz (or use cached clone in
/// target/tcc-src/), configure with `--prefix=/usr` so TCC knows where
/// to find headers and libraries at runtime inside the OS, build with
/// `x86_64-linux-musl-gcc -static`, strip, and place the resulting
/// binary in a staging directory.
///
/// Returns the path to the staging directory containing the TCC binary,
/// or `None` if the build fails (musl cross-compiler not available, etc.).
///
/// The staging directory layout:
/// target/tcc-staging/usr/bin/tcc — TCC binary
/// target/tcc-staging/usr/lib/libc.a — musl libc
/// target/tcc-staging/usr/lib/crt1.o — CRT start
/// target/tcc-staging/usr/lib/crti.o — CRT init prologue
/// target/tcc-staging/usr/lib/crtn.o — CRT init epilogue
/// target/tcc-staging/usr/lib/tcc/include/ — TCC-specific headers
/// target/tcc-staging/usr/include/ — musl system headers
/// target/tcc-staging/usr/src/hello.c — test program
/// target/tcc-staging/usr/src/tcc/ — TCC source for self-hosting
fn build_tcc() -> Option<PathBuf> {
let root = workspace_root();
let staging = root.join("target/tcc-staging"); Code spotlight
sys_open FD lifecycle audit
sys_open FD lifecycle audit. Why it matters: TCC opens dozens of header files sequentially; closed FDs must be recycled so the process does not exhaust the per-process FD table. Focus on `alloc_fd` in `kernel/src/arch/x86_64/syscall.rs` while you trace the snippet.
/// Allocate the lowest available FD slot (starting from `min_fd`) in the
/// current process's FD table. Returns the FD number or `None` if full.
fn alloc_fd(min_fd: usize, entry: FdEntry) -> Option<usize> {
let pid = crate::process::CURRENT_PID.load(core::sync::atomic::Ordering::Relaxed);
let mut table = crate::process::PROCESS_TABLE.lock();
let proc = table.find_mut(pid)?;
for i in min_fd..MAX_FDS {
if proc.fd_table[i].is_none() {
proc.fd_table[i] = Some(entry);
return Some(i);
}
}
None
} Code spotlight
Edit-compile-run cycle
Edit-compile-run cycle. Why it matters: confirms the complete authoring workflow — create source in the editor, compile with TCC, run the result — all within the OS. Focus on `edit_main` and `.c` in `userspace/edit/src/main.rs` while you trace the snippet.
fn edit_main(args: &[&str]) -> i32 {
register_sigwinch_handler();
let mut editor = Editor::new();
if args.len() >= 2 {
editor.open_file(args[1]);
} else {
editor.rows.push(Row::new(Vec::new()));
}
editor.run();
// Restore terminal
let orig = editor.orig_termios;
disable_raw_mode(&orig);
// Clear screen on exit
syscall_lib::write(1, b"\x1b[2J\x1b[H");
0
} Key files
How real OSes differ
- Real systems ship with a full toolchain: compiler, assembler, linker, debugger, make, and a package manager. Bootstrapping a modern Linux distribution from source requires a carefully ordered multi-stage build (tarballs -> stage-1 gcc -> stage-2 gcc -> full system) documented by projects like Linux From Scratch. This phase achieves the same conceptual milestone — a system that can build and run its own tools — using TCC's single-file simplicity instead of a multi-stage GCC build.
Success criteria
- `tcc --version` runs inside the OS and prints the version string.
- `hello.c` compiled by TCC inside the OS runs and prints `hello, world`.
- TCC successfully compiles itself inside the OS (self-hosting milestone).
- The self-compiled `tcc2` passes the same `hello.c` test.
- No host tools are required after the disk image is built.
Related phases
Keep exploring the roadmap
Phase 30
CompleteTelnet Server
A telnet server runs inside the OS, allowing remote users to log in over the network and get an interactive shell session. This is the first demonstration of the OS as a networked, multi-user system — a machine you can connect to from another computer (or another terminal on the host) and do real work.
Phase 32
CompleteBuild Tools
Multi-file C projects can be built inside the OS using a `make`-compatible build tool and shell scripts. This transforms the OS from a "compile one file" environment into one where real software projects can be developed.