Installation
[]
= "0.2"
Or from the terminal:
Usage
A front-end walks its own syntax tree and drives the Builder: a literal becomes a
constant, an operator becomes an instruction, an if becomes two blocks and a
branch, a join becomes a block parameter. The builder hands back a Value for every
result, so SSA numbering is captured without tracking it by hand. When the function
is built, validate confirms it is well-formed.
Lowering fn abs(x: int) -> int { if x < 0 { -x } else { x } }:
use ;
let mut b = new;
let x = b.block_params;
// The merge point takes the result as a parameter.
let join = b.create_block;
let neg_blk = b.create_block;
let pos_blk = b.create_block;
let zero = b.iconst;
let is_neg = b.bin;
b.branch;
b.switch_to;
let negated = b.un;
b.jump;
b.switch_to;
b.jump;
b.switch_to;
let result = b.block_params;
b.ret;
let func = b.finish;
assert!;
A function prints as a readable textual IR, which is handy in tests and when debugging a pass:
use ;
let mut b = new;
let a = b.block_params;
let c = b.block_params;
let sum = b.bin;
let diff = b.bin;
let product = b.bin;
b.ret;
let text = b.finish.to_string;
assert!;
assert!;
Construction does not check well-formedness as it goes; validate does, returning a
defined error rather than panicking when the IR is wrong:
use ;
// `int + bool` is not a valid operation.
let mut b = new;
let x = b.block_params;
let flag = b.block_params;
let oops = b.bin;
b.ret;
assert!;
See docs/API.md for the full reference.
How it works
A Function keeps its blocks and values in flat arenas addressed by dense Block
and Value indices, so building and walking the IR is a linear pass over
contiguous memory rather than a chase through pointers, and a pass can key a side
table directly on a handle's index. Values cross control-flow joins as block
parameters — a jump or branch passes one argument per target parameter — which
keeps the representation flat and removes the bookkeeping of phi nodes.
validate is where correctness is enforced. It checks the structural rules (one
terminator per block, branch targets that exist, argument counts and types that
match the target parameters, a numeric or boolean operand where the operation
requires it) and then the SSA dominance property: every use of a value is reached by
its single definition. Dominators are computed with the Cooper–Harvey–Kennedy
algorithm over the reachable graph, and both the dominator and reachability walks use
an explicit stack, so a deeply nested function cannot overflow the call stack.
Status
v0.2.0 is the core release: the IR types, the Builder lowering
interface, the textual Display, and validate are in place, with the public
surface still being designed across the 0.x series and frozen at 1.0.0.
The crate is self-contained — it defines its own machine-level Type and is driven
entirely through the builder — so it pulls in no first-party dependency; a front-end
maps its own AST and source types onto the IR. Every core invariant is
property-tested against generated programs and verified on Linux, macOS, and Windows.
Contributing
See dev/DIRECTIVES.md for engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.