m³OS docs GitHub

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.

Complete

Learning goal

Understand what "bootstrapping" means: deriving a tool from itself.

Category

productivity

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 --version prints the expected string.
  • Write a hello.c inside 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 (gdb or lldb)
  • Multi-stage bootstrap to remove host-compiled binaries from the chain entirely

Source materials

Important code

Code spotlight cards

Code spotlight

Obtain TCC source

xtask/src/main.rs L357-L380

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

kernel/src/arch/x86_64/syscall.rs L2390-L2403

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

userspace/edit/src/main.rs L1096-L1117

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