Skip to main content
Dryft uses a flexible backend system that allows the compiler to target different output formats. The compiler currently supports two backends: C99 (transpiling to C) and x86 (native assembly via NASM).

Backend Architecture

The backend system is built around the Backend trait defined in src/backends.rs:21. Each backend implements this trait to translate Dryft’s intermediate representation into platform-specific code.

The Backend Trait

Every backend must implement the following key methods: Core Functions
  • complete() - Wraps compiled code with runtime dependencies and boilerplate
  • create_function() - Defines callable methods (functions and actions)
  • user_function() - Generates code to call user-defined functions
Stack Operations
  • push_integer() - Push integer literals onto the stack
  • push_string() - Push string literals onto the stack
  • push_true() / push_false() - Push boolean values
  • fun_copy() / fun_drop() / fun_swap() - Stack manipulation primitives
Arithmetic & Logic
  • fun_add(), fun_sub(), fun_mul(), fun_div(), fun_mod() - Math operations
  • fun_simple_equality() / fun_simple_non_equality() - Equality checks
  • fun_logical_not(), fun_logical_and(), fun_logical_or() - Boolean logic
  • fun_num_greater(), fun_num_less_than(), etc. - Numeric comparisons
Control Flow
  • create_conditional_statement() - If-then blocks
  • create_elect_block() - When/elect blocks (switch-like constructs)
  • create_loop_block() - Infinite loops
  • loop_break() - Break from loops
  • method_return() - Return from functions
Variables
  • create_variable() - Declare and initialize variables
  • read_variable() - Push variable value onto stack
  • write_variable() - Pop stack value into variable
External Linking
  • linkin_function() - Link to external native functions

Existing Backends

C99 Backend

The C99 backend (src/backends/c99/mod.rs) transpiles Dryft code to C99-compliant C code. It:
  • Uses a stack-based runtime implemented in base.c
  • Prefixes all Dryft functions with fun_ to avoid naming conflicts
  • Implements variables using size_t C variables with var_ prefix
  • Uses GCC computed goto labels for the elect (when) construct
  • Supports linking to external C functions via linkin
Example output:
void fun_add5() { dryft_push(5); add(); }

x86 Backend

The x86 backend (src/backends/x86/mod.rs) generates NASM-compatible x86-64 assembly. This backend:
  • Outputs native assembly code for Linux ELF64 targets
  • Implements a custom stack in assembly defined in base.asm
  • Is currently under development (many functions use todo!() macros)
  • Generates position-independent code
Example output:
fun_add5:
	mpush 5
	call builtin_add
	ret

How to Add a Backend

1

Create backend directory

Create a new directory in src/backends/ with an appropriate name for your target platform.
mkdir src/backends/mybackend
2

Create mod.rs file

Inside your new directory, create a mod.rs file. This will contain your backend implementation.
use crate::backends::Backend;

pub struct MyBackend {}

impl Backend for MyBackend {
    // Implementation goes here
}
3

Register your backend

In src/backends.rs, add your module at the top with the other backend imports:
pub mod c99;
pub mod x86;
pub mod mybackend;  // Add this line
4

Define a codename

In the select() function in src/backends.rs:90, add a match arm for your backend’s codename:
pub fn select(name: &str) -> Box<dyn Backend> {
    match name {
        "C99" => Box::new(c99::C99Backend {}),
        "x86" => Box::new(x86::Nasm64Backend {}),
        "mybackend" => Box::new(mybackend::MyBackend {}),
        other => panic!("Invalid backend {other}"),
    }
}
5

Implement all trait methods

Back in your mod.rs, implement ALL the required methods from the Backend trait. You can start with todo!() placeholders for incomplete functionality:
fn push_integer(&self, i: &str) -> String {
    format!("LOAD {}", i)  // Your implementation
}

fn push_string(&self, s: &str) -> String {
    todo!("String support not yet implemented")
}
6

Create build profile

Create a TOML file in src/targets/ that defines how to build and run programs using your backend:
[unix]
backend = "mybackend"
intermediate = "build/ir.out"
stdlib = ""
assemble = "myassembler build/ir.out -o build/output.o"
link = "mylinker build/output.o -o a.out"
interpret = "./a.out"
The fields specify:
  • backend - The codename from the select() function
  • intermediate - Path where the backend’s output will be written
  • stdlib - Command to compile standard library (if needed)
  • assemble - Command to assemble/compile the intermediate output
  • link - Command to link object files into an executable
  • interpret - Command to run the final executable
7

Test and ship

Test your backend thoroughly with Dryft programs and fix any issues. Once it works reliably, submit your contribution!

Backend Selection in the Compiler

The frontend (src/frontend.rs:27) compiles Dryft source code into an intermediate representation that is backend-agnostic. The backend is selected via the build profile and transforms this IR into the target format. The compilation flow is:
  1. Frontend parses .dry source files into tokens
  2. Tokens are processed into backend method calls stored in CompileState
  3. Backend generates target-specific code (C, assembly, etc.)
  4. Build profile commands assemble and link the output
  5. Final executable is produced

Tips for Backend Development

  • Study the C99 backend as a reference implementation - it’s the most complete
  • Start with basic operations (integers, arithmetic, functions) before tackling advanced features
  • Use todo!() for unimplemented features so the compiler still builds
  • Test incrementally with simple Dryft programs
  • The MockBackend in backends.rs:98 shows the minimum structure needed
  • Remember that Dryft is stack-based - your backend needs to maintain a data stack

Build docs developers (and LLMs) love