Comprehensive Examples
This section provides complete, end-to-end examples that demonstrate how to implement complex Y Lang programs using Inkwell, combining multiple concepts from previous sections into working code generation patterns.
Example 1: Complete Function with Local Variables
Y Lang Source:
fn calculate_area(width: i64, height: i64) -> i64 {
let area = width * height;
let doubled = area * 2;
doubled
}
Complete Inkwell Implementation:
#![allow(unused)] fn main() { use inkwell::context::Context; use inkwell::types::BasicMetadataTypeEnum; fn generate_calculate_area(context: &Context, module: &Module, builder: &Builder) { let i64_type = context.i64_type(); // 1. Create function signature let param_types = vec![ BasicMetadataTypeEnum::IntType(i64_type), BasicMetadataTypeEnum::IntType(i64_type), ]; let fn_type = i64_type.fn_type(¶m_types, false); let function = module.add_function("calculate_area", fn_type, None); // 2. Create entry block let entry_block = context.append_basic_block(function, "entry"); builder.position_at_end(entry_block); // 3. Access parameters let width = function.get_nth_param(0).unwrap().into_int_value(); let height = function.get_nth_param(1).unwrap().into_int_value(); // 4. Allocate local variables let area_alloca = builder.build_alloca(i64_type, "area").unwrap(); let doubled_alloca = builder.build_alloca(i64_type, "doubled").unwrap(); // 5. Calculate and store area = width * height let area_value = builder.build_int_mul(width, height, "area_calc").unwrap(); builder.build_store(area_alloca, area_value).unwrap(); // 6. Calculate and store doubled = area * 2 let area_loaded = builder.build_load(i64_type, area_alloca, "area_val").unwrap().into_int_value(); let two = i64_type.const_int(2, false); let doubled_value = builder.build_int_mul(area_loaded, two, "doubled_calc").unwrap(); builder.build_store(doubled_alloca, doubled_value).unwrap(); // 7. Return doubled (last expression) let result = builder.build_load(i64_type, doubled_alloca, "result").unwrap(); builder.build_return(Some(&result)).unwrap(); } }
Generated LLVM IR:
define i64 @calculate_area(i64 %0, i64 %1) {
entry:
%area = alloca i64
%doubled = alloca i64
%area_calc = mul i64 %0, %1
store i64 %area_calc, ptr %area
%area_val = load i64, ptr %area
%doubled_calc = mul i64 %area_val, 2
store i64 %doubled_calc, ptr %doubled
%result = load i64, ptr %doubled
ret i64 %result
}
Key Implementation Steps:
- Define function signature with parameter types
- Create entry basic block for function body
- Extract parameters using
get_nth_param()
- Allocate local variables with
build_alloca()
- Generate computation instructions
- Store intermediate results in local variables
- Load final result and return it
Example 2: Conditional Expression with Complex Logic
Y Lang Source:
fn grade_classifier(score: i64) -> i64 {
if score >= 90 {
1 // A grade
} else if score >= 80 {
2 // B grade
} else if score >= 70 {
3 // C grade
} else {
4 // F grade
}
}
Complete Inkwell Implementation:
#![allow(unused)] fn main() { fn generate_grade_classifier(context: &Context, module: &Module, builder: &Builder) { let i64_type = context.i64_type(); // Function signature let param_types = vec![BasicMetadataTypeEnum::IntType(i64_type)]; let fn_type = i64_type.fn_type(¶m_types, false); let function = module.add_function("grade_classifier", fn_type, None); // Create all basic blocks let entry_block = context.append_basic_block(function, "entry"); let check_90_block = context.append_basic_block(function, "check_90"); let check_80_block = context.append_basic_block(function, "check_80"); let check_70_block = context.append_basic_block(function, "check_70"); let grade_a_block = context.append_basic_block(function, "grade_a"); let grade_b_block = context.append_basic_block(function, "grade_b"); let grade_c_block = context.append_basic_block(function, "grade_c"); let grade_f_block = context.append_basic_block(function, "grade_f"); let merge_block = context.append_basic_block(function, "merge"); // Entry: get parameter and start checking builder.position_at_end(entry_block); let score = function.get_nth_param(0).unwrap().into_int_value(); builder.build_unconditional_branch(check_90_block).unwrap(); // Check if score >= 90 builder.position_at_end(check_90_block); let ninety = i64_type.const_int(90, false); let is_90_or_above = builder.build_int_compare( IntPredicate::SGE, score, ninety, "is_90_or_above" ).unwrap(); builder.build_conditional_branch(is_90_or_above, grade_a_block, check_80_block).unwrap(); // Check if score >= 80 builder.position_at_end(check_80_block); let eighty = i64_type.const_int(80, false); let is_80_or_above = builder.build_int_compare( IntPredicate::SGE, score, eighty, "is_80_or_above" ).unwrap(); builder.build_conditional_branch(is_80_or_above, grade_b_block, check_70_block).unwrap(); // Check if score >= 70 builder.position_at_end(check_70_block); let seventy = i64_type.const_int(70, false); let is_70_or_above = builder.build_int_compare( IntPredicate::SGE, score, seventy, "is_70_or_above" ).unwrap(); builder.build_conditional_branch(is_70_or_above, grade_c_block, grade_f_block).unwrap(); // Grade outcomes builder.position_at_end(grade_a_block); let grade_a = i64_type.const_int(1, false); builder.build_unconditional_branch(merge_block).unwrap(); builder.position_at_end(grade_b_block); let grade_b = i64_type.const_int(2, false); builder.build_unconditional_branch(merge_block).unwrap(); builder.position_at_end(grade_c_block); let grade_c = i64_type.const_int(3, false); builder.build_unconditional_branch(merge_block).unwrap(); builder.position_at_end(grade_f_block); let grade_f = i64_type.const_int(4, false); builder.build_unconditional_branch(merge_block).unwrap(); // Merge all paths with PHI builder.position_at_end(merge_block); let phi = builder.build_phi(i64_type, "grade_result").unwrap(); phi.add_incoming(&[ (&grade_a, grade_a_block), (&grade_b, grade_b_block), (&grade_c, grade_c_block), (&grade_f, grade_f_block), ]); builder.build_return(Some(&phi.as_basic_value())).unwrap(); } }
Generated LLVM IR:
define i64 @grade_classifier(i64 %0) {
entry:
br label %check_90
check_90:
%is_90_or_above = icmp sge i64 %0, 90
br i1 %is_90_or_above, label %grade_a, label %check_80
check_80:
%is_80_or_above = icmp sge i64 %0, 80
br i1 %is_80_or_above, label %grade_b, label %check_70
check_70:
%is_70_or_above = icmp sge i64 %0, 70
br i1 %is_70_or_above, label %grade_c, label %grade_f
grade_a:
br label %merge
grade_b:
br label %merge
grade_c:
br label %merge
grade_f:
br label %merge
merge:
%grade_result = phi i64 [ 1, %grade_a ], [ 2, %grade_b ], [ 3, %grade_c ], [ 4, %grade_f ]
ret i64 %grade_result
}
Example 3: Struct with Methods and Array Processing
Y Lang Source:
struct Point {
x: i64,
y: i64
}
fn process_points(points: [Point; 3]) -> i64 {
let mut sum = 0;
let mut i = 0;
while i < 3 {
let point = points[i];
sum = sum + point.x + point.y;
i = i + 1;
}
sum
}
Complete Inkwell Implementation:
#![allow(unused)] fn main() { fn generate_point_processing(context: &Context, module: &Module, builder: &Builder) { let i64_type = context.i64_type(); // 1. Define Point struct type let point_type = context.struct_type(&[ i64_type.into(), // x field i64_type.into(), // y field ], false); // 2. Define array type: [Point; 3] let point_array_type = point_type.array_type(3); // 3. Function signature: fn process_points(points: [Point; 3]) -> i64 let param_types = vec![BasicMetadataTypeEnum::ArrayType(point_array_type)]; let fn_type = i64_type.fn_type(¶m_types, false); let function = module.add_function("process_points", fn_type, None); // 4. Create basic blocks let entry_block = context.append_basic_block(function, "entry"); let loop_header = context.append_basic_block(function, "loop_header"); let loop_body = context.append_basic_block(function, "loop_body"); let loop_exit = context.append_basic_block(function, "loop_exit"); // 5. Entry block: initialize variables builder.position_at_end(entry_block); // Copy array parameter to local memory let points_param = function.get_nth_param(0).unwrap().into_array_value(); let points_alloca = builder.build_alloca(point_array_type, "points").unwrap(); builder.build_store(points_alloca, points_param).unwrap(); // Initialize local variables let sum_alloca = builder.build_alloca(i64_type, "sum").unwrap(); let i_alloca = builder.build_alloca(i64_type, "i").unwrap(); let zero = i64_type.const_zero(); builder.build_store(sum_alloca, zero).unwrap(); builder.build_store(i_alloca, zero).unwrap(); builder.build_unconditional_branch(loop_header).unwrap(); // 6. Loop header: check condition i < 3 builder.position_at_end(loop_header); let current_i = builder.build_load(i64_type, i_alloca, "current_i").unwrap().into_int_value(); let three = i64_type.const_int(3, false); let condition = builder.build_int_compare( IntPredicate::SLT, current_i, three, "i_lt_3" ).unwrap(); builder.build_conditional_branch(condition, loop_body, loop_exit).unwrap(); // 7. Loop body: process array element builder.position_at_end(loop_body); // Access points[i] let zero_idx = i64_type.const_zero(); let current_i_body = builder.build_load(i64_type, i_alloca, "i_for_access").unwrap().into_int_value(); let point_ptr = unsafe { builder.build_gep( point_array_type, points_alloca, &[zero_idx, current_i_body], "point_ptr" ).unwrap() }; // Load point struct let point = builder.build_load(point_type, point_ptr, "point").unwrap().into_struct_value(); // Extract x and y fields let point_x = builder.build_extract_value(point, 0, "point_x").unwrap().into_int_value(); let point_y = builder.build_extract_value(point, 1, "point_y").unwrap().into_int_value(); // Update sum: sum = sum + point.x + point.y let current_sum = builder.build_load(i64_type, sum_alloca, "current_sum").unwrap().into_int_value(); let sum_plus_x = builder.build_int_add(current_sum, point_x, "sum_plus_x").unwrap(); let new_sum = builder.build_int_add(sum_plus_x, point_y, "new_sum").unwrap(); builder.build_store(sum_alloca, new_sum).unwrap(); // Update i: i = i + 1 let one = i64_type.const_int(1, false); let i_for_increment = builder.build_load(i64_type, i_alloca, "i_for_inc").unwrap().into_int_value(); let new_i = builder.build_int_add(i_for_increment, one, "new_i").unwrap(); builder.build_store(i_alloca, new_i).unwrap(); builder.build_unconditional_branch(loop_header).unwrap(); // 8. Loop exit: return sum builder.position_at_end(loop_exit); let final_sum = builder.build_load(i64_type, sum_alloca, "final_sum").unwrap(); builder.build_return(Some(&final_sum)).unwrap(); } }
Generated LLVM IR:
%Point = type { i64, i64 }
define i64 @process_points([3 x %Point] %0) {
entry:
%points = alloca [3 x %Point]
store [3 x %Point] %0, ptr %points
%sum = alloca i64
%i = alloca i64
store i64 0, ptr %sum
store i64 0, ptr %i
br label %loop_header
loop_header:
%current_i = load i64, ptr %i
%i_lt_3 = icmp slt i64 %current_i, 3
br i1 %i_lt_3, label %loop_body, label %loop_exit
loop_body:
%i_for_access = load i64, ptr %i
%point_ptr = getelementptr [3 x %Point], ptr %points, i64 0, i64 %i_for_access
%point = load %Point, ptr %point_ptr
%point_x = extractvalue %Point %point, 0
%point_y = extractvalue %Point %point, 1
%current_sum = load i64, ptr %sum
%sum_plus_x = add i64 %current_sum, %point_x
%new_sum = add i64 %sum_plus_x, %point_y
store i64 %new_sum, ptr %sum
%i_for_inc = load i64, ptr %i
%new_i = add i64 %i_for_inc, 1
store i64 %new_i, ptr %i
br label %loop_header
loop_exit:
%final_sum = load i64, ptr %sum
ret i64 %final_sum
}
Example 4: Higher-Order Function with Closure
Y Lang Source:
fn map_and_sum(arr: [i64; 3], transform: |i64| -> i64) -> i64 {
let mut sum = 0;
let mut i = 0;
while i < 3 {
let transformed = transform(arr[i]);
sum = sum + transformed;
i = i + 1;
}
sum
}
fn main() -> i64 {
let numbers = [1, 2, 3];
let multiplier = 10;
let closure = |x| x * multiplier;
map_and_sum(numbers, closure)
}
Complete Inkwell Implementation:
#![allow(unused)] fn main() { fn generate_closure_example(context: &Context, module: &Module, builder: &Builder) { let i64_type = context.i64_type(); let ptr_type = context.ptr_type(Default::default()); // 1. Define closure environment for captured variables let closure_env_type = context.struct_type(&[ i64_type.into(), // captured multiplier ], false); // 2. Define closure function type: (env*, i64) -> i64 let closure_fn_param_types = vec![ BasicMetadataTypeEnum::PointerType(closure_env_type.ptr_type(Default::default())), BasicMetadataTypeEnum::IntType(i64_type), ]; let closure_fn_type = i64_type.fn_type(&closure_fn_param_types, false); // 3. Define closure representation: {fn_ptr, env_ptr} let closure_type = context.struct_type(&[ closure_fn_type.ptr_type(Default::default()).into(), closure_env_type.ptr_type(Default::default()).into(), ], false); // 4. Generate the closure function: |x| x * multiplier let closure_function = module.add_function("closure_multiply", closure_fn_type, None); let closure_entry = context.append_basic_block(closure_function, "entry"); builder.position_at_end(closure_entry); let env_param = closure_function.get_nth_param(0).unwrap().into_pointer_value(); let x_param = closure_function.get_nth_param(1).unwrap().into_int_value(); // Extract multiplier from environment let multiplier_ptr = builder.build_struct_gep(closure_env_type, env_param, 0, "multiplier_ptr").unwrap(); let multiplier = builder.build_load(i64_type, multiplier_ptr, "multiplier").unwrap().into_int_value(); // Compute x * multiplier let result = builder.build_int_mul(x_param, multiplier, "multiply_result").unwrap(); builder.build_return(Some(&result)).unwrap(); // 5. Generate map_and_sum function let array_type = i64_type.array_type(3); let map_sum_param_types = vec![ BasicMetadataTypeEnum::ArrayType(array_type), BasicMetadataTypeEnum::StructType(closure_type), ]; let map_sum_fn_type = i64_type.fn_type(&map_sum_param_types, false); let map_sum_function = module.add_function("map_and_sum", map_sum_fn_type, None); // Map and sum implementation blocks let entry_block = context.append_basic_block(map_sum_function, "entry"); let loop_header = context.append_basic_block(map_sum_function, "loop_header"); let loop_body = context.append_basic_block(map_sum_function, "loop_body"); let loop_exit = context.append_basic_block(map_sum_function, "loop_exit"); builder.position_at_end(entry_block); // Copy parameters to local memory let arr_param = map_sum_function.get_nth_param(0).unwrap().into_array_value(); let closure_param = map_sum_function.get_nth_param(1).unwrap().into_struct_value(); let arr_alloca = builder.build_alloca(array_type, "arr").unwrap(); let closure_alloca = builder.build_alloca(closure_type, "closure").unwrap(); builder.build_store(arr_alloca, arr_param).unwrap(); builder.build_store(closure_alloca, closure_param).unwrap(); // Initialize loop variables let sum_alloca = builder.build_alloca(i64_type, "sum").unwrap(); let i_alloca = builder.build_alloca(i64_type, "i").unwrap(); let zero = i64_type.const_zero(); builder.build_store(sum_alloca, zero).unwrap(); builder.build_store(i_alloca, zero).unwrap(); builder.build_unconditional_branch(loop_header).unwrap(); // Loop header builder.position_at_end(loop_header); let current_i = builder.build_load(i64_type, i_alloca, "current_i").unwrap().into_int_value(); let three = i64_type.const_int(3, false); let condition = builder.build_int_compare(IntPredicate::SLT, current_i, three, "i_lt_3").unwrap(); builder.build_conditional_branch(condition, loop_body, loop_exit).unwrap(); // Loop body: call closure with array element builder.position_at_end(loop_body); // Get arr[i] let zero_idx = i64_type.const_zero(); let i_for_access = builder.build_load(i64_type, i_alloca, "i_for_access").unwrap().into_int_value(); let elem_ptr = unsafe { builder.build_gep(array_type, arr_alloca, &[zero_idx, i_for_access], "elem_ptr").unwrap() }; let elem_value = builder.build_load(i64_type, elem_ptr, "elem_value").unwrap().into_int_value(); // Extract closure function and environment let closure_loaded = builder.build_load(closure_type, closure_alloca, "closure_loaded").unwrap().into_struct_value(); let fn_ptr = builder.build_extract_value(closure_loaded, 0, "fn_ptr").unwrap().into_pointer_value(); let env_ptr = builder.build_extract_value(closure_loaded, 1, "env_ptr").unwrap().into_pointer_value(); // Call closure: transform(arr[i]) let call_args = vec![env_ptr.into(), elem_value.into()]; let transformed = builder.build_indirect_call(closure_fn_type, fn_ptr, &call_args, "transformed").unwrap() .try_as_basic_value().left().unwrap().into_int_value(); // Update sum let current_sum = builder.build_load(i64_type, sum_alloca, "current_sum").unwrap().into_int_value(); let new_sum = builder.build_int_add(current_sum, transformed, "new_sum").unwrap(); builder.build_store(sum_alloca, new_sum).unwrap(); // Update i let one = i64_type.const_int(1, false); let i_for_inc = builder.build_load(i64_type, i_alloca, "i_for_inc").unwrap().into_int_value(); let new_i = builder.build_int_add(i_for_inc, one, "new_i").unwrap(); builder.build_store(i_alloca, new_i).unwrap(); builder.build_unconditional_branch(loop_header).unwrap(); // Loop exit builder.position_at_end(loop_exit); let final_sum = builder.build_load(i64_type, sum_alloca, "final_sum").unwrap(); builder.build_return(Some(&final_sum)).unwrap(); // 6. Generate main function let main_fn_type = i64_type.fn_type(&[], false); let main_function = module.add_function("main", main_fn_type, None); let main_entry = context.append_basic_block(main_function, "entry"); builder.position_at_end(main_entry); // Create numbers array: [1, 2, 3] let numbers_array = i64_type.const_array(&[ i64_type.const_int(1, false), i64_type.const_int(2, false), i64_type.const_int(3, false), ]); // Create closure environment with multiplier = 10 let multiplier_value = i64_type.const_int(10, false); let env_alloca = builder.build_alloca(closure_env_type, "env").unwrap(); let multiplier_field = builder.build_struct_gep(closure_env_type, env_alloca, 0, "multiplier_field").unwrap(); builder.build_store(multiplier_field, multiplier_value).unwrap(); // Create closure struct let closure_struct_alloca = builder.build_alloca(closure_type, "closure_struct").unwrap(); let fn_ptr_field = builder.build_struct_gep(closure_type, closure_struct_alloca, 0, "fn_ptr_field").unwrap(); let env_ptr_field = builder.build_struct_gep(closure_type, closure_struct_alloca, 1, "env_ptr_field").unwrap(); let closure_fn_ptr = closure_function.as_global_value().as_pointer_value(); builder.build_store(fn_ptr_field, closure_fn_ptr).unwrap(); builder.build_store(env_ptr_field, env_alloca).unwrap(); // Call map_and_sum let closure_struct_value = builder.build_load(closure_type, closure_struct_alloca, "closure_value").unwrap(); let call_args = vec![numbers_array.into(), closure_struct_value.into()]; let result = builder.build_call(map_sum_function, &call_args, "map_sum_result").unwrap() .try_as_basic_value().left().unwrap(); builder.build_return(Some(&result)).unwrap(); } }
Generated LLVM IR:
%ClosureEnv = type { i64 }
%Closure = type { ptr, ptr }
define i64 @closure_multiply(ptr %0, i64 %1) {
entry:
%multiplier_ptr = getelementptr %ClosureEnv, ptr %0, i32 0, i32 0
%multiplier = load i64, ptr %multiplier_ptr
%multiply_result = mul i64 %1, %multiplier
ret i64 %multiply_result
}
define i64 @map_and_sum([3 x i64] %0, %Closure %1) {
entry:
%arr = alloca [3 x i64]
store [3 x i64] %0, ptr %arr
%closure = alloca %Closure
store %Closure %1, ptr %closure
%sum = alloca i64
%i = alloca i64
store i64 0, ptr %sum
store i64 0, ptr %i
br label %loop_header
loop_header:
%current_i = load i64, ptr %i
%i_lt_3 = icmp slt i64 %current_i, 3
br i1 %i_lt_3, label %loop_body, label %loop_exit
loop_body:
%i_for_access = load i64, ptr %i
%elem_ptr = getelementptr [3 x i64], ptr %arr, i64 0, i64 %i_for_access
%elem_value = load i64, ptr %elem_ptr
%closure_loaded = load %Closure, ptr %closure
%fn_ptr = extractvalue %Closure %closure_loaded, 0
%env_ptr = extractvalue %Closure %closure_loaded, 1
%transformed = call i64 %fn_ptr(ptr %env_ptr, i64 %elem_value)
%current_sum = load i64, ptr %sum
%new_sum = add i64 %current_sum, %transformed
store i64 %new_sum, ptr %sum
%i_for_inc = load i64, ptr %i
%new_i = add i64 %i_for_inc, 1
store i64 %new_i, ptr %i
br label %loop_header
loop_exit:
%final_sum = load i64, ptr %sum
ret i64 %final_sum
}
define i64 @main() {
entry:
%env = alloca %ClosureEnv
%multiplier_field = getelementptr %ClosureEnv, ptr %env, i32 0, i32 0
store i64 10, ptr %multiplier_field
%closure_struct = alloca %Closure
%fn_ptr_field = getelementptr %Closure, ptr %closure_struct, i32 0, i32 0
%env_ptr_field = getelementptr %Closure, ptr %closure_struct, i32 0, i32 1
store ptr @closure_multiply, ptr %fn_ptr_field
store ptr %env, ptr %env_ptr_field
%closure_value = load %Closure, ptr %closure_struct
%map_sum_result = call i64 @map_and_sum([3 x i64] [i64 1, i64 2, i64 3], %Closure %closure_value)
ret i64 %map_sum_result
}
Implementation Patterns Summary
Pattern 1: Function Structure
- Define signature - Parameter types and return type
- Create entry block - Starting point for function body
- Handle parameters - Extract and optionally allocate for mutation
- Generate body - Implement function logic with LLVM instructions
- Handle return - Return value or void
Pattern 2: Control Flow
- Create all blocks first - Plan the control flow graph
- Position builder - Move between blocks systematically
- Generate conditions - Use comparison instructions
- Branch appropriately - Conditional or unconditional branches
- Merge with PHI - Combine values from different paths
Pattern 3: Data Structures
- Define types - Struct, array, or composite types
- Allocate storage - Stack allocation for local data
- Access elements - GEP for arrays, struct_gep for structs
- Load/store values - Move data between memory and registers
- Extract/insert - Work with composite values directly
Pattern 4: Complex Features
- Plan data representation - How to represent language features in LLVM
- Create helper structures - Environment structs for closures, etc.
- Generate helper functions - Functions that implement language semantics
- Coordinate multiple components - Tie together all the pieces
- Optimize for clarity - Keep generated code readable and efficient
These examples demonstrate how to combine the individual concepts from previous sections into complete, working implementations of Y Lang programs using Inkwell and LLVM IR generation.