The HIR Builder provides a fluent API for constructing HIR modules directly in Rust, similar to LLVM's IRBuilder or Cranelift's FunctionBuilder. This enables language engineers to translate their AST to HIR without requiring source-based compilation.
use zyntax_compiler::HirBuilder;
use zyntax_typed_ast::AstArena;
let mut arena = AstArena::new();
let mut builder = HirBuilder::new("my_module", &mut arena);
// Build types first
let i32_ty = builder.i32_type();
// Define function: fn add(a: i32, b: i32) -> i32
let func_id = builder.begin_function("add")
.param("a", i32_ty.clone())
.param("b", i32_ty.clone())
.returns(i32_ty.clone())
.build();
// Set function context
builder.set_current_function(func_id);
// Create entry block
let entry = builder.create_block("entry");
builder.set_insert_point(entry);
// Build function body: return a + b
let a = builder.get_param(0);
let b = builder.get_param(1);
let result = builder.add(a, b, i32_ty);
builder.ret(result);
// Finish and get the HIR module
let module = builder.finish();Here's how you would build a standard library Option<T> type and its methods:
use zyntax_compiler::HirBuilder;
use zyntax_typed_ast::AstArena;
let mut arena = AstArena::new();
let mut builder = HirBuilder::new("std_option", &mut arena);
// Define Option<T> as a union type
// union Option<T> {
// None, // variant 0: no value
// Some(T), // variant 1: value of type T
// }
// For demonstration, let's build Option<i32>
let i32_ty = builder.i32_type();
let void_ty = builder.void_type();
let option_i32_variants = vec![
HirUnionVariant {
name: builder.intern("None"),
ty: void_ty, // None carries no data
discriminant: 0,
},
HirUnionVariant {
name: builder.intern("Some"),
ty: i32_ty.clone(), // Some carries an i32
discriminant: 1,
},
];
let option_i32_ty = builder.union_type(Some("Option"), option_i32_variants);
// Build: fn unwrap(opt: Option<i32>) -> i32
let unwrap_func = builder.begin_function("unwrap")
.param("opt", option_i32_ty.clone())
.returns(i32_ty.clone())
.build();
builder.set_current_function(unwrap_func);
let entry = builder.create_block("entry");
let some_block = builder.create_block("some_case");
let none_block = builder.create_block("none_case");
builder.set_insert_point(entry);
// Get parameter and extract discriminant
let opt = builder.get_param(0);
let discriminant = builder.extract_discriminant(opt);
let one = builder.const_i32(1);
// Check if discriminant == 1 (Some)
let is_some = builder.icmp(IntPredicate::Eq, discriminant, one, builder.bool_type());
builder.cond_br(is_some, some_block, none_block);
// Some case: extract and return the value
builder.set_insert_point(some_block);
let value = builder.extract_union_value(opt, 1, i32_ty.clone());
builder.ret(value);
// None case: panic (abort)
builder.set_insert_point(none_block);
builder.panic();
builder.unreachable();
let module = builder.finish();Using Gap 11's extern function support:
use zyntax_compiler::{HirBuilder, CallingConvention};
use zyntax_typed_ast::AstArena;
let mut arena = AstArena::new();
let mut builder = HirBuilder::new("std_alloc", &mut arena);
// Build types
let usize_ty = builder.u64_type();
let u8_ty = builder.u8_type();
let ptr_u8_ty = builder.ptr_type(u8_ty.clone());
let void_ty = builder.void_type();
// Declare extern functions
let malloc = builder.begin_extern_function("malloc", CallingConvention::C)
.param("size", usize_ty.clone())
.returns(ptr_u8_ty.clone())
.build();
let free = builder.begin_extern_function("free", CallingConvention::C)
.param("ptr", ptr_u8_ty.clone())
.returns(void_ty.clone())
.build();
// Build safe wrapper: fn allocate(size: usize) -> Option<*u8>
// Returns None if allocation fails (null pointer)
let option_ptr_variants = vec![
HirUnionVariant {
name: builder.intern("None"),
ty: void_ty,
discriminant: 0,
},
HirUnionVariant {
name: builder.intern("Some"),
ty: ptr_u8_ty.clone(),
discriminant: 1,
},
];
let option_ptr_ty = builder.union_type(Some("Option"), option_ptr_variants);
let allocate_func = builder.begin_function("allocate")
.param("size", usize_ty.clone())
.returns(option_ptr_ty.clone())
.build();
builder.set_current_function(allocate_func);
let entry = builder.create_block("entry");
let null_check = builder.create_block("null_check");
let success_block = builder.create_block("success");
let failure_block = builder.create_block("failure");
builder.set_insert_point(entry);
// Call malloc
let size = builder.get_param(0);
let malloc_ref = builder.function_ref(malloc);
let ptr = builder.call(malloc_ref, vec![size]).unwrap();
// Check if null
builder.br(null_check);
builder.set_insert_point(null_check);
let null = builder.null_ptr(u8_ty);
let is_null = builder.icmp(IntPredicate::Eq, ptr, null, builder.bool_type());
builder.cond_br(is_null, failure_block, success_block);
// Success: return Some(ptr)
builder.set_insert_point(success_block);
let some_value = builder.create_union(option_ptr_ty.clone(), 1, ptr);
builder.ret(some_value);
// Failure: return None
builder.set_insert_point(failure_block);
let none_value = builder.create_union(option_ptr_ty, 0, builder.unit_value());
builder.ret(none_value);
let module = builder.finish();Language frontends can directly construct HIR from their AST without needing to generate and parse Zyntax source code.
The builder API provides compile-time type checking for HIR construction. Invalid HIR structures are caught at Rust compile time.
Developers familiar with LLVM IRBuilder or Cranelift FunctionBuilder will find this API intuitive.
The compiler can ship with a pre-built HIR standard library, avoiding bootstrap issues.
Direct HIR construction is faster than parsing source code.
HirBuilder::new(name, arena)- Create new module builderbuilder.finish()- Complete module and returnHirModule
builder.i32_type(),builder.bool_type(), etc. - Primitive typesbuilder.ptr_type(pointee)- Pointer typesbuilder.struct_type(name, fields)- Struct typesbuilder.union_type(name, variants)- Union/enum typesbuilder.array_type(element, count)- Array types
builder.begin_function(name)- Start regular functionbuilder.begin_extern_function(name, cc)- Start extern functionbuilder.begin_generic_function(name, type_params)- Start generic function.param(name, type)- Add parameter.returns(type)- Set return type.build()- Finish function signature
builder.create_block(name)- Create new blockbuilder.set_insert_point(block)- Set where to insert instructions
builder.add(lhs, rhs, type)- Arithmeticbuilder.load(ptr, type)- Memory loadbuilder.store(value, ptr)- Memory storebuilder.call(callee, args)- Function callbuilder.icmp(pred, lhs, rhs, type)- Integer comparison
builder.ret(value)- Return valuebuilder.br(target)- Unconditional branchbuilder.cond_br(cond, then_block, else_block)- Conditional branchbuilder.switch(value, default, cases)- Switch statementbuilder.unreachable()- Unreachable marker
builder.const_i32(value)- Integer constantbuilder.const_bool(value)- Boolean constantbuilder.null_ptr(type)- Null pointerbuilder.unit_value()- Unit/void value
builder.create_union(type, variant, value)- Create union valuebuilder.extract_union_value(union, variant, type)- Extract variantbuilder.extract_discriminant(union)- Get discriminant
- Compiler Intrinsics - size_of, align_of, transmute
- Link Name Attributes - Custom symbol names for FFI
- Variadic Functions - printf-style functions
- Inline Assembly - Platform-specific operations
- Metadata Attributes - Documentation, optimization hints
// Future API:
let size = builder.call_intrinsic(Intrinsic::SizeOf, vec![], vec![struct_ty]);
let align = builder.call_intrinsic(Intrinsic::AlignOf, vec![], vec![struct_ty]);The HIR Builder enables Gap 10 by allowing the standard library to be written as HIR construction code:
// std/lib.rs - Standard library as HIR builder code
pub fn build_stdlib(arena: &mut AstArena) -> HirModule {
let mut builder = HirBuilder::new("std", arena);
// Build Option<T>
build_option_type(&mut builder);
// Build Result<T, E>
build_result_type(&mut builder);
// Build Vec<T>
build_vec_type(&mut builder);
// Build String
build_string_type(&mut builder);
builder.finish()
}This approach:
- ✅ No parser needed
- ✅ Type-safe construction
- ✅ Version controlled as Rust code
- ✅ Can be unit tested
- ✅ Fast compilation
- ✅ Easy to maintain
Completed: ✅ HIR Builder implementation (708 lines) Tests: ✅ 2/2 passing Next Step: Use HIR Builder to implement Gap 10 (Standard Library)
Created: November 13, 2025 Gap: Gap 10 - Standard Library (Phase 1) Related: Gap 11 - Extern Functions & FFI