Skip to main content
The Dryft compiler uses a sophisticated multi-stage build process that transforms high-level Dryft code into native executables through intermediate representations.

Build Graph Overview

The following diagram illustrates the complete build pipeline:

Pipeline Stages

The build process consists of seven distinct stages, orchestrated by the target system.

1. Frontend Compilation

Location: src/frontend.rs Input:
  • Dryft source code (source.dry)
  • Standard library (stdlib.dry)
Process:
  • Lexical analysis and parsing
  • Semantic analysis
  • Code generation using the selected backend
Output: Backend-specific intermediate representation (IR) The frontend is backend-agnostic. It processes Dryft syntax and calls the backend’s trait methods to generate appropriate IR.
pub fn compile_full(mut backend: Box<dyn Backend>, code: &str) -> String {
    let built = compile(&mut backend, code)
        .body
        .iter()
        .fold(String::new(), |a, b| a + b);
    backend.complete(&built)
}

2. Backend Code Generation

Location: src/backends/ Available backends:
  • C99 (src/backends/c99/): Generates C99-compliant C code
  • x86 (src/backends/x86/): Generates NASM x86-64 assembly
Process: The backend implements the Backend trait, providing methods for:
  • Arithmetic operations (fun_add, fun_sub, etc.)
  • Control flow (create_conditional_statement, create_loop_block)
  • Functions (create_function, user_function)
  • Variables (create_variable, read_variable, write_variable)
  • Stack operations (fun_copy, fun_drop, fun_swap)
Output: Complete IR file written to the path specified in the target’s intermediate field

3. Standard Library Compilation

Command: Defined in target’s stdlib field Example (GCC target):
gcc native/stdc/std.c -c -fPIE -o build/stdc.o
Process:
  • Compiles native bindings for Dryft’s standard library
  • Creates a separate object file for linking
Output: Standard library object file (obj2 in diagram) Note: Some targets (like the x86 target) may skip this step if they don’t use external standard library bindings.

4. Assembly

Command: Defined in target’s assemble field Example (GCC target):
gcc -c -o build/ir.o -w build/ir.c
Example (ELF target):
nasm -f elf64 -o build/obj.o build/ir.asm
Process:
  • Passes the IR to an external compiler/assembler
  • Converts IR into an object file
Output: Program object file (obj1 in diagram) Skip with: --assembly-only flag prevents this and all subsequent steps

5. Linking

Command: Defined in target’s link field Example (GCC target):
gcc build/ir.o build/stdc.o -o a.out
Process:
  • Links program object with standard library object
  • Resolves external symbols
  • Produces final executable
Output: Native executable binary Skip with: --object-only flag stops after assembly, skipping the link step

6. Interpretation (Optional)

Command: Defined in target’s interpret field (default: ./a.out) When executed:
  • Automatically in REPL mode after each input
  • With file compilation when using the --run flag
Process:
  • Executes the compiled binary
  • May invoke a VM or interpreter for non-native targets
Output: Program execution results (stdout/stderr)

Implementation Details

Build Functions

The build process is implemented in src/main.rs with these key functions:

build_file() (line 85)

Compiles a source file to IR:
fn build_file(inp: &Path, out: &Path, backend_name: &str) {
    let src = &String::from_utf8(fs::read(inp).unwrap_or("".into())).unwrap();
    if src.is_empty() {
        println!("Nothing to compile :/");
    } else {
        let backend = crate::backends::select(backend_name);
        fs::write(out, frontend::compile_full(backend, src)).unwrap();
    }
}

stdlib() (line 102)

Compiles the standard library using shell command:
fn stdlib(cmd: &str) {
    let (_, stderr) = bash(cmd);
    simple_print(&stderr);
}

assemble() (line 108)

Runs the external assembler/compiler:
fn assemble(cmd: &str) {
    let (_, stderr) = bash(cmd);
    simple_print(&stderr);
}
Links object files into executable:
fn link(cmd: &str) {
    let (_, stderr) = bash(cmd);
    simple_print(&stderr);
}

interpret() (line 122)

Executes the final binary:
fn interpret(cmd: &str) {
    let (stdout, _) = bash(cmd);
    println!("{}", stdout);
}

Main Compilation Flow

From src/main.rs:219-235, the main function orchestrates the build:
if let Some(f) = cli.inputfile {
    build_file(&f, &targetspec.intermediate, &targetspec.backend);
    stdlib(&targetspec.stdlib.unwrap_or("".to_string()));
    if !cli.assembly_only {
        assemble(&targetspec.assemble.unwrap_or("".to_string()));
        if !cli.object_only {
            link(&targetspec.link.unwrap_or("".to_string()));
        }
    }
    if cli.is_run {
        interpret(
            targetspec
                .interpret
                .unwrap_or("./a.out".to_string())
                .as_ref(),
        );
    }
}

Intermediate Files

Depending on your target and flags, the build process creates these intermediate files:
FileStageDescriptionExample
IR sourceFrontendBackend-specific intermediate representationbuild/ir.c, build/ir.asm
Program objectAssemblyCompiled program codebuild/ir.o
Stdlib objectStdlib compilationStandard library native codebuild/stdc.o
ExecutableLinkingFinal runnable binarya.out

Build Flags and Their Effects

--assembly-only

dryftc --assembly-only program.dry
Pipeline executed:
  1. Frontend compilation ✓
  2. Backend code generation ✓
  3. Standard library compilation ✗
  4. Assembly ✗
  5. Linking ✗
Output: IR file only

--object-only

dryftc --object-only program.dry
Pipeline executed:
  1. Frontend compilation ✓
  2. Backend code generation ✓
  3. Standard library compilation ✓
  4. Assembly ✓
  5. Linking ✗
Output: Object files (program + stdlib)

--run

dryftc --run program.dry
Pipeline executed:
  1. Frontend compilation ✓
  2. Backend code generation ✓
  3. Standard library compilation ✓
  4. Assembly ✓
  5. Linking ✓
  6. Interpretation ✓
Output: Executable + execution results

Error Handling

Errors can occur at any stage:
  • Frontend errors: Syntax errors, semantic errors in Dryft code
  • Backend errors: Code generation failures
  • Assembly errors: Invalid IR, missing tools (gcc, nasm)
  • Linking errors: Undefined symbols, missing libraries
  • Runtime errors: Execution failures (segfaults, etc.)
Each stage prints errors to stderr, which is captured and displayed by the compiler.

Customizing the Build Process

You can customize any stage by creating a custom target file. See Target System for details on:
  • Changing compiler flags
  • Using different assemblers/linkers
  • Cross-compilation setups
  • Alternative execution environments

Build docs developers (and LLMs) love