refactor: centralize IR construction invariants

This commit is contained in:
2026-10-25 02:07:34 +02:00
parent 2c11772931
commit c01d935a7b
6 changed files with 332 additions and 176 deletions
+113 -113
View File
@@ -10,10 +10,10 @@ add_function proc(
locals []ir.Type,
parameter_count usize,
result ir.Type,
value_count usize,
) ir.FunctionId ! mem.AllocError {
) ir.FunctionId ! (mem.AllocError | diagnostics.CapacityError) {
function := ir.init_function(module.allocator)
errdefer {
transferred := false
errdefer if (!transferred) {
arraylist.deinit(&function.parameters)
arraylist.deinit(&function.locals)
arraylist.deinit(&function.array_storage)
@@ -24,22 +24,24 @@ add_function proc(
}
arraylist.deinit(&function.blocks)
}
for (locals) |type, index| {
try arraylist.append(&function.locals, type)
if (index < parameter_count) try arraylist.append(&function.parameters, type)
}
function.result = result
function.value_count = value_count
try arraylist.append(&function.blocks, ir.init_block(module.allocator))
id :: ir.FunctionId(module.functions.items.len)
try arraylist.append(&module.functions, function)
transferred = true
for (locals) |type, index| {
if (index < parameter_count) {
_ = try ir.add_parameter(module, id, type, ir.no_array)
} else {
_ = try ir.add_local(module, id, type, ir.no_array)
}
}
return id
}
@hide:file
emit proc(module @mut ir.Module, function ir.FunctionId, instruction ir.Instruction) void ! mem.AllocError {
if (instruction.type == .address) module.functions.items[usize(function)].has_addresses = true
try arraylist.append(&module.functions.items[usize(function)].blocks.items[0].instructions, instruction)
emit proc(module @mut ir.Module, function ir.FunctionId, instruction ir.Instruction) void ! (mem.AllocError | diagnostics.CapacityError) {
_ = try ir.emit(module, function, ir.BlockId(0), instruction)
}
@hide:file
@@ -91,15 +93,15 @@ repeated_calls_have_fresh_parameters_locals_and_temporaries test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [2]ir.Type :: [.int, .int]
bump :: try add_function(&module, types[..], 1, .int, 4)
bump :: try add_function(&module, types[..], 1, .int)
try emit(&module, bump, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
try emit(&module, bump, integer(1, 1))
try emit(&module, bump, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
try emit(&module, bump, ir.Instruction{ op = .store_local, left = ir.ValueId(2), slot = 1 })
try emit(&module, bump, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(3), slot = 1 })
return_value(&module, bump, ir.ValueId(3))
noop :: try add_function(&module, &[], 0, .void, 0)
main :: try add_function(&module, &[], 0, .int, 8)
noop :: try add_function(&module, &[], 0, .void)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, function_value(0, bump))
try emit(&module, main, integer(1, 19))
@@ -125,8 +127,8 @@ lazy_globals_follow_forward_dependencies_and_reset_between_runs test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
first :: try add_function(&module, &[], 0, .int, 3)
second :: try add_function(&module, &[], 0, .int, 1)
first :: try add_function(&module, &[], 0, .int)
second :: try add_function(&module, &[], 0, .int)
try emit(&module, first, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 1 })
try emit(&module, first, integer(1, 1))
try emit(&module, first, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
@@ -135,7 +137,7 @@ lazy_globals_follow_forward_dependencies_and_reset_between_runs test {
return_value(&module, second, ir.ValueId(0))
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = first, span = diagnostics.Span{ start = 0, end = 0 } })
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = second, span = diagnostics.Span{ start = 0, end = 0 } })
main :: try add_function(&module, &[], 0, .int, 3)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 })
try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(1), slot = 0 })
@@ -164,7 +166,7 @@ function_aliases_and_borrowed_string_values_execute test {
memcopy!(owned, text)
try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len })
transferred = true
target :: try add_function(&module, &[], 0, .string, 1)
target :: try add_function(&module, &[], 0, .string)
try emit(&module, target, ir.Instruction{
op = .constant,
type = .string,
@@ -172,11 +174,11 @@ function_aliases_and_borrowed_string_values_execute test {
constant = ir.Value{ type = .string, data = ir.ValueData{ string = ir.StringId(0) } },
})
return_value(&module, target, ir.ValueId(0))
initializer :: try add_function(&module, &[], 0, .function, 1)
initializer :: try add_function(&module, &[], 0, .function)
try emit(&module, initializer, function_value(0, target))
return_value(&module, initializer, ir.ValueId(0))
try arraylist.append(&module.globals, ir.Global{ type = .function, initializer = initializer, span = diagnostics.Span{ start = 0, end = 0 } })
main :: try add_function(&module, &[], 0, .string, 2)
main :: try add_function(&module, &[], 0, .string)
module.entry = main
try emit(&module, main, ir.Instruction{ op = .load_global, type = .function, destination = ir.ValueId(0), slot = 0 })
try emit(&module, main, ir.Instruction{ op = .call, type = .string, destination = ir.ValueId(1), left = ir.ValueId(0) })
@@ -195,7 +197,7 @@ premature_local_reads_and_global_cycles_report_instruction_spans test {
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 14, end = 19 }
types [1]ir.Type :: [.int]
main :: try add_function(&module, types[..], 0, .int, 1)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0, span = span })
return_value(&module, main, ir.ValueId(0))
@@ -203,8 +205,8 @@ premature_local_reads_and_global_cycles_report_instruction_spans test {
defer deinit(&premature)
try expect_trap(premature, &store, .read_before_initialization, span)
first :: try add_function(&module, &[], 0, .int, 1)
second :: try add_function(&module, &[], 0, .int, 1)
first :: try add_function(&module, &[], 0, .int)
second :: try add_function(&module, &[], 0, .int)
cycle_span :: diagnostics.Span{ start = 40, end = 44 }
try emit(&module, first, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 1 })
try emit(&module, second, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0, span = cycle_span })
@@ -212,7 +214,10 @@ premature_local_reads_and_global_cycles_report_instruction_spans test {
return_value(&module, second, ir.ValueId(0))
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = first, span = diagnostics.Span{ start = 0, end = 0 } })
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = second, span = diagnostics.Span{ start = 0, end = 0 } })
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 }
cycle_main :: try add_function(&module, &[], 0, .int)
module.entry = cycle_main
try emit(&module, cycle_main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 })
return_value(&module, cycle_main, ir.ValueId(0))
cycle := try run(mem.c_allocator, &module, &store)
defer deinit(&cycle)
try expect_trap(cycle, &store, .initialization_cycle, cycle_span)
@@ -232,9 +237,9 @@ existing_trap_identity_entry_failures_and_unused_errors_survive test {
defer deinit(&absent)
try testing.expect_equal(entry_id, absent.trap?)
invalid_id :: try diagnostics.add(&store, .unknown_name, diagnostics.Span{ start = 12, end = 17 })
bad :: try add_function(&module, &[], 0, .invalid, 0)
bad :: try add_function(&module, &[], 0, .invalid)
module.functions.items[usize(bad)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = invalid_id }
main :: try add_function(&module, &[], 0, .int, 2)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, integer(0, 42))
return_value(&module, main, ir.ValueId(0))
@@ -242,8 +247,11 @@ existing_trap_identity_entry_failures_and_unused_errors_survive test {
defer deinit(&success)
try testing.expect(success.trap == null)
try testing.expect_equal(i64(42), success.value.data.integer)
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = function_value(0, bad)
try emit(&module, main, ir.Instruction{ op = .call, type = .invalid, destination = ir.ValueId(1), left = ir.ValueId(0) })
trap_main :: try add_function(&module, &[], 0, .int)
module.entry = trap_main
try emit(&module, trap_main, function_value(0, bad))
try emit(&module, trap_main, ir.Instruction{ op = .call, type = .invalid, destination = ir.ValueId(1), left = ir.ValueId(0) })
return_value(&module, trap_main, ir.ValueId(0))
trapped := try run(mem.c_allocator, &module, &store)
defer deinit(&trapped)
try testing.expect_equal(invalid_id, trapped.trap?)
@@ -286,7 +294,7 @@ integer_arithmetic_checks_boundaries_before_host_operations test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 8, end = 16 }
main :: try add_function(&module, &[], 0, .int, 3)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, integer(0, case.left))
try emit(&module, main, integer(1, case.right))
@@ -309,7 +317,7 @@ integer_negation_traps_only_at_minimum test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 21, end = 27 }
main :: try add_function(&module, &[], 0, .int, 2)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, integer(0, minval!(i64)))
try emit(&module, main, ir.Instruction{ op = .negate, type = .int, destination = ir.ValueId(1), left = ir.ValueId(0), span = span })
@@ -329,7 +337,7 @@ float_arithmetic_and_ieee_zero_division_execute test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .float, 7)
main :: try add_function(&module, &[], 0, .float)
module.entry = main
try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(0), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 10.5 } } })
try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(1), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 2.0 } } })
@@ -413,7 +421,7 @@ recursive_invocations_use_heap_frames_and_free_on_allocation_failure test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .void, 2)
main :: try add_function(&module, &[], 0, .void)
module.entry = main
recursive_block :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = recursive_block }
@@ -440,13 +448,13 @@ frames_and_globals_are_freed_at_every_allocation_boundary_and_trap test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
initializer :: try add_function(&module, types[..], 0, .int, 1)
initializer :: try add_function(&module, types[..], 0, .int)
init_block :: try add_block(&module, initializer)
module.functions.items[usize(initializer)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = init_block }
try emit_at(&module, initializer, init_block, integer(0, 42))
module.functions.items[usize(initializer)].blocks.items[usize(init_block)].terminator = ir.Terminator{ value = ir.ValueId(0) }
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = initializer, span = diagnostics.Span{ start = 0, end = 0 } })
main :: try add_function(&module, types[..], 0, .int, 1)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
work :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = work }
@@ -490,7 +498,7 @@ runtime_diagnostic_allocation_failure_is_infrastructure_error_and_frees_frames t
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
types [1]ir.Type :: [.int]
main :: try add_function(&module, types[..], 0, .int, 1)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
return_value(&module, main, ir.ValueId(0))
@@ -523,9 +531,8 @@ add_block proc(module @mut ir.Module, function ir.FunctionId) ir.BlockId ! mem.A
}
@hide:file
emit_at proc(module @mut ir.Module, function ir.FunctionId, block ir.BlockId, instruction ir.Instruction) void ! mem.AllocError {
if (instruction.type == .address) module.functions.items[usize(function)].has_addresses = true
try arraylist.append(&module.functions.items[usize(function)].blocks.items[usize(block)].instructions, instruction)
emit_at proc(module @mut ir.Module, function ir.FunctionId, block ir.BlockId, instruction ir.Instruction) void ! (mem.AllocError | diagnostics.CapacityError) {
_ = try ir.emit(module, function, block, instruction)
}
branching_recursive_calls_and_lazy_initializers_resume_the_current_block test {
@@ -534,7 +541,7 @@ branching_recursive_calls_and_lazy_initializers_resume_the_current_block test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
count :: try add_function(&module, types[..], 1, .int, 9)
count :: try add_function(&module, types[..], 1, .int)
done :: try add_block(&module, count)
recurse :: try add_block(&module, count)
try emit(&module, count, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
@@ -552,7 +559,7 @@ branching_recursive_calls_and_lazy_initializers_resume_the_current_block test {
try emit_at(&module, count, recurse, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(7), left = ir.ValueId(6), right = ir.ValueId(4) })
module.functions.items[usize(count)].blocks.items[usize(recurse)].terminator = ir.Terminator{ value = ir.ValueId(7) }
initializer :: try add_function(&module, &[], 0, .int, 3)
initializer :: try add_function(&module, &[], 0, .int)
init_entry :: try add_block(&module, initializer)
init_exit :: try add_block(&module, initializer)
trap_id :: try diagnostics.add(&store, .invalid_escape, diagnostics.Span{ start = 0, end = 0 })
@@ -566,7 +573,7 @@ branching_recursive_calls_and_lazy_initializers_resume_the_current_block test {
module.functions.items[usize(initializer)].blocks.items[usize(init_exit)].terminator = ir.Terminator{ value = ir.ValueId(2) }
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = initializer, span = diagnostics.Span{ start = 0, end = 0 } })
main :: try add_function(&module, &[], 0, .int, 5)
main :: try add_function(&module, &[], 0, .int)
main_entry :: try add_block(&module, main)
main_exit :: try add_block(&module, main)
module.entry = main
@@ -611,7 +618,7 @@ all_numeric_comparisons_dispatch_on_operand_type_not_bool_result_type test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3)
main :: try add_function(&module, &[], 0, .bool)
module.entry = main
left_value := ir.Value{ type = type, data = ir.ValueData{ integer = left } }
right_value := ir.Value{ type = type, data = ir.ValueData{ integer = right } }
@@ -650,7 +657,7 @@ nan_comparisons_obey_ieee_rules_in_either_operand_position test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3)
main :: try add_function(&module, &[], 0, .bool)
module.entry = main
try emit(&module, main, ir.Instruction{
op = .constant, type = .float, destination = ir.ValueId(0),
@@ -680,7 +687,7 @@ boolean_equality_and_branching_use_boolean_payloads test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .int, 5)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
yes :: try add_block(&module, main)
no :: try add_block(&module, main)
@@ -710,7 +717,7 @@ assign_local_replaces_initialized_values_without_producing_a_result test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
main :: try add_function(&module, types[..], 0, .int, 3)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
try emit(&module, main, integer(0, 1))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) })
@@ -735,7 +742,7 @@ reset_local_invalidates_reads_and_assignments_at_the_instruction_span test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
main :: try add_function(&module, types[..], 0, .int, 2)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
span :: diagnostics.Span{ start = 21, end = 26 }
try emit(&module, main, integer(0, 42))
@@ -758,7 +765,7 @@ premature_assignment_and_reset_traps_release_frames_at_all_allocation_boundaries
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
main :: try add_function(&module, types[..], 0, .int, 1)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
span :: diagnostics.Span{ start = 14, end = 19 }
try emit(&module, main, integer(0, 42))
@@ -793,7 +800,7 @@ assignment_diagnostic_allocation_failure_releases_runtime_storage test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
types [1]ir.Type :: [.int]
main :: try add_function(&module, types[..], 0, .int, 1)
main :: try add_function(&module, types[..], 0, .int)
module.entry = main
try emit(&module, main, integer(0, 42))
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(0) })
@@ -825,7 +832,7 @@ logical_not_inverts_boolean_payload_and_composes_in_typed_ir test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3)
main :: try add_function(&module, &[], 0, .bool)
module.entry = main
try emit(&module, main, ir.Instruction{
op = .constant,
@@ -859,27 +866,12 @@ unsigned proc(destination u32, value u64) ir.Instruction {
}
@hide:file
add_array proc(module @mut ir.Module, element ir.Type, child ir.ArrayId, len usize) ir.ArrayId ! mem.AllocError {
width :: if (element == .array) module.arrays.items[usize(child)].cells else 1
id :: ir.ArrayId(module.arrays.items.len)
try arraylist.append(&module.arrays, ir.ArrayType{
element = element, element_array = child, len = len, cells = len * width, mutable = true,
})
return id
add_array proc(module @mut ir.Module, element ir.Type, child ir.ArrayId, len usize) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
return try ir.add_array(module, element, child, len, true)
}
@hide:file
array_storage proc(module @mut ir.Module, function ir.FunctionId, index u32, shape ir.ArrayId, local bool) void ! mem.AllocError {
body :: &module.functions.items[usize(function)]
try arraylist.append(&body.array_storage, ir.ArrayStorage{
index = index, array_type = shape, offset = body.aggregate_cells, local = local,
})
body.aggregate_cells += module.arrays.items[usize(shape)].cells
}
@hide:file
literal proc(module @mut ir.Module, function ir.FunctionId, destination u32, shape ir.ArrayId, elements []u32) void ! mem.AllocError {
try array_storage(module, function, destination, shape, false)
literal proc(module @mut ir.Module, function ir.FunctionId, destination u32, shape ir.ArrayId, elements []u32) void ! (mem.AllocError | diagnostics.CapacityError) {
block :: &module.functions.items[usize(function)].blocks.items[0]
start :: block.arguments.items.len
for (elements) |element| try arraylist.append(&block.arguments, ir.ValueId(element))
@@ -896,12 +888,12 @@ nested_array_places_and_rvalues_copy_only_their_typed_leaf_segments test {
defer diagnostics.deinit(&store)
row :: try add_array(&module, .int, ir.no_array, 2)
matrix :: try add_array(&module, .array, row, 2)
types [2]ir.Type :: [.array, .array]
main :: try add_function(&module, types[..], 0, .array, 15)
main :: try add_function(&module, &[], 0, .array)
_ = try ir.add_local(&module, main, .array, matrix)
_ = try ir.add_local(&module, main, .array, matrix)
module.entry = main
module.functions.items[usize(main)].result_array_type = matrix
try array_storage(&module, main, 0, matrix, true)
try array_storage(&module, main, 1, matrix, true)
try emit(&module, main, integer(0, 1))
try emit(&module, main, integer(1, 2))
pair [2]u32 :: [0, 1]
@@ -909,14 +901,14 @@ nested_array_places_and_rvalues_copy_only_their_typed_leaf_segments test {
rows [2]u32 :: [2, 2]
try literal(&module, main, 3, matrix, rows[..])
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(3) })
try array_storage(&module, main, 4, matrix, false)
try emit(&module, main, ir.Instruction{ op = .load_local, type = .array, array_type = matrix, destination = ir.ValueId(4), slot = 0 })
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(4) })
try emit(&module, main, unsigned(5, 0))
try emit(&module, main, unsigned(6, 1))
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = matrix, destination = ir.ValueId(7), slot = 0 })
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .array, array_type = row, destination = ir.ValueId(8), left = ir.ValueId(7), right = ir.ValueId(5) })
try array_storage(&module, main, 9, row, false)
try emit(&module, main, ir.Instruction{ op = .load_address, type = .array, array_type = row, destination = ir.ValueId(9), left = ir.ValueId(8) })
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(10), left = ir.ValueId(8), right = ir.ValueId(6) })
try emit(&module, main, integer(11, 9))
@@ -926,12 +918,12 @@ nested_array_places_and_rvalues_copy_only_their_typed_leaf_segments test {
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(10), right = ir.ValueId(11) })
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .array, array_type = row, destination = ir.ValueId(8), left = ir.ValueId(7), right = ir.ValueId(6) })
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(8), right = ir.ValueId(9) })
try array_storage(&module, main, 12, matrix, false)
try emit(&module, main, ir.Instruction{ op = .load_local, type = .array, array_type = matrix, destination = ir.ValueId(12), slot = 0 })
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(12) })
try emit(&module, main, integer(13, 7))
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(10), right = ir.ValueId(13) })
try array_storage(&module, main, 14, matrix, false)
try emit(&module, main, ir.Instruction{ op = .load_local, type = .array, array_type = matrix, destination = ir.ValueId(14), slot = 1 })
return_value(&module, main, ir.ValueId(14))
outcome := try run(mem.c_allocator, &module, &store)
@@ -949,13 +941,11 @@ array_arguments_and_returns_are_private_values_before_frame_release test {
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1)
pair_shape :: try add_array(&module, .int, ir.no_array, 2)
types [2]ir.Type :: [.array, .array]
callee :: try add_function(&module, types[..], 1, .array, 6)
callee :: try add_function(&module, &[], 0, .array)
_ = try ir.add_parameter(&module, callee, .array, shape)
_ = try ir.add_local(&module, callee, .array, shape)
module.functions.items[usize(callee)].result_array_type = shape
try array_storage(&module, callee, 0, shape, true)
try array_storage(&module, callee, 1, shape, true)
try array_storage(&module, callee, 0, shape, false)
try array_storage(&module, callee, 5, shape, false)
try emit(&module, callee, ir.Instruction{ op = .load_local, type = .array, array_type = shape, destination = ir.ValueId(0), slot = 0 })
try emit(&module, callee, ir.Instruction{ op = .store_local, left = ir.ValueId(0), slot = 1 })
try emit(&module, callee, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(1), slot = 1 })
@@ -970,14 +960,14 @@ array_arguments_and_returns_are_private_values_before_frame_release test {
try emit(&module, callee, integer(4, 7))
try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) })
return_value(&module, callee, ir.ValueId(5))
main :: try add_function(&module, &[], 0, .array, 8)
main :: try add_function(&module, &[], 0, .array)
module.entry = main
module.functions.items[usize(main)].result_array_type = pair_shape
try emit(&module, main, integer(0, 42))
element [1]u32 :: [0]
try literal(&module, main, 1, shape, element[..])
try emit(&module, main, function_value(2, callee))
try array_storage(&module, main, 3, shape, false)
try arraylist.append(&module.functions.items[usize(main)].blocks.items[0].arguments, ir.ValueId(1))
try emit(&module, main, ir.Instruction{ op = .call, type = .array, array_type = shape, destination = ir.ValueId(3), left = ir.ValueId(2), arguments_start = 1, arguments_count = 1 })
try emit(&module, main, unsigned(4, 0))
@@ -999,11 +989,11 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1)
first :: try add_function(&module, &[], 0, .array, 1)
second :: try add_function(&module, &[], 0, .array, 2)
first :: try add_function(&module, &[], 0, .array)
second :: try add_function(&module, &[], 0, .array)
module.functions.items[usize(first)].result_array_type = shape
module.functions.items[usize(second)].result_array_type = shape
try array_storage(&module, first, 0, shape, false)
try emit(&module, first, ir.Instruction{ op = .load_global, type = .array, array_type = shape, destination = ir.ValueId(0), slot = 1 })
return_value(&module, first, ir.ValueId(0))
try emit(&module, second, integer(0, 42))
@@ -1017,11 +1007,11 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t
span = diagnostics.Span{ start = 0, end = 0 },
})
}
main :: try add_function(&module, &[], 0, .array, 2)
main :: try add_function(&module, &[], 0, .array)
module.entry = main
module.functions.items[usize(main)].result_array_type = shape
try emit(&module, main, ir.Instruction{ op = .address_global, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(0), slot = 0 })
try array_storage(&module, main, 1, shape, false)
try emit(&module, main, ir.Instruction{ op = .load_address, type = .array, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0) })
return_value(&module, main, ir.ValueId(1))
succeeded := false
@@ -1049,11 +1039,18 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t
try testing.expect(failures > 0)
# restarting a run clears global readiness, including address-triggered initialization cycles.
cycle_span :: diagnostics.Span{ start = 51, end = 59 }
module.functions.items[usize(second)].has_addresses = true
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = ir.Instruction{
cycle_initializer :: try add_function(&module, &[], 0, .array)
module.functions.items[usize(cycle_initializer)].result_array_type = shape
try emit(&module, cycle_initializer, ir.Instruction{
op = .address_global, type = .address, pointee = .array, array_type = shape,
destination = ir.ValueId(0), slot = 0, span = cycle_span,
}
})
try emit(&module, cycle_initializer, ir.Instruction{
op = .load_address, type = .array, array_type = shape,
destination = ir.ValueId(1), left = ir.ValueId(0),
})
return_value(&module, cycle_initializer, ir.ValueId(1))
module.globals.items[1].initializer = cycle_initializer
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
cycle := try run(allocator, &module, &store)
@@ -1063,7 +1060,7 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t
try testing.expect_equal(usize(0), state.live)
trap_id :: try diagnostics.add(&store, .invalid_escape, cycle_span)
module.functions.items[usize(second)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = integer(0, 42)
module.globals.items[1].initializer = second
trapped := try run(allocator, &module, &store)
defer deinit(&trapped)
try testing.expect_equal(trap_id, trapped.trap?)
@@ -1083,10 +1080,10 @@ array_bounds_check_zero_lengths_and_the_full_unsigned_word_before_addressing tes
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, length)
types [1]ir.Type :: [.array]
main :: try add_function(&module, types[..], 0, .int, 5)
main :: try add_function(&module, &[], 0, .int)
_ = try ir.add_local(&module, main, .array, shape)
module.entry = main
try array_storage(&module, main, 0, shape, true)
try emit(&module, main, integer(0, 42))
operands [1]u32 :: [0]
try literal(&module, main, 1, shape, operands[..length])
@@ -1129,7 +1126,7 @@ zero_leaf_nested_arrays_return_a_shape_without_an_owned_allocation test {
defer diagnostics.deinit(&store)
empty :: try add_array(&module, .bool, ir.no_array, 0)
rows :: try add_array(&module, .array, empty, 3)
main :: try add_function(&module, &[], 0, .array, 4)
main :: try add_function(&module, &[], 0, .array)
module.entry = main
module.functions.items[usize(main)].result_array_type = rows
try literal(&module, main, 0, empty, &[])
@@ -1148,7 +1145,7 @@ zero_leaf_nested_arrays_return_a_shape_without_an_owned_allocation test {
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
try emit(&module, main, unsigned(2, 2))
try array_storage(&module, main, 3, empty, false)
try emit(&module, main, ir.Instruction{ op = .index, type = .array, array_type = empty, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
return_value(&module, main, ir.ValueId(3))
module.functions.items[usize(main)].result_array_type = empty
@@ -1189,7 +1186,7 @@ unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .usize, 3)
main :: try add_function(&module, &[], 0, .usize)
module.entry = main
span :: diagnostics.Span{ start = 7, end = 12 }
try emit(&module, main, unsigned(0, case.left))
@@ -1211,7 +1208,7 @@ unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3)
main :: try add_function(&module, &[], 0, .bool)
module.entry = main
try emit(&module, main, unsigned(0, maxval!(u64)))
try emit(&module, main, unsigned(1, 1))
@@ -1244,11 +1241,11 @@ all_scalar_leaf_families_survive_aggregate_return_and_function_extraction test {
}
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
target :: try add_function(&module, &[], 0, .int, 1)
target :: try add_function(&module, &[], 0, .int)
try emit(&module, target, integer(0, 42))
return_value(&module, target, ir.ValueId(0))
shape :: try add_array(&module, scalar.type, ir.no_array, 1)
main :: try add_function(&module, &[], 0, .array, 5)
main :: try add_function(&module, &[], 0, .array)
module.entry = main
module.functions.items[usize(main)].result_array_type = shape
try emit(&module, main, ir.Instruction{ op = .constant, type = scalar.type, destination = ir.ValueId(0), constant = scalar })
@@ -1288,10 +1285,10 @@ array_address_initialization_flags_survive_reset_and_diagnostic_allocation_failu
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
shape :: try add_array(&module, .int, ir.no_array, 1)
types [1]ir.Type :: [.array]
main :: try add_function(&module, types[..], 0, .int, 5)
main :: try add_function(&module, &[], 0, .int)
_ = try ir.add_local(&module, main, .array, shape)
module.entry = main
try array_storage(&module, main, 0, shape, true)
try emit(&module, main, integer(0, 42))
operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..])
@@ -1301,7 +1298,8 @@ array_address_initialization_flags_survive_reset_and_diagnostic_allocation_failu
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(4), left = ir.ValueId(2), right = ir.ValueId(3) })
try emit(&module, main, ir.Instruction{ op = .reset_local, slot = 0 })
span :: diagnostics.Span{ start = 23, end = 37 }
try emit(&module, main, ir.Instruction{ op = op, type = .int, destination = ir.ValueId(0), left = ir.ValueId(4), right = ir.ValueId(0), span = span })
destination :: if (op == .load_address) ir.ValueId(0) else ir.no_value
try emit(&module, main, ir.Instruction{ op = op, type = .int, destination = destination, left = ir.ValueId(4), right = ir.ValueId(0), span = span })
for (usize(0)..usize(2)) |attempt| {
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
@@ -1332,10 +1330,11 @@ aggregate_loop_places_reuse_storage_without_allocation_per_iteration test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1)
types [2]ir.Type :: [.array, .usize]
main :: try add_function(&module, types[..], 0, .int, 14)
main :: try add_function(&module, &[], 0, .int)
_ = try ir.add_local(&module, main, .array, shape)
_ = try ir.add_local(&module, main, .usize, ir.no_array)
module.entry = main
try array_storage(&module, main, 0, shape, true)
try emit(&module, main, integer(0, 0))
operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..])
@@ -1389,9 +1388,10 @@ recursive_array_backings_and_addresses_remain_fresh_across_stack_growth test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1)
types [2]ir.Type :: [.int, .array]
recursive :: try add_function(&module, types[..], 1, .int, 12)
try array_storage(&module, recursive, 1, shape, true)
recursive :: try add_function(&module, &[], 0, .int)
_ = try ir.add_parameter(&module, recursive, .int, ir.no_array)
_ = try ir.add_local(&module, recursive, .array, shape)
try emit(&module, recursive, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
operand [1]u32 :: [0]
try literal(&module, recursive, 1, shape, operand[..])
@@ -1413,7 +1413,7 @@ recursive_array_backings_and_addresses_remain_fresh_across_stack_growth test {
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(10), left = ir.ValueId(4) })
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(11), left = ir.ValueId(9), right = ir.ValueId(10) })
module.functions.items[usize(recursive)].blocks.items[usize(recurse)].terminator = ir.Terminator{ value = ir.ValueId(11) }
main :: try add_function(&module, &[], 0, .int, 3)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, function_value(0, recursive))
try emit(&module, main, integer(1, 64))
@@ -1432,7 +1432,7 @@ inclusive_unsigned_range_reaches_the_maximum_without_incrementing_past_it test {
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [2]ir.Type :: [.usize, .usize]
main :: try add_function(&module, types[..], 0, .usize, 9)
main :: try add_function(&module, types[..], 0, .usize)
module.entry = main
try emit(&module, main, unsigned(0, maxval!(u64) - 1))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) })
+123 -1
View File
@@ -1,5 +1,6 @@
import "@std/mem"
import "@std/arraylist"
import "@std/debug"
import "@source/diagnostics"
FunctionId :: distinct u32
@@ -41,7 +42,7 @@ Value :: struct {
data ValueData = ValueData{ integer = 0 }
}
#! fixed recursive shape; `cells` counts flattened scalar leaves, including zero-sized children.
#! fixed recursive shape; add_array derives `cells`, the flattened scalar-leaf count.
ArrayType :: struct {
element Type
element_array ArrayId = no_array
@@ -124,6 +125,8 @@ Block :: struct {
terminator Terminator = Terminator{}
}
#! construction derives storage segments, aggregate totals, value counts, and address presence.
#! producers use add_local, add_parameter, and emit; derived metadata is read-only to callers.
Function :: struct {
parameters arraylist.ArrayList(Type)
locals arraylist.ArrayList(Type)
@@ -189,6 +192,125 @@ init_block proc(allocator mem.Allocator) Block {
}
}
#! adds a fixed shape and derives its flattened storage size; child shapes must already exist.
#! scalar elements use no_array; zero-leaf children retain their logical lengths.
add_array proc(
module @mut Module,
element Type,
element_array ArrayId,
len usize,
mutable bool,
) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
debug.assert((element == .array) == (element_array != no_array))
width :: if (element == .array) module.arrays.items[usize(element_array)].cells else 1
if (width != 0 and len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
cells :: len * width
if (cells > divtrunc!(maxval!(usize), sizeof!(Value))) return .storage_limit_reached
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
id :: ArrayId(module.arrays.items.len)
try arraylist.append(&module.arrays, ArrayType{
element = element,
element_array = element_array,
len = len,
cells = cells,
mutable = mutable,
})
return id
}
@hide:file
prepare_storage proc(module @Module, function @mut Function, index u32, shape ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
cells :: module.arrays.items[usize(shape)].cells
if (cells > divtrunc!(maxval!(usize), sizeof!(Value)) - function.aggregate_cells) return .storage_limit_reached
try arraylist.append(&function.array_storage, ArrayStorage{
index = index,
array_type = shape,
offset = function.aggregate_cells,
local = local,
})
function.aggregate_cells += cells
}
#! creates a local and its disjoint by-value backing together; scalar locals use no_array.
add_local proc(
module @mut Module,
function FunctionId,
type Type,
shape ArrayId,
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
debug.assert((type == .array) == (shape != no_array))
body :: &module.functions.items[usize(function)]
if (body.locals.items.len >= usize(maxval!(LocalId))) return .storage_limit_reached
id :: LocalId(body.locals.items.len)
try arraylist.append(&body.locals, type)
errdefer _ = arraylist.pop(&body.locals)
if (type == .array) try prepare_storage(module, body, u32(id), shape, true)
return id
}
#! creates a parameter and its local backing; all parameters precede non-parameter locals.
add_parameter proc(
module @mut Module,
function FunctionId,
type Type,
shape ArrayId,
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
body :: &module.functions.items[usize(function)]
debug.assert(body.parameters.items.len == body.locals.items.len)
try arraylist.append(&body.parameters, type)
errdefer _ = arraylist.pop(&body.parameters)
return try add_local(module, function, type, shape)
}
#! appends an instruction and derives its result count, array backing, and address presence.
#! no_value requests a fresh result; explicit result IDs support non-SSA definitions across blocks.
#! repeated array results must retain their shape and reuse their existing segment.
#! failures leave instruction and storage lengths, totals, counts, and flags unchanged.
emit proc(
module @mut Module,
function FunctionId,
block BlockId,
instruction Instruction,
) ValueId ! (mem.AllocError | diagnostics.CapacityError) {
body :: &module.functions.items[usize(function)]
target :: &body.blocks.items[usize(block)]
operation := instruction
produces_value :: (
operation.op != .store_local
and operation.op != .assign_local
and operation.op != .reset_local
and operation.op != .store_address
)
count := body.value_count
if (produces_value) {
if (operation.destination == no_value) {
if (count >= usize(no_value)) return .storage_limit_reached
operation.destination = ValueId(count)
}
if (usize(operation.destination) >= count) count = usize(operation.destination) + 1
} else {
debug.assert(operation.destination == no_value)
}
try arraylist.append(&target.instructions, operation)
errdefer _ = arraylist.pop(&target.instructions)
if (produces_value and operation.type == .array) {
debug.assert(operation.array_type != no_array)
registered := false
if (usize(operation.destination) < body.value_count) {
for (body.array_storage.items) |storage| {
if (storage.local or storage.index != u32(operation.destination)) continue
debug.assert(storage.array_type == operation.array_type)
registered = true
break
}
}
if (!registered) try prepare_storage(module, body, u32(operation.destination), operation.array_type, false)
}
body.value_count = count
if (operation.type == .address) body.has_addresses = true
return operation.destination
}
#! resolves a module-local ID to bytes borrowed until module deinit.
string_bytes proc(module @Module, id StringId) []u8 {
string :: module.strings.items[usize(id)]
+61
View File
@@ -0,0 +1,61 @@
import "@std/mem"
import "@std/arraylist"
import "@std/testing"
shape_and_segment_limits_preserve_zero_leaf_arrays_and_allow_retry test {
module := init(mem.c_allocator)
defer deinit(&module)
limit :: divtrunc!(maxval!(usize), sizeof!(Value))
rejected := false
_ = add_array(&module, .int, no_array, limit + 1, false) catch |error| {
rejected = error == .storage_limit_reached
yield no_array
}
try testing.expect(rejected)
pair :: try add_array(&module, .int, no_array, 2, false)
rejected = false
_ = add_array(&module, .array, pair, maxval!(usize), false) catch |error| {
rejected = error == .storage_limit_reached
yield no_array
}
try testing.expect(rejected)
full :: try add_array(&module, .int, no_array, limit - 2, false)
empty :: try add_array(&module, .bool, no_array, 0, false)
zero_rows :: try add_array(&module, .array, empty, maxval!(usize), false)
try arraylist.append(&module.functions, init_function(module.allocator))
function :: FunctionId(0)
try arraylist.append(&module.functions.items[0].blocks, init_block(module.allocator))
try testing.expect_equal(LocalId(0), try add_parameter(&module, function, .array, full))
element :: try emit(&module, function, BlockId(0), Instruction{
op = .constant, type = .int, constant = Value{ type = .int, data = ValueData{ integer = 42 } },
})
try arraylist.append(&module.functions.items[0].blocks.items[0].arguments, element)
try arraylist.append(&module.functions.items[0].blocks.items[0].arguments, element)
result :: try emit(&module, function, BlockId(0), Instruction{
op = .array_literal, type = .array, array_type = pair, arguments_count = 2,
})
# redefining an array result reuses backing even when no further cells fit.
_ = try emit(&module, function, BlockId(0), Instruction{
op = .array_literal, type = .array, array_type = pair, arguments_count = 2, destination = result,
})
rejected = false
_ = add_parameter(&module, function, .array, pair) catch |error| {
rejected = error == .storage_limit_reached
yield LocalId(0)
}
try testing.expect(rejected)
# a rejected parameter consumes neither a parameter/local identity nor aggregate storage.
try testing.expect_equal(LocalId(1), try add_parameter(&module, function, .array, zero_rows))
rejected = false
_ = emit(&module, function, BlockId(0), Instruction{
op = .array_literal, type = .array, array_type = pair, arguments_count = 2,
}) catch |error| {
rejected = error == .storage_limit_reached
yield no_value
}
try testing.expect(rejected)
# a rejected result consumes no temporary; zero-leaf shapes still fit a full segment layout.
try testing.expect_equal(ValueId(2), try emit(&module, function, BlockId(0), Instruction{
op = .load_local, type = .array, array_type = zero_rows, slot = 1,
}))
}
+32 -62
View File
@@ -129,41 +129,18 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
descriptor :: typecheck.array_type(state.checked, id)
child := ir.no_array
width := usize(1)
if (descriptor.element.kind == .array) {
child = try prepare_array(state, descriptor.element.array_type)
width = state.module.arrays.items[usize(child)].cells
}
if (width != 0 and descriptor.len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
if (descriptor.len * width > divtrunc!(maxval!(usize), sizeof!(ir.Value))) return .storage_limit_reached
if (state.module.arrays.items.len >= usize(ir.no_array)) return .storage_limit_reached
result :: ir.ArrayId(state.module.arrays.items.len)
try arraylist.append(&state.module.arrays, ir.ArrayType{
element = lower_type(descriptor.element),
element_array = child,
len = descriptor.len,
cells = descriptor.len * width,
mutable = descriptor.mutable,
})
if (descriptor.element.kind == .array) child = try prepare_array(state, descriptor.element.array_type)
result :: try ir.add_array(
state.module,
lower_type(descriptor.element),
child,
descriptor.len,
descriptor.mutable,
)
state.arrays[usize(id)] = result
return result
}
@hide:file
prepare_storage proc(state @mut State, index u32, shape ir.ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
if (shape == ir.no_array) return
function := &state.module.functions.items[state.function]
cells :: state.module.arrays.items[usize(shape)].cells
if (cells > divtrunc!(maxval!(usize), sizeof!(ir.Value)) - function.aggregate_cells) return .storage_limit_reached
try arraylist.append(&function.array_storage, ir.ArrayStorage{
index = index,
array_type = shape,
offset = function.aggregate_cells,
local = local,
})
function.aggregate_cells += cells
}
@hide:file
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
id :: ir.FunctionId(module.functions.items.len)
@@ -239,17 +216,7 @@ finish proc(state @mut State, value ir.ValueId, span diagnostics.Span) void {
@hide:file
emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
function := &state.module.functions.items[state.function]
operation := instruction
if (operation.op != .store_local and operation.op != .assign_local and operation.op != .reset_local and operation.op != .store_address) {
if (function.value_count >= usize(ir.no_value)) return .storage_limit_reached
operation.destination = ir.ValueId(function.value_count)
function.value_count += 1
if (operation.type == .array) try prepare_storage(state, u32(operation.destination), operation.array_type, false)
}
if (operation.type == .address) function.has_addresses = true
try arraylist.append(&function.blocks.items[usize(state.block)].instructions, operation)
return operation.destination
return try ir.emit(state.module, ir.FunctionId(state.function), state.block, instruction)
}
@hide:file
@@ -280,16 +247,18 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
state.function = usize(state.storage[usize(id)].index)
state.block = ir.BlockId(0)
state.terminated = false
function := &state.module.functions.items[state.function]
function.result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
function.result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
state.module.functions.items[state.function].result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i)
type :: lower_type(typecheck.type_of(state.checked, parameter))
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
try arraylist.append(&function.parameters, type)
try arraylist.append(&function.locals, type)
try prepare_storage(state, state.storage[usize(parameter)].index, try array_shape(state, typecheck.type_of(state.checked, parameter)), true)
type :: typecheck.type_of(state.checked, parameter)
local :: try ir.add_parameter(
state.module,
ir.FunctionId(state.function),
lower_type(type),
try array_shape(state, type),
)
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
}
try predeclare_locals(state, declaration.body)
# signature errors are entry traps; body errors are reached in statement order.
@@ -313,10 +282,14 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
statement :: ast.block_statement(state.tree, block, i)
match ast.stmt_kind(state.tree, statement) {
.declaration: {
function := &state.module.functions.items[state.function]
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
try arraylist.append(&function.locals, lower_type(typecheck.type_of(state.checked, statement)))
try prepare_storage(state, state.storage[usize(statement)].index, try array_shape(state, typecheck.type_of(state.checked, statement)), true)
type :: typecheck.type_of(state.checked, statement)
local :: try ir.add_local(
state.module,
ir.FunctionId(state.function),
lower_type(type),
try array_shape(state, type),
)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
}
.if_stmt: {
conditional :: ast.if_stmt(state.tree, statement)
@@ -326,16 +299,14 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
.while_stmt: try predeclare_locals(state, ast.while_stmt(state.tree, statement).body)
.for_stmt: {
loop :: ast.for_stmt(state.tree, statement)
function := &state.module.functions.items[state.function]
slot :: u32(function.locals.items.len)
family :: typecheck.type_of(state.checked, loop.iterable).range_element
type ir.Type :: if (family == .usize) .usize else .int
try arraylist.append(&function.locals, type)
state.storage[usize(statement)] = Storage{ kind = .local, index = slot }
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
if (loop.index) |index| {
state.storage[usize(index)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
try arraylist.append(&function.locals, ir.Type.int)
index_slot :: try ir.add_local(state.module, ir.FunctionId(state.function), .int, ir.no_array)
state.storage[usize(index)] = Storage{ kind = .local, index = u32(index_slot) }
}
try predeclare_locals(state, loop.body)
}
@@ -604,8 +575,7 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
left :: try lower_expression(state, expression.left)
if (state.terminated) return ir.no_value
origin :: state.block
slot :: u32(state.module.functions.items[state.function].locals.items.len)
try arraylist.append(&state.module.functions.items[state.function].locals, ir.Type.bool)
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
right_block :: try add_block(state)
skip :: try add_block(state)
join :: try add_block(state)
+1
View File
@@ -32,6 +32,7 @@ test import "@source/ast"
test import "@source/parser"
test import "@source/sema"
test import "@source/sema/check"
test import "@source/ir"
test import "@source/lower"
test import "@source/interpreter"