# How the Maru Compiler Emits Literal Objects into the Read-Only Segment

> Discover how the Maru compiler emits literal objects into the read-only segment using emit-objectC-string, generating assembler directives and omitting heap header fields for efficient memory management.

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

---

**The Maru compiler emits string literals and immutable objects into the `.rodata` section using the `emit-object/C-string` function, which generates assembler directives like `.section .rodata` and `.asciz`, while omitting heap header fields to prevent garbage collection traversal.**

The Maru self-hosting Lisp compiler targets native x86 architectures and places constant data in read-only memory to protect immutability and reduce binary size. Understanding how literal objects are emitted into the read-only segment requires examining the interaction between assembler pseudo-instructions, object header generation, and the compiler’s code generation pipeline.

## The Three-Stage Emission Process

Maru’s native back-ends coordinate three distinct steps to place literal objects in the read-only data segment. Each step is implemented in the compiler’s emission layer.

### Defining the RODATA Pseudo-Instruction

The assembler abstraction defines a `RODATA` instruction that prints the platform-specific assembler directive for read-only data sections. In `source/compiler/emit-x86-common.l` at lines 97–99, this directive maps to `.section .rodata` on Linux and `.section __TEXT,__const` on macOS.

```lisp
;; From source/compiler/emit-x86-common.l
(define-instruction RODATA ()
  (emit "  .section .rodata"))

```

### Emitting the Literal Object

When the compiler encounters a string literal, it invokes `emit-object/C-string` (lines 90–95 in `source/compiler/emit-x86-common.l`). This function switches to the read-only segment, generates a unique label (e.g., `cstr0`, `cstr1`), and emits the zero-terminated string using the `ASCIZ` pseudo-instruction.

```lisp
;; Conceptual flow from emit-object/C-string
(RODATA)           ; switch to .rodata section
(label "cstr0")    ; define label
(ASCIZ "Maru")     ; emit .asciz "Maru"

```

### Handling Object Headers for Read-Only Data

Heap-allocated objects in Maru contain a header with `next` and `chunk-size` fields for garbage collection. For read-only literals, the compiler **omits** these first two header words to prevent the GC from traversing them. This special case is documented in `source/compiler/emit-x86-objects.l` at lines 38–41 within `emit-object/header`.

```lisp
;; From source/compiler/emit-x86-objects.l
;; For read-only objects, we omit the 'next' and 'chunk-size' slots
;; because the GC must never see them as heap blocks.

```

## Code Generation Flow for String Literals

The complete pipeline from source code to read-only segment involves multiple compiler phases. When the compiler processes a form like `(C-string "hello")`, the following occurs:

1. **Compilation** – `compile/C-string` recognizes the literal and prepares it for emission.
2. **Label Generation** – `emit-object/C-string` creates a unique identifier (e.g., `cstr0`).
3. **Section Switching** – The `RODATA` directive switches the assembler output to `.rodata`.
4. **Data Emission** – The `ASCIZ` directive outputs the null-terminated bytes.
5. **Reference Generation** – The compiler emits a `LOAD` instruction to place the label address into a register.

```asm
        .section .rodata
cstr0:
        .asciz "hello"
        .text
        lea     cstr0(%rip), %rax      ; Load address of read-only literal

```

## Distinguishing Read-Only Literals from Immediate Values

Not all literals in Maru occupy the read-only segment. **Immediate numeric literals** (e.g., `(LITERAL 42)`) are handled by the `LITERAL` operand class defined in `source/compiler/emit-x86-common.l` at line 66. These values are encoded directly into the instruction stream with the `$` prefix and do not require storage in `.rodata`.

```asm
        mov     $42, %rax          ; Immediate literal, no .rodata entry

```

This distinction ensures that small constants incur no memory overhead, while larger immutable objects like strings reside safely in the read-only segment where the garbage collector cannot corrupt or relocate them.

## Summary

- The Maru compiler emits string literals into the `.rodata` section using the `RODATA` pseudo-instruction defined in `source/compiler/emit-x86-common.l`.
- The `emit-object/C-string` function generates unique labels and emits zero-terminated strings via the `ASCIZ` directive.
- Read-only objects omit the `next` and `chunk-size` header fields to prevent garbage collection traversal, as implemented in `source/compiler/emit-x86-objects.l`.
- Immediate numeric literals are encoded directly into instructions using the `LITERAL` operand class and do not occupy the read-only segment.

## Frequently Asked Questions

### How does Maru prevent the garbage collector from scanning read-only literals?

Maru omits the first two header words (`next` and `chunk-size`) when emitting read-only objects in `source/compiler/emit-x86-objects.l`. Because the GC uses these fields to traverse the heap, their absence ensures the collector never interprets a `.rodata` address as a heap-allocated block to be scanned or relocated.

### What is the difference between `RODATA` and `ASCIZ` in Maru’s compiler?

`RODATA` is a pseudo-instruction that switches the assembler output to the read-only data section (`.rodata` on Linux, `__TEXT,__const` on macOS). `ASCIZ` is a separate pseudo-instruction that emits a null-terminated ASCII string at the current position. `emit-object/C-string` uses `RODATA` to select the section, then `ASCIZ` to write the actual bytes.

### Why are immediate numbers not stored in the read-only segment?

Immediate numeric literals use the `LITERAL` operand class defined in `source/compiler/emit-x86-common.l`, which encodes values directly into the instruction stream using the `$` prefix (e.g., `$42`). This avoids the memory overhead and cache pressure of storing small constants in `.rodata`, while strings and larger immutable objects benefit from the space savings and protection offered by the read-only segment.