Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Compiler Architecture

Furnace is divided into several stages.

Parser

The parser uses pest, a PEG parser generator for Rust.

It converts Sydrogen source into the AST.

The grammar uses explicit precedence rules rather than left-recursive expression rules.

Operator precedence, from highest to lowest, is:

flowchart LR
    A(primary) --> B(postfix)
    B --> C(power)
    C --> D(unary)
    D --> E(multiplicative)
    E --> F(additive)
    F --> G(comparison)
    G --> H(and)
    H --> I(or)
    I --> J(xor)

A documented language rule is that unary operators bind looser than **.

Therefore:

-2 ** 2

is interpreted as:

-(2 ** 2)

which produces:

-4

AST

The AST is represented using Rust structures.

It acts as the representation shared between parsing, semantic analysis, and code generation.

The compiler works with the AST rather than passing raw source text between compiler stages.

Semantic Analysis

Before semantic analysis, project or standalone source loading builds a module table and import dependency graph. Use and Using are resolved into uniquely named declaration references, cycles and visibility errors are diagnosed, and the backends receive one already-resolved program without import statements.

src/semantic.rs validates the AST before code generation.

This stage handles language-level checks such as:

  • Type compatibility
  • Variable lookup
  • Function lookup
  • Function argument counts
  • Collection operations
  • Scope-related checks
  • Control-flow restrictions

Code Generation

Furnace has two code generation paths. The direct native path lowers supported programs to the internal representation in src/ir.rs, writes x86-64 instruction bytes, and wraps them as either an ELF64 Linux executable or a PE32+ Windows executable. The other path in src/codegen.rs converts programs that need typed features to Cranelift IR and produces an object file.

The paths share the AST and semantic analysis. The compiler selects the path after semantic analysis based on the types and statements used by the program. See Native Code Generation for the direct path.

Function Calls

The direct path writes each Sydrogen function as a separate block of machine code. Calls use the System V x86-64 argument registers, and return values use RAX.

The Cranelift path declares each Sydrogen function as an independent Cranelift function and emits calls from the caller.

Collection Layout

The current backend uses malloc for heap allocation of arrays, tuples, and lists.

Their layouts are:

Array (Ore)

OffsetContents
0Length
8Element size
16 onwardElement data

Each element currently occupies 8 bytes.

List (Materials)

OffsetContents
0Length
8Capacity
16Buffer pointer

Tuple (Ore with named fields)

Fields are stored starting at offset 0 in declaration order.

Each field currently occupies 8 bytes.

These layouts are implementation details of the current backend and may change in future compiler versions.

Linking

The direct native path creates its ELF64 or PE32+ executable itself, so it does not call a linker. Target selection is centralized in src/backend/mod.rs; format-specific layout stays in src/backend/elf.rs and src/backend/pe.rs.

The Cranelift path generates a native object file and uses the system C compiler as the linker.

The object file is linked using the system C compiler.

For example:

cc main.o -o main -lm

The linker produces the final executable.


← Previous Next →