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

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.

;; 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.

;; 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.

;; 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. Compilationcompile/C-string recognizes the literal and prepares it for emission.
  2. Label Generationemit-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.
        .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.

        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.

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 →