How the Maru Bootstrapping Process Works: A Multi-Stage Self-Hosting Guide

Maru’s bootstrapping process is an iterative, multi-stage build system that allows the language to compile itself while running on an older host VM, using a three-actor model of Host, Slave, and Target environments.

The Maru programming language, maintained in the attila-lendvai/maru repository, achieves self-hosting through a sophisticated bootstrapping pipeline defined in the Makefile and orchestrated by source files in source/bootstrapping/. This process ensures that new language features can be introduced while the system remains reproducible and verifiable across different backend targets like x86-64 and LLVM.

The Three-Actor Model in Maru Bootstrapping

Maru’s bootstrapping architecture relies on three distinct roles that interact during the build process. According to doc/bootstrap.md, these actors manage the transition between language versions while maintaining execution capability【1†L64-L73】【1†L74-L87】.

Host represents the previous stage’s VM (for example, maru.9). It provides the eval0 executable capable of running Maru code but potentially lacking support for the newest primitives or optimizations.

Slave constitutes the current stage’s source tree loaded into the host VM. It operates within its own isolated environment (*slave-env*) and contains the definitions that will eventually become the next stage’s executable.

Target serves as the destination environment (*target-env*) that receives the compiled code. Once populated, this environment becomes the new host for the subsequent iteration of the bootstrapping cycle.

Stage-by-Stage Workflow

The Maru bootstrapping process progresses through distinct stages labeled eval0, eval1, and eval2, with an optional eval3 for additional verification. Each stage transforms the source code while maintaining the ability to execute within the previous stage’s context.

Stage 0: Building the Host (eval0)

The bootstrap begins by establishing the host environment. The Makefile creates a Git worktree for the previous stage branch and builds its evaluator:

$(HOST_DIR)/eval:
    git worktree add --detach --force $(BUILD)/$(PREVIOUS_STAGE) $(PREVIOUS_STAGE)
    $(MAKE) --directory=$(BUILD)/$(PREVIOUS_STAGE) eval$(PREVIOUS_STAGE_BACKEND)

This produces eval0, the host binary that will compile the current stage【2†L81-L89】. The source/bootstrapping/prepare.l file then establishes the working directories for both host and slave environments【3†L3-L4】.

Stage 1: Compiling the Slave (eval1)

With the host ready, the system initializes the three-actor model through source/bootstrapping/host-ready.l. This file creates *host-env* (pointing to *globals*), establishes *slave-env* as a new environment, and defines helper functions like eval-in-host and eval-in-slave. It also sets the bootstrapping? flag to true and initializes evolving? to false【4†L19-L45】【4†L46-L53】.

The source/bootstrapping/early.l file then switches execution to the slave environment:

(switch-to-slave-env)
(define feature/profiler/build (eval-in-host 'feature/profiler/build))

This transition ensures subsequent forms are compiled within the slave context rather than the host【5†L3-L7】.

The Makefile then invokes the host compiler to generate eval1. For the x86 backend, this appears as:

$(BUILD_x86)/eval1.s: $(EVAL_OBJ_x86) boot.l $(EMIT_FILES_x86) source/bootstrapping/*.l $(EVALUATOR_FILES)
    @mkdir -p $(BUILD_x86)
    $(call ensure-built,$(EVAL0))
    $(call compile-x86,$(EVAL0_DIR),$(EVAL0),source/platforms/$(PLATFORM)/eval.l,$@)

The compile-x86 function passes the bootstrapping files to the host compiler, producing assembly output that becomes the first self-compiled stage【2†L99-L119】.

Stage 2: Self-Hosting Verification (eval2)

After eval1 is built, source/bootstrapping/late.l exposes host functionality to the slave through forms like host-value and slave-repl. It also performs sanity checks to verify that host and slave environments maintain their expected names【6†L3-L16】【6†L12-L15】:

(define-form host-value (form) `(eval-in-host ',form))
(define-form slave-repl () '(eval-in-slave …))
(when-at-expand (feature debug-info) …)

The final bootstrap stage generates eval2 by using eval1 to compile itself. The Makefile rule uses the slave directory as the source, ensuring the generated binary is identical to eval1:

$(BUILD_x86)/eval2.s: … $(BUILD_x86)/eval1 …
    $(call compile-x86,$(SLAVE_DIR),$(BUILD_x86)/eval1,…,$@)

The test-bootstrap-x86 target then diffs eval1 and eval2 outputs and runs a sanity program to confirm self-consistency【2†L72-L78】【2†L84-L86】.

Bootstrap Shortcuts and Cached Stages

From stage 5 onward, Maru’s build system supports bootstrap shortcuts to accelerate development. The LLVM IR output (eval2.ll) is checked into the repository under build/. When present, the Makefile can skip the recursive host-bootstrapping process and emit the final binary directly from the stored IR【1†L69-L74】.

To force a complete recursive bootstrap—useful for verifying reproducibility or when modifying core compiler primitives—run:

make PREVIOUS_STAGE_EXTRA_TARGETS=veryclean veryclean test-bootstrap-recursively

This removes cached .ll files and rebuilds from eval0 through eval2 to ensure the entire chain remains intact.

Key Bootstrapping Source Files

File Role
doc/bootstrap.md High-level architectural documentation describing the three-actor model【1†L64-L87】
Makefile Orchestrates stage transitions, worktree management, and verification targets【2†L72-L119】
source/bootstrapping/prepare.l Defines *host-directory* and *slave-directory* for path resolution【3†L3-L4】
source/bootstrapping/host-ready.l Initializes *host-env*, *slave-env*, and evaluation helpers【4†L19-L53】
source/bootstrapping/early.l Switches to slave environment and defines early-stage features【5†L3-L7】
source/bootstrapping/late.l Exposes host values to slave and defines host-value/slave-repl forms【6†L3-L16】
boot.l Core loader that initializes the VM and loads bootstrapping files

Summary

  • Maru uses an iterative three-actor model (Host, Slave, Target) to bootstrap new language versions while maintaining execution capability on older VMs.
  • The process generates three distinct stages: eval0 (previous stage host), eval1 (first self-compilation), and eval2 (verified self-hosted binary).
  • Bootstrapping logic resides in source/bootstrapping/ files (prepare.l, host-ready.l, early.l, late.l), which manage environment isolation via *host-env* and *slave-env*.
  • The Makefile automates worktree creation for previous stages, compilation pipelines, and verification via test-bootstrap-x86 or test-bootstrap-llvm.
  • Bootstrap shortcuts allow skipping recursive stages using checked-in LLVM IR (eval2.ll), while make test-bootstrap-recursively forces a complete rebuild for reproducibility testing.

Frequently Asked Questions

What is the difference between eval0, eval1, and eval2 in Maru?

eval0 is the host binary from the previous Maru stage (e.g., maru.9) that runs the current stage's compiler. eval1 is the first executable compiled by eval0 using the current stage's source code—it represents the stage's initial self-compilation. eval2 is produced when eval1 compiles itself, serving as verification that the stage is fully self-hosted and reproducible.

How does Maru isolate the host and slave environments during bootstrapping?

Maru isolates environments through the source/bootstrapping/host-ready.l file, which creates *host-env* (pointing to *globals*) and *slave-env* (a fresh environment). The switch-to-slave-env function moves execution into the slave context, while eval-in-host and eval-in-slave allow controlled cross-environment evaluation. This ensures the current stage's definitions don't pollute the host VM while still allowing the host to execute slave code.

Can I skip the recursive bootstrapping process when building Maru?

Yes, Maru supports bootstrap shortcuts starting from stage 5. The build system checks for pre-generated LLVM IR files (like build/eval2.ll) in the repository. If present, the Makefile can emit the final binary directly without building eval0 or eval1. To force a full recursive bootstrap—necessary when modifying core compiler primitives—run make PREVIOUS_STAGE_EXTRA_TARGETS=veryclean veryclean test-bootstrap-recursively.

What files control the transition between bootstrapping stages?

The transition between stages is orchestrated by several files in source/bootstrapping/: prepare.l sets directory paths; host-ready.l initializes the environment separation and helper functions; early.l switches to the slave environment and defines early features; and late.l exposes host functionality to the slave and performs sanity checks. The Makefile coordinates these files by passing them to the host compiler (eval0) in the correct order to generate eval1 and eval2.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →