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
+2
View File
@@ -91,6 +91,8 @@ type errors carry source spans and diagnostic identities; invalid operands propa
array IR retains recursive element types, counts and mutability. typed aggregate construction, element addresses and load/store instructions use flattened storage reserved per invocation; nested copies and indexed accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers. array IR retains recursive element types, counts and mutability. typed aggregate construction, element addresses and load/store instructions use flattened storage reserved per invocation; nested copies and indexed accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers.
IR construction owns derived layout metadata. `ir.add_array` checks shape capacity and computes flattened leaf counts; `ir.add_parameter` and `ir.add_local` create slots with their disjoint array backing. parameters precede other locals. `ir.emit` assigns fresh result IDs by default and maintains temporary counts, array-result segments and address presence. hand-authored non-SSA IR may supply explicit result IDs; repeated array results retain their shape and reuse the same segment. producers do not register backing separately or write derived counts, offsets or flags. construction failures leave logical contents unchanged, and no finalization pass or extra runtime allocation is required.
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned arrays own their escaped cell storage; release every outcome with `interpreter.deinit` before releasing its module. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. array descriptors also borrow the module. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. `int`/`i64` use signed 64-bit storage, `usize` uses unsigned 64-bit storage, both with checked arithmetic; `float`/`f64` use 64-bit IEEE arithmetic and comparisons, including NaN behavior. `usize` supports `+`, `-`, `*` and comparisons, but not unary negation or division. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes. `interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned arrays own their escaped cell storage; release every outcome with `interpreter.deinit` before releasing its module. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. array descriptors also borrow the module. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. `int`/`i64` use signed 64-bit storage, `usize` uses unsigned 64-bit storage, both with checked arithmetic; `float`/`f64` use 64-bit IEEE arithmetic and comparisons, including NaN behavior. `usize` supports `+`, `-`, `*` and comparisons, but not unary negation or division. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. native code generation and compound assignment remain unimplemented. recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. native code generation and compound assignment remain unimplemented.
+113 -113
View File
@@ -10,10 +10,10 @@ add_function proc(
locals []ir.Type, locals []ir.Type,
parameter_count usize, parameter_count usize,
result ir.Type, result ir.Type,
value_count usize, ) ir.FunctionId ! (mem.AllocError | diagnostics.CapacityError) {
) ir.FunctionId ! mem.AllocError {
function := ir.init_function(module.allocator) function := ir.init_function(module.allocator)
errdefer { transferred := false
errdefer if (!transferred) {
arraylist.deinit(&function.parameters) arraylist.deinit(&function.parameters)
arraylist.deinit(&function.locals) arraylist.deinit(&function.locals)
arraylist.deinit(&function.array_storage) arraylist.deinit(&function.array_storage)
@@ -24,22 +24,24 @@ add_function proc(
} }
arraylist.deinit(&function.blocks) 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.result = result
function.value_count = value_count
try arraylist.append(&function.blocks, ir.init_block(module.allocator)) try arraylist.append(&function.blocks, ir.init_block(module.allocator))
id :: ir.FunctionId(module.functions.items.len) id :: ir.FunctionId(module.functions.items.len)
try arraylist.append(&module.functions, function) 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 return id
} }
@hide:file @hide:file
emit proc(module @mut ir.Module, function ir.FunctionId, instruction ir.Instruction) void ! mem.AllocError { emit proc(module @mut ir.Module, function ir.FunctionId, instruction ir.Instruction) void ! (mem.AllocError | diagnostics.CapacityError) {
if (instruction.type == .address) module.functions.items[usize(function)].has_addresses = true _ = try ir.emit(module, function, ir.BlockId(0), instruction)
try arraylist.append(&module.functions.items[usize(function)].blocks.items[0].instructions, instruction)
} }
@hide:file @hide:file
@@ -91,15 +93,15 @@ repeated_calls_have_fresh_parameters_locals_and_temporaries test {
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [2]ir.Type :: [.int, .int] 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, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
try emit(&module, bump, integer(1, 1)) 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 = .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 = .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 }) try emit(&module, bump, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(3), slot = 1 })
return_value(&module, bump, ir.ValueId(3)) return_value(&module, bump, ir.ValueId(3))
noop :: try add_function(&module, &[], 0, .void, 0) noop :: try add_function(&module, &[], 0, .void)
main :: try add_function(&module, &[], 0, .int, 8) main :: try add_function(&module, &[], 0, .int)
module.entry = main module.entry = main
try emit(&module, main, function_value(0, bump)) try emit(&module, main, function_value(0, bump))
try emit(&module, main, integer(1, 19)) 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
first :: try add_function(&module, &[], 0, .int, 3) first :: try add_function(&module, &[], 0, .int)
second :: try add_function(&module, &[], 0, .int, 1) 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, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 1 })
try emit(&module, first, integer(1, 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) }) 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)) 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 = 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 } }) 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 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(0), slot = 0 })
try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(1), 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) memcopy!(owned, text)
try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len }) try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len })
transferred = true transferred = true
target :: try add_function(&module, &[], 0, .string, 1) target :: try add_function(&module, &[], 0, .string)
try emit(&module, target, ir.Instruction{ try emit(&module, target, ir.Instruction{
op = .constant, op = .constant,
type = .string, 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) } }, constant = ir.Value{ type = .string, data = ir.ValueData{ string = ir.StringId(0) } },
}) })
return_value(&module, target, ir.ValueId(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)) try emit(&module, initializer, function_value(0, target))
return_value(&module, initializer, ir.ValueId(0)) 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 } }) 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 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 = .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) }) 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) defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 14, end = 19 } span :: diagnostics.Span{ start = 14, end = 19 }
types [1]ir.Type :: [.int] 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 module.entry = main
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0, span = span }) 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)) return_value(&module, main, ir.ValueId(0))
@@ -203,8 +205,8 @@ premature_local_reads_and_global_cycles_report_instruction_spans test {
defer deinit(&premature) defer deinit(&premature)
try expect_trap(premature, &store, .read_before_initialization, span) try expect_trap(premature, &store, .read_before_initialization, span)
first :: try add_function(&module, &[], 0, .int, 1) first :: try add_function(&module, &[], 0, .int)
second :: try add_function(&module, &[], 0, .int, 1) second :: try add_function(&module, &[], 0, .int)
cycle_span :: diagnostics.Span{ start = 40, end = 44 } 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, 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 }) 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)) 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 = 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 } }) 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) cycle := try run(mem.c_allocator, &module, &store)
defer deinit(&cycle) defer deinit(&cycle)
try expect_trap(cycle, &store, .initialization_cycle, cycle_span) 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) defer deinit(&absent)
try testing.expect_equal(entry_id, absent.trap?) try testing.expect_equal(entry_id, absent.trap?)
invalid_id :: try diagnostics.add(&store, .unknown_name, diagnostics.Span{ start = 12, end = 17 }) 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 } 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 module.entry = main
try emit(&module, main, integer(0, 42)) try emit(&module, main, integer(0, 42))
return_value(&module, main, ir.ValueId(0)) return_value(&module, main, ir.ValueId(0))
@@ -242,8 +247,11 @@ existing_trap_identity_entry_failures_and_unused_errors_survive test {
defer deinit(&success) defer deinit(&success)
try testing.expect(success.trap == null) try testing.expect(success.trap == null)
try testing.expect_equal(i64(42), success.value.data.integer) 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) trap_main :: try add_function(&module, &[], 0, .int)
try emit(&module, main, ir.Instruction{ op = .call, type = .invalid, destination = ir.ValueId(1), left = ir.ValueId(0) }) 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) trapped := try run(mem.c_allocator, &module, &store)
defer deinit(&trapped) defer deinit(&trapped)
try testing.expect_equal(invalid_id, trapped.trap?) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 8, end = 16 } 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 module.entry = main
try emit(&module, main, integer(0, case.left)) try emit(&module, main, integer(0, case.left))
try emit(&module, main, integer(1, case.right)) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 21, end = 27 } 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 module.entry = main
try emit(&module, main, integer(0, minval!(i64))) 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 }) 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .float, 7) main :: try add_function(&module, &[], 0, .float)
module.entry = main 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(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 } } }) 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .void, 2) main :: try add_function(&module, &[], 0, .void)
module.entry = main module.entry = main
recursive_block :: try add_block(&module, main) recursive_block :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = recursive_block } 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int] 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) init_block :: try add_block(&module, initializer)
module.functions.items[usize(initializer)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = init_block } 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)) 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) } 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 } }) 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 module.entry = main
work :: try add_block(&module, main) work :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = work } 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) module := ir.init(mem.c_allocator)
defer ir.deinit(&module) defer ir.deinit(&module)
types [1]ir.Type :: [.int] 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 module.entry = main
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 }) try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
return_value(&module, main, ir.ValueId(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 @hide:file
emit_at proc(module @mut ir.Module, function ir.FunctionId, block ir.BlockId, instruction ir.Instruction) void ! mem.AllocError { emit_at proc(module @mut ir.Module, function ir.FunctionId, block ir.BlockId, instruction ir.Instruction) void ! (mem.AllocError | diagnostics.CapacityError) {
if (instruction.type == .address) module.functions.items[usize(function)].has_addresses = true _ = try ir.emit(module, function, block, instruction)
try arraylist.append(&module.functions.items[usize(function)].blocks.items[usize(block)].instructions, instruction)
} }
branching_recursive_calls_and_lazy_initializers_resume_the_current_block test { 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int] 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) done :: try add_block(&module, count)
recurse :: 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 }) 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) }) 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) } 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_entry :: try add_block(&module, initializer)
init_exit :: 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 }) 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) } 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 } }) 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_entry :: try add_block(&module, main)
main_exit :: try add_block(&module, main) main_exit :: try add_block(&module, main)
module.entry = main module.entry = main
@@ -611,7 +618,7 @@ all_numeric_comparisons_dispatch_on_operand_type_not_bool_result_type test {
defer ir.deinit(&module) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3) main :: try add_function(&module, &[], 0, .bool)
module.entry = main module.entry = main
left_value := ir.Value{ type = type, data = ir.ValueData{ integer = left } } left_value := ir.Value{ type = type, data = ir.ValueData{ integer = left } }
right_value := ir.Value{ type = type, data = ir.ValueData{ integer = right } } 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3) main :: try add_function(&module, &[], 0, .bool)
module.entry = main module.entry = main
try emit(&module, main, ir.Instruction{ try emit(&module, main, ir.Instruction{
op = .constant, type = .float, destination = ir.ValueId(0), op = .constant, type = .float, destination = ir.ValueId(0),
@@ -680,7 +687,7 @@ boolean_equality_and_branching_use_boolean_payloads test {
defer ir.deinit(&module) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .int, 5) main :: try add_function(&module, &[], 0, .int)
module.entry = main module.entry = main
yes :: try add_block(&module, main) yes :: try add_block(&module, main)
no :: 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int] 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 module.entry = main
try emit(&module, main, integer(0, 1)) try emit(&module, main, integer(0, 1))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) }) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int] 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 module.entry = main
span :: diagnostics.Span{ start = 21, end = 26 } span :: diagnostics.Span{ start = 21, end = 26 }
try emit(&module, main, integer(0, 42)) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int] 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 module.entry = main
span :: diagnostics.Span{ start = 14, end = 19 } span :: diagnostics.Span{ start = 14, end = 19 }
try emit(&module, main, integer(0, 42)) 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) module := ir.init(mem.c_allocator)
defer ir.deinit(&module) defer ir.deinit(&module)
types [1]ir.Type :: [.int] 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 module.entry = main
try emit(&module, main, integer(0, 42)) try emit(&module, main, integer(0, 42))
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(0) }) 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3) main :: try add_function(&module, &[], 0, .bool)
module.entry = main module.entry = main
try emit(&module, main, ir.Instruction{ try emit(&module, main, ir.Instruction{
op = .constant, op = .constant,
@@ -859,27 +866,12 @@ unsigned proc(destination u32, value u64) ir.Instruction {
} }
@hide:file @hide:file
add_array proc(module @mut ir.Module, element ir.Type, child ir.ArrayId, len usize) ir.ArrayId ! mem.AllocError { add_array proc(module @mut ir.Module, element ir.Type, child ir.ArrayId, len usize) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
width :: if (element == .array) module.arrays.items[usize(child)].cells else 1 return try ir.add_array(module, element, child, len, true)
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
} }
@hide:file @hide:file
array_storage proc(module @mut ir.Module, function ir.FunctionId, index u32, shape ir.ArrayId, local bool) void ! mem.AllocError { literal proc(module @mut ir.Module, function ir.FunctionId, destination u32, shape ir.ArrayId, elements []u32) void ! (mem.AllocError | diagnostics.CapacityError) {
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)
block :: &module.functions.items[usize(function)].blocks.items[0] block :: &module.functions.items[usize(function)].blocks.items[0]
start :: block.arguments.items.len start :: block.arguments.items.len
for (elements) |element| try arraylist.append(&block.arguments, ir.ValueId(element)) 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) defer diagnostics.deinit(&store)
row :: try add_array(&module, .int, ir.no_array, 2) row :: try add_array(&module, .int, ir.no_array, 2)
matrix :: try add_array(&module, .array, row, 2) matrix :: try add_array(&module, .array, row, 2)
types [2]ir.Type :: [.array, .array] main :: try add_function(&module, &[], 0, .array)
main :: try add_function(&module, types[..], 0, .array, 15) _ = try ir.add_local(&module, main, .array, matrix)
_ = try ir.add_local(&module, main, .array, matrix)
module.entry = main module.entry = main
module.functions.items[usize(main)].result_array_type = matrix 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(0, 1))
try emit(&module, main, integer(1, 2)) try emit(&module, main, integer(1, 2))
pair [2]u32 :: [0, 1] 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] rows [2]u32 :: [2, 2]
try literal(&module, main, 3, matrix, rows[..]) try literal(&module, main, 3, matrix, rows[..])
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(3) }) 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 = .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, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(4) })
try emit(&module, main, unsigned(5, 0)) try emit(&module, main, unsigned(5, 0))
try emit(&module, main, unsigned(6, 1)) 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 = .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 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 = .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, 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)) 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 = .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 = .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 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 = .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, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(12) })
try emit(&module, main, integer(13, 7)) 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 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 }) 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)) return_value(&module, main, ir.ValueId(14))
outcome := try run(mem.c_allocator, &module, &store) 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) defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1) shape :: try add_array(&module, .int, ir.no_array, 1)
pair_shape :: try add_array(&module, .int, ir.no_array, 2) pair_shape :: try add_array(&module, .int, ir.no_array, 2)
types [2]ir.Type :: [.array, .array] callee :: try add_function(&module, &[], 0, .array)
callee :: try add_function(&module, types[..], 1, .array, 6) _ = 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 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 = .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 = .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 }) 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, integer(4, 7))
try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) }) try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) })
return_value(&module, callee, ir.ValueId(5)) 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.entry = main
module.functions.items[usize(main)].result_array_type = pair_shape module.functions.items[usize(main)].result_array_type = pair_shape
try emit(&module, main, integer(0, 42)) try emit(&module, main, integer(0, 42))
element [1]u32 :: [0] element [1]u32 :: [0]
try literal(&module, main, 1, shape, element[..]) try literal(&module, main, 1, shape, element[..])
try emit(&module, main, function_value(2, callee)) 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 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, 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)) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1) shape :: try add_array(&module, .int, ir.no_array, 1)
first :: try add_function(&module, &[], 0, .array, 1) first :: try add_function(&module, &[], 0, .array)
second :: try add_function(&module, &[], 0, .array, 2) second :: try add_function(&module, &[], 0, .array)
module.functions.items[usize(first)].result_array_type = shape module.functions.items[usize(first)].result_array_type = shape
module.functions.items[usize(second)].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 }) 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)) return_value(&module, first, ir.ValueId(0))
try emit(&module, second, integer(0, 42)) 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 }, 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.entry = main
module.functions.items[usize(main)].result_array_type = shape 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 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) }) 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)) return_value(&module, main, ir.ValueId(1))
succeeded := false succeeded := false
@@ -1049,11 +1039,18 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t
try testing.expect(failures > 0) try testing.expect(failures > 0)
# restarting a run clears global readiness, including address-triggered initialization cycles. # restarting a run clears global readiness, including address-triggered initialization cycles.
cycle_span :: diagnostics.Span{ start = 51, end = 59 } cycle_span :: diagnostics.Span{ start = 51, end = 59 }
module.functions.items[usize(second)].has_addresses = true cycle_initializer :: try add_function(&module, &[], 0, .array)
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = ir.Instruction{ 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, op = .address_global, type = .address, pointee = .array, array_type = shape,
destination = ir.ValueId(0), slot = 0, span = cycle_span, 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 } state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable } allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
cycle := try run(allocator, &module, &store) 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) try testing.expect_equal(usize(0), state.live)
trap_id :: try diagnostics.add(&store, .invalid_escape, cycle_span) 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].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) trapped := try run(allocator, &module, &store)
defer deinit(&trapped) defer deinit(&trapped)
try testing.expect_equal(trap_id, trapped.trap?) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, length) shape :: try add_array(&module, .int, ir.no_array, length)
types [1]ir.Type :: [.array] main :: try add_function(&module, &[], 0, .int)
main :: try add_function(&module, types[..], 0, .int, 5) _ = try ir.add_local(&module, main, .array, shape)
module.entry = main module.entry = main
try array_storage(&module, main, 0, shape, true)
try emit(&module, main, integer(0, 42)) try emit(&module, main, integer(0, 42))
operands [1]u32 :: [0] operands [1]u32 :: [0]
try literal(&module, main, 1, shape, operands[..length]) 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) defer diagnostics.deinit(&store)
empty :: try add_array(&module, .bool, ir.no_array, 0) empty :: try add_array(&module, .bool, ir.no_array, 0)
rows :: try add_array(&module, .array, empty, 3) 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.entry = main
module.functions.items[usize(main)].result_array_type = rows module.functions.items[usize(main)].result_array_type = rows
try literal(&module, main, 0, empty, &[]) try literal(&module, main, 0, empty, &[])
@@ -1148,7 +1145,7 @@ zero_leaf_nested_arrays_return_a_shape_without_an_owned_allocation test {
deinit(&outcome) deinit(&outcome)
try testing.expect_equal(usize(0), state.live) try testing.expect_equal(usize(0), state.live)
try emit(&module, main, unsigned(2, 2)) 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) }) 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)) return_value(&module, main, ir.ValueId(3))
module.functions.items[usize(main)].result_array_type = empty 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .usize, 3) main :: try add_function(&module, &[], 0, .usize)
module.entry = main module.entry = main
span :: diagnostics.Span{ start = 7, end = 12 } span :: diagnostics.Span{ start = 7, end = 12 }
try emit(&module, main, unsigned(0, case.left)) 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) defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool, 3) main :: try add_function(&module, &[], 0, .bool)
module.entry = main module.entry = main
try emit(&module, main, unsigned(0, maxval!(u64))) try emit(&module, main, unsigned(0, maxval!(u64)))
try emit(&module, main, unsigned(1, 1)) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) 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)) try emit(&module, target, integer(0, 42))
return_value(&module, target, ir.ValueId(0)) return_value(&module, target, ir.ValueId(0))
shape :: try add_array(&module, scalar.type, ir.no_array, 1) 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.entry = main
module.functions.items[usize(main)].result_array_type = shape 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 }) 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) module := ir.init(mem.c_allocator)
defer ir.deinit(&module) defer ir.deinit(&module)
shape :: try add_array(&module, .int, ir.no_array, 1) shape :: try add_array(&module, .int, ir.no_array, 1)
types [1]ir.Type :: [.array] main :: try add_function(&module, &[], 0, .int)
main :: try add_function(&module, types[..], 0, .int, 5) _ = try ir.add_local(&module, main, .array, shape)
module.entry = main module.entry = main
try array_storage(&module, main, 0, shape, true)
try emit(&module, main, integer(0, 42)) try emit(&module, main, integer(0, 42))
operand [1]u32 :: [0] operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..]) 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 = .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 }) try emit(&module, main, ir.Instruction{ op = .reset_local, slot = 0 })
span :: diagnostics.Span{ start = 23, end = 37 } 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| { for (usize(0)..usize(2)) |attempt| {
state := AllocationState{ remaining = 64 } state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable } 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1) shape :: try add_array(&module, .int, ir.no_array, 1)
types [2]ir.Type :: [.array, .usize] main :: try add_function(&module, &[], 0, .int)
main :: try add_function(&module, types[..], 0, .int, 14) _ = try ir.add_local(&module, main, .array, shape)
_ = try ir.add_local(&module, main, .usize, ir.no_array)
module.entry = main module.entry = main
try array_storage(&module, main, 0, shape, true)
try emit(&module, main, integer(0, 0)) try emit(&module, main, integer(0, 0))
operand [1]u32 :: [0] operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..]) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1) shape :: try add_array(&module, .int, ir.no_array, 1)
types [2]ir.Type :: [.int, .array] recursive :: try add_function(&module, &[], 0, .int)
recursive :: try add_function(&module, types[..], 1, .int, 12) _ = try ir.add_parameter(&module, recursive, .int, ir.no_array)
try array_storage(&module, recursive, 1, shape, true) _ = 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 }) try emit(&module, recursive, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
operand [1]u32 :: [0] operand [1]u32 :: [0]
try literal(&module, recursive, 1, shape, operand[..]) 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 = .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) }) 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) } 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 module.entry = main
try emit(&module, main, function_value(0, recursive)) try emit(&module, main, function_value(0, recursive))
try emit(&module, main, integer(1, 64)) 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) store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store) defer diagnostics.deinit(&store)
types [2]ir.Type :: [.usize, .usize] 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 module.entry = main
try emit(&module, main, unsigned(0, maxval!(u64) - 1)) try emit(&module, main, unsigned(0, maxval!(u64) - 1))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) }) 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/mem"
import "@std/arraylist" import "@std/arraylist"
import "@std/debug"
import "@source/diagnostics" import "@source/diagnostics"
FunctionId :: distinct u32 FunctionId :: distinct u32
@@ -41,7 +42,7 @@ Value :: struct {
data ValueData = ValueData{ integer = 0 } 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 { ArrayType :: struct {
element Type element Type
element_array ArrayId = no_array element_array ArrayId = no_array
@@ -124,6 +125,8 @@ Block :: struct {
terminator Terminator = Terminator{} 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 { Function :: struct {
parameters arraylist.ArrayList(Type) parameters arraylist.ArrayList(Type)
locals 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. #! resolves a module-local ID to bytes borrowed until module deinit.
string_bytes proc(module @Module, id StringId) []u8 { string_bytes proc(module @Module, id StringId) []u8 {
string :: module.strings.items[usize(id)] 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)] if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
descriptor :: typecheck.array_type(state.checked, id) descriptor :: typecheck.array_type(state.checked, id)
child := ir.no_array child := ir.no_array
width := usize(1) if (descriptor.element.kind == .array) child = try prepare_array(state, descriptor.element.array_type)
if (descriptor.element.kind == .array) { result :: try ir.add_array(
child = try prepare_array(state, descriptor.element.array_type) state.module,
width = state.module.arrays.items[usize(child)].cells lower_type(descriptor.element),
} child,
if (width != 0 and descriptor.len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached descriptor.len,
if (descriptor.len * width > divtrunc!(maxval!(usize), sizeof!(ir.Value))) return .storage_limit_reached descriptor.mutable,
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,
})
state.arrays[usize(id)] = result state.arrays[usize(id)] = result
return 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 @hide:file
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError { add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
id :: ir.FunctionId(module.functions.items.len) 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 @hide:file
emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
function := &state.module.functions.items[state.function] return try ir.emit(state.module, ir.FunctionId(state.function), state.block, instruction)
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
} }
@hide:file @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.function = usize(state.storage[usize(id)].index)
state.block = ir.BlockId(0) state.block = ir.BlockId(0)
state.terminated = false state.terminated = false
function := &state.module.functions.items[state.function] state.module.functions.items[state.function].result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
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))
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| { for (0..ast.function_param_count(tree, id)) |i| {
parameter :: ast.function_param(tree, id, i) parameter :: ast.function_param(tree, id, i)
type :: lower_type(typecheck.type_of(state.checked, parameter)) type :: typecheck.type_of(state.checked, parameter)
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(function.locals.items.len) } local :: try ir.add_parameter(
try arraylist.append(&function.parameters, type) state.module,
try arraylist.append(&function.locals, type) ir.FunctionId(state.function),
try prepare_storage(state, state.storage[usize(parameter)].index, try array_shape(state, typecheck.type_of(state.checked, parameter)), true) lower_type(type),
try array_shape(state, type),
)
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
} }
try predeclare_locals(state, declaration.body) try predeclare_locals(state, declaration.body)
# signature errors are entry traps; body errors are reached in statement order. # 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) statement :: ast.block_statement(state.tree, block, i)
match ast.stmt_kind(state.tree, statement) { match ast.stmt_kind(state.tree, statement) {
.declaration: { .declaration: {
function := &state.module.functions.items[state.function] type :: typecheck.type_of(state.checked, statement)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(function.locals.items.len) } local :: try ir.add_local(
try arraylist.append(&function.locals, lower_type(typecheck.type_of(state.checked, statement))) state.module,
try prepare_storage(state, state.storage[usize(statement)].index, try array_shape(state, typecheck.type_of(state.checked, statement)), true) ir.FunctionId(state.function),
lower_type(type),
try array_shape(state, type),
)
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
} }
.if_stmt: { .if_stmt: {
conditional :: ast.if_stmt(state.tree, statement) 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) .while_stmt: try predeclare_locals(state, ast.while_stmt(state.tree, statement).body)
.for_stmt: { .for_stmt: {
loop :: ast.for_stmt(state.tree, statement) 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 family :: typecheck.type_of(state.checked, loop.iterable).range_element
type ir.Type :: if (family == .usize) .usize else .int type ir.Type :: if (family == .usize) .usize else .int
try arraylist.append(&function.locals, type) slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
state.storage[usize(statement)] = Storage{ kind = .local, index = slot } state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)] if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
if (loop.index) |index| { if (loop.index) |index| {
state.storage[usize(index)] = Storage{ kind = .local, index = u32(function.locals.items.len) } index_slot :: try ir.add_local(state.module, ir.FunctionId(state.function), .int, ir.no_array)
try arraylist.append(&function.locals, ir.Type.int) state.storage[usize(index)] = Storage{ kind = .local, index = u32(index_slot) }
} }
try predeclare_locals(state, loop.body) 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) left :: try lower_expression(state, expression.left)
if (state.terminated) return ir.no_value if (state.terminated) return ir.no_value
origin :: state.block origin :: state.block
slot :: u32(state.module.functions.items[state.function].locals.items.len) slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
try arraylist.append(&state.module.functions.items[state.function].locals, ir.Type.bool)
right_block :: try add_block(state) right_block :: try add_block(state)
skip :: try add_block(state) skip :: try add_block(state)
join :: 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/parser"
test import "@source/sema" test import "@source/sema"
test import "@source/sema/check" test import "@source/sema/check"
test import "@source/ir"
test import "@source/lower" test import "@source/lower"
test import "@source/interpreter" test import "@source/interpreter"