# How the Maru Bootstrap Isolates Host and Target Environments: A Three-Stage Architecture

> Learn how the Maru bootstrap isolates host and target environments using a three-stage architecture. Explore exclusive environment objects and global pointer rebinding to prevent host primitive leakage.

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

---

**The Maru bootstrap isolates host and target environments by creating three distinct environment objects—`*host-env*`, `*slave-env*`, and `*target-env*`—and switching between them by rebinding the global `*globals*` pointer, ensuring that host-side primitives never leak into the compiled target binary.**

The self-hosting Maru compiler uses a sophisticated bootstrapping process to ensure complete separation between the environment running the bootstrap code and the environment that will become the final compiled binary. This article examines how the `attila-lendvai/maru` repository implements a three-stage architecture to bootstrap isolate host and target environments, preventing contamination between the host VM and the generated target code.

## The Three-Environment Architecture

The bootstrap creates three distinct environments with specific roles and parent-child relationships:

| Environment | Purpose | Creation Location |
|-------------|---------|-------------------|
| **Host** | The original VM that runs the bootstrap code, owns the initial global environment (`*globals*`), and provides primitive I/O and error handling. | `source/boot.l` → `source/bootstrapping/early.l` |
| **Slave** | A fresh module with no parent that becomes the syntax-module for the target. Isolated from the host to prevent accidental use of host-side definitions while generating target code. | `source/bootstrapping/host-ready.l` |
| **Target** | The final environment holding compiled definitions that will be level-shifted into the produced binary. Its parent is the slave environment, making all slave macros visible to the target. | `source/compiler/target.l` |

## Stage 1: Initializing the Host Environment

The bootstrap begins in `source/boot.l`, which sets up a minimal standard library and defines the **bootstrap flag** `bootstrapping?`:

```lisp
;; source/boot.l
(if (not (defined? 'bootstrapping?))
    (eval '(define bootstrapping? ())))

```

Immediately after, `source/bootstrapping/early.l` initiates the transition to the slave environment:

```lisp
;; source/bootstrapping/early.l
(switch-to-slave-env)

```

This early switch ensures that subsequent bootstrap files are loaded into an isolated context rather than polluting the host's global environment.

## Stage 2: Creating the Slave Environment

The critical isolation mechanism is implemented in `source/bootstrapping/host-ready.l`. Here, the bootstrap creates `*slave-env*` as a **fresh environment with no parent** (level 0):

```lisp
;; source/bootstrapping/host-ready.l
(let ((slave-env (environment (if-at-expand (defined? '*maru-module*)
                                   (<module>-globals *maru-module*)
                                   *maru*))))
  (assert (= 0 (<env>-level slave-env)))  ; Verifies no parent
  ;; ...
  )

```

This level-0 environment ensures complete separation from the host. The file then defines the environment switching primitives:

```lisp
;; source/bootstrapping/host-ready.l
(environment-define *slave-env* 'switch-to-host-env
    (lambda ()
      (verbosity 2 (warn "---> host env\n"))
      (set *globals* *host-env*)))

(define-function switch-to-slave-env ()
  (verbosity 2 (warn "---> slave env\n"))
  (set *globals* *slave-env*))

```

The bootstrap also provides `eval-in-host` for cross-environment manipulation without switching:

```lisp
;; source/bootstrapping/host-ready.l
(define eval-in-host (lambda (form) (eval form *host-env*)))

```

## Stage 3: Establishing the Target Environment

Once the compiler infrastructure is loaded into the slave environment, `source/compiler/target.l` creates the **target environment** as a child of the slave:

```lisp
;; source/compiler/target.l
(define *target-env* (environment *slave-env*))

```

This parent-child relationship ensures that all macros and syntax definitions stored in the slave are automatically visible to the target during compilation, while keeping the target's runtime definitions separate.

The target environment is activated by rebinding `*globals*`:

```lisp
;; source/compiler/target.l
(define-function switch-to-target-env ()
  (verbosity 2 (warn "---> target env\n"))
  (set *globals* *target-env*))

(switch-to-target-env)

```

## Cross-Environment Evaluation Primitives

To manipulate one environment while executing in another, the bootstrap provides thin wrappers:

| Wrapper | Function | Source Location |
|---------|----------|----------------|
| `host-value` | Evaluates a form in the host environment | `source/bootstrapping/late.l` |
| `eval-in-slave` | Evaluates a form in the slave environment | `source/bootstrapping/host-ready.l` |
| `target-value` | Evaluates a form in the target environment | `source/compiler/target.l` |

These functions allow the bootstrap to query host-side I/O or debugging facilities while the `*globals*` pointer remains bound to the target environment.

## Isolation Guarantees and Safety Checks

The bootstrap enforces strict isolation through several mechanisms:

- **No shared mutable state**: Each environment maintains its own `*globals*` binding and definition tables.
- **Level-0 slave**: The assertion `(= 0 (<env>-level slave-env))` in `source/bootstrapping/host-ready.l` guarantees the slave has no parent environment.
- **Safety checks**: `source/compiler/target.l` includes verification that `*globals*` is not accidentally the same object during critical phases (lines 123-127).
- **Macro isolation**: Macro expansion occurs in the slave environment, ensuring that target-specific redefinitions—such as redefining `CONS` for the generated binary—do not affect the host's macro system.

## Summary

- The Maru bootstrap creates **three distinct environments**—host, slave, and target—to maintain strict separation between the bootstrap machinery and the compiled output.
- **Environment switching** is achieved by rebinding the global pointer `*globals*` using `switch-to-host-env`, `switch-to-slave-env`, and `switch-to-target-env`.
- The **slave environment** is created as a level-0 environment with no parent in `source/bootstrapping/host-ready.l`, ensuring complete isolation from host definitions.
- The **target environment** is created as a child of the slave in `source/compiler/target.l`, allowing it to access slave macros while keeping runtime definitions separate.
- **Cross-environment evaluation** is supported through wrappers like `host-value` and `target-value`, enabling manipulation of one environment while executing in another.

## Frequently Asked Questions

### What is the difference between the slave and target environments in Maru?

The **slave environment** (`*slave-env*`) serves as the syntax module for the target, holding macro definitions and compiler forms that must be visible during compilation but should not be part of the final runtime. The **target environment** (`*target-env*`) is a child of the slave environment and contains the actual compiled definitions that will be level-shifted into the produced binary, ensuring macros live in the parent while runtime code lives in the child.

### How does Maru prevent host environment definitions from leaking into the target?

Maru prevents leakage by creating the slave environment as a fresh module with no parent (level 0) in `source/bootstrapping/host-ready.l`, ensuring it shares no bindings with the host. The bootstrap then explicitly switches between environments by rebinding `*globals*` using functions like `switch-to-slave-env` and `switch-to-target-env`, guaranteeing that code loaded in one environment cannot accidentally reference bindings from another.

### Why does the Maru bootstrap use three environments instead of two?

The three-environment architecture separates concerns between the **host** (running the bootstrap machinery), the **slave** (holding compiler macros and syntax), and the **target** (holding the final runtime definitions). Using only two environments would force either the host to be polluted with target macros or the target to inherit host-specific primitives, breaking the isolation required for self-hosting and cross-compilation.

### Can the bootstrap process modify the host environment after switching to the target?

Yes, the bootstrap retains the ability to modify the host environment even after switching to the target through the `eval-in-host` function defined in `source/bootstrapping/host-ready.l` and the `host-value` wrapper from `source/bootstrapping/late.l`. These cross-environment evaluation primitives allow the bootstrap to query or manipulate host-side state—such as file I/O or debugging facilities—without permanently switching `*globals*` back to the host environment.