refactor: centralize IR construction invariants
This commit is contained in:
@@ -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
@@ -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)]
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user