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

> Discover how Maru's multi-stage bootstrapping process works. Learn its three-actor model for self-hosting and compiling your language.

- Repository: [Attila Lendvai/maru](https://github.com/attila-lendvai/maru)
- Tags: internals
- Published: 2026-02-25

---

**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`](https://github.com/attila-lendvai/maru/blob/main/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:

```make
$(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:

```lisp
(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:

```make
$(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】:

```lisp
(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`:

```make
$(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:

```bash
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`](https://github.com/attila-lendvai/maru/blob/main/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`.