Files

1681 lines
93 KiB
Plaintext

import "@std/mem"
import "@std/arraylist"
import "@std/testing"
import "@source/ir"
import "@source/diagnostics"
@hide:file
add_function proc(
module @mut ir.Module,
locals []ir.Type,
parameter_count usize,
result ir.Type,
) ir.FunctionId ! (mem.AllocError | diagnostics.CapacityError) {
function := ir.init_function(module.allocator)
transferred := false
errdefer if (!transferred) {
arraylist.deinit(&function.parameters)
arraylist.deinit(&function.locals)
arraylist.deinit(&function.array_storage)
for (function.blocks.items) |block| {
owned := block
arraylist.deinit(&owned.instructions)
arraylist.deinit(&owned.arguments)
}
arraylist.deinit(&function.blocks)
}
function.result = result
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 | diagnostics.CapacityError) {
_ = try ir.emit(module, function, ir.BlockId(0), instruction)
}
@hide:file
return_value proc(module @mut ir.Module, function ir.FunctionId, value ir.ValueId) void {
module.functions.items[usize(function)].blocks.items[0].terminator = ir.Terminator{ value = value }
}
@hide:file
integer proc(destination u32, value i64) ir.Instruction {
return ir.Instruction{
op = .constant,
type = .int,
destination = ir.ValueId(destination),
constant = ir.Value{ type = .int, data = ir.ValueData{ integer = value } },
}
}
@hide:file
function_value proc(destination u32, function ir.FunctionId) ir.Instruction {
return ir.Instruction{
op = .constant,
type = .function,
destination = ir.ValueId(destination),
constant = ir.Value{ type = .function, data = ir.ValueData{ function = function } },
}
}
@hide:file
expect_trap proc(
outcome Outcome,
store @diagnostics.Store,
code diagnostics.Code,
span diagnostics.Span,
) void ! testing.Error {
if outcome.trap |id| {
if diagnostics.get(store, id) |diagnostic| {
try testing.expect_equal(code, diagnostic.code)
try testing.expect_equal(span.start, diagnostic.span.start)
try testing.expect_equal(span.end, diagnostic.span.end)
return
}
}
return .expectation_failed
}
repeated_calls_have_fresh_parameters_locals_and_temporaries test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [2]ir.Type :: [.int, .int]
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)
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))
try arraylist.append(&module.functions.items[usize(main)].blocks.items[0].arguments, ir.ValueId(1))
try emit(&module, main, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), arguments_count = 1 })
try emit(&module, main, integer(3, 21))
try arraylist.append(&module.functions.items[usize(main)].blocks.items[0].arguments, ir.ValueId(3))
try emit(&module, main, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(4), left = ir.ValueId(0), arguments_start = 1, arguments_count = 1 })
try emit(&module, main, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(5), left = ir.ValueId(2), right = ir.ValueId(4) })
try emit(&module, main, function_value(6, noop))
try emit(&module, main, ir.Instruction{ op = .call, type = .void, destination = ir.ValueId(7), left = ir.ValueId(6) })
return_value(&module, main, ir.ValueId(5))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(ir.Type.int, outcome.value.type)
try testing.expect_equal(i64(42), outcome.value.data.integer)
try testing.expect_equal(usize(0), diagnostics.count(&store))
}
lazy_globals_follow_forward_dependencies_and_reset_between_runs test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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) })
return_value(&module, first, ir.ValueId(2))
try emit(&module, second, integer(0, 20))
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)
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 })
try emit(&module, main, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
return_value(&module, main, ir.ValueId(2))
initial := try run(mem.c_allocator, &module, &store)
defer deinit(&initial)
try testing.expect(initial.trap == null)
try testing.expect_equal(i64(42), initial.value.data.integer)
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = integer(0, 30)
repeated := try run(mem.c_allocator, &module, &store)
defer deinit(&repeated)
try testing.expect(repeated.trap == null)
try testing.expect_equal(i64(62), repeated.value.data.integer)
}
function_aliases_and_borrowed_string_values_execute test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
text :: "line\n\"quoted\"\x00"
owned :: try mem.alloc(u8, module.allocator, text.len)
transferred := false
defer if (!transferred) mem.free(module.allocator, owned)
memcopy!(owned, text)
try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len })
transferred = true
target :: try add_function(&module, &[], 0, .string)
try emit(&module, target, ir.Instruction{
op = .constant,
type = .string,
destination = ir.ValueId(0),
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)
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)
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) })
return_value(&module, main, ir.ValueId(1))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(ir.Type.string, outcome.value.type)
try testing.expect_equal([]u8, text, ir.string_bytes(&module, outcome.value.data.string))
}
premature_local_reads_and_global_cycles_report_instruction_spans test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 14, end = 19 }
types [1]ir.Type :: [.int]
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))
premature := try run(mem.c_allocator, &module, &store)
defer deinit(&premature)
try expect_trap(premature, &store, .read_before_initialization, span)
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 })
return_value(&module, first, 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 = second, span = diagnostics.Span{ start = 0, end = 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)
retry := try run(mem.c_allocator, &module, &store)
defer deinit(&retry)
try expect_trap(retry, &store, .initialization_cycle, cycle_span)
}
existing_trap_identity_entry_failures_and_unused_errors_survive test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
entry_id :: try diagnostics.add(&store, .missing_entry, diagnostics.Span{ start = 0, end = 0 })
module.entry_diagnostic = entry_id
absent := try run(mem.c_allocator, &module, &store)
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)
module.functions.items[usize(bad)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = invalid_id }
main :: try add_function(&module, &[], 0, .int)
module.entry = main
try emit(&module, main, integer(0, 42))
return_value(&module, main, ir.ValueId(0))
success := try run(mem.c_allocator, &module, &store)
defer deinit(&success)
try testing.expect(success.trap == null)
try testing.expect_equal(i64(42), success.value.data.integer)
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?)
try testing.expect_equal(usize(2), diagnostics.count(&store))
}
@hide:file
IntegerCase :: struct {
op ir.Op
left i64
right i64
expected ?i64
}
integer_arithmetic_checks_boundaries_before_host_operations test {
cases [19]IntegerCase :: [
IntegerCase{ op = .add, left = 19, right = 23, expected = 42 },
IntegerCase{ op = .add, left = maxval!(i64), right = 0, expected = maxval!(i64) },
IntegerCase{ op = .add, left = maxval!(i64), right = 1, expected = null },
IntegerCase{ op = .add, left = minval!(i64), right = -1, expected = null },
IntegerCase{ op = .add, left = minval!(i64), right = maxval!(i64), expected = -1 },
IntegerCase{ op = .subtract, left = minval!(i64), right = 0, expected = minval!(i64) },
IntegerCase{ op = .subtract, left = minval!(i64), right = 1, expected = null },
IntegerCase{ op = .subtract, left = maxval!(i64), right = -1, expected = null },
IntegerCase{ op = .subtract, left = -1, right = minval!(i64), expected = maxval!(i64) },
IntegerCase{ op = .multiply, left = minval!(i64), right = 1, expected = minval!(i64) },
IntegerCase{ op = .multiply, left = minval!(i64), right = -1, expected = null },
IntegerCase{ op = .multiply, left = -1, right = minval!(i64), expected = null },
IntegerCase{ op = .multiply, left = maxval!(i64), right = 2, expected = null },
IntegerCase{ op = .multiply, left = minval!(i64), right = 2, expected = null },
IntegerCase{ op = .multiply, left = -3, right = -14, expected = 42 },
IntegerCase{ op = .multiply, left = -3, right = 14, expected = -42 },
IntegerCase{ op = .multiply, left = 3, right = -14, expected = -42 },
IntegerCase{ op = .multiply, left = minval!(i64), right = 0, expected = 0 },
IntegerCase{ op = .multiply, left = 0, right = minval!(i64), expected = 0 },
]
for (cases) |case| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 8, end = 16 }
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))
try emit(&module, main, ir.Instruction{ op = case.op, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
if case.expected |expected| {
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected, outcome.value.data.integer)
} else {
try expect_trap(outcome, &store, .integer_overflow, span)
}
}
}
integer_remainder_preserves_dividend_sign_and_minimum_without_overflow test {
cases [6]IntegerCase :: [
IntegerCase{ op = .remainder, left = -5, right = 3, expected = -2 },
IntegerCase{ op = .remainder, left = 5, right = -3, expected = 2 },
IntegerCase{ op = .remainder, left = minval!(i64), right = -1, expected = 0 },
IntegerCase{ op = .remainder, left = minval!(i64), right = 10, expected = -8 },
IntegerCase{ op = .remainder, left = 0, right = -1, expected = 0 },
IntegerCase{ op = .remainder, left = 1, right = 0, expected = null },
]
for (cases) |case| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 8, end = 16 }
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))
try emit(&module, main, ir.Instruction{ op = .remainder, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
if case.expected |expected| {
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected, outcome.value.data.integer)
} else try expect_trap(outcome, &store, .division_by_zero, span)
}
}
integer_negation_traps_only_at_minimum test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
span :: diagnostics.Span{ start = 21, end = 27 }
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 })
return_value(&module, main, ir.ValueId(1))
trapped := try run(mem.c_allocator, &module, &store)
defer deinit(&trapped)
try expect_trap(trapped, &store, .integer_overflow, span)
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = integer(0, maxval!(i64))
success := try run(mem.c_allocator, &module, &store)
defer deinit(&success)
try testing.expect(success.trap == null)
try testing.expect_equal(-maxval!(i64), success.value.data.integer)
}
float_arithmetic_and_ieee_zero_division_execute test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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 } } })
try emit(&module, main, ir.Instruction{ op = .multiply, type = .float, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
try emit(&module, main, ir.Instruction{ op = .add, type = .float, destination = ir.ValueId(3), left = ir.ValueId(2), right = ir.ValueId(1) })
try emit(&module, main, ir.Instruction{ op = .subtract, type = .float, destination = ir.ValueId(4), left = ir.ValueId(3), right = ir.ValueId(1) })
try emit(&module, main, ir.Instruction{ op = .divide, type = .float, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(1) })
try emit(&module, main, ir.Instruction{ op = .negate, type = .float, destination = ir.ValueId(6), left = ir.ValueId(5) })
return_value(&module, main, ir.ValueId(6))
result := try run(mem.c_allocator, &module, &store)
defer deinit(&result)
try testing.expect(result.trap == null)
try testing.expect_equal(ir.Type.float, result.value.type)
try testing.expect_equal(f64(-10.5), result.value.data.real)
module.functions.items[usize(main)].blocks.items[0].instructions.items[0].constant.data.real = 0
module.functions.items[usize(main)].blocks.items[0].instructions.items[1].constant.data.real = 0
nan := try run(mem.c_allocator, &module, &store)
defer deinit(&nan)
try testing.expect(nan.trap == null)
try testing.expect(nan.value.data.real != nan.value.data.real)
module.functions.items[usize(main)].blocks.items[0].instructions.items[0].constant.data.real = 1
module.functions.items[usize(main)].blocks.items[0].instructions.items[2].op = .add
infinite := try run(mem.c_allocator, &module, &store)
defer deinit(&infinite)
try testing.expect(infinite.trap == null)
try testing.expect(infinite.value.data.real < f64(-1000000000))
try testing.expect_equal(infinite.value.data.real, infinite.value.data.real * f64(2))
try testing.expect_equal(usize(0), diagnostics.count(&store))
}
@hide:file
AllocationState :: struct {
remaining usize
live usize = 0
}
@hide:file
limited_alloc proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8 {
if context |userdata| {
state @mut AllocationState :: ptrcast!(userdata)
if (state.remaining == 0) return null
state.remaining -= 1
memory :: mem.raw_alloc(mem.c_allocator, size, alignment)
if (memory) |_| state.live += 1
return memory
}
unreachable
}
@hide:file
limited_realloc proc(context ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size usize, alignment usize) ?*mut u8 {
if context |userdata| {
state @mut AllocationState :: ptrcast!(userdata)
if (state.remaining == 0) return null
state.remaining -= 1
resized :: mem.raw_realloc(mem.c_allocator, memory, old_size, new_size, alignment)
if resized |_| {
if (memory) |_| {} else state.live += 1
}
return resized
}
unreachable
}
@hide:file
limited_free proc(context ?@mut anyopaque, memory ?*mut u8, size usize, alignment usize) void {
if context |userdata| {
state @mut AllocationState :: ptrcast!(userdata)
if (memory) |_| state.live -= 1
mem.raw_free(mem.c_allocator, memory, size, alignment)
return
}
unreachable
}
@hide:file
limited_vtable :: mem.AllocatorVTable{ alloc = limited_alloc, realloc = limited_realloc, free = limited_free }
recursive_invocations_use_heap_frames_and_free_on_allocation_failure test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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 }
try emit_at(&module, main, recursive_block, function_value(0, main))
try emit_at(&module, main, recursive_block, ir.Instruction{ op = .call, type = .void, destination = ir.ValueId(1), left = ir.ValueId(0) })
state := AllocationState{ remaining = 2048 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
unexpected := run(allocator, &module, &store) catch |error| {
match error {
.out_of_memory: {}
else: return .expectation_failed
}
try testing.expect_equal(usize(0), state.live)
try testing.expect_equal(usize(0), diagnostics.count(&store))
return
}
deinit(&unexpected)
return .expectation_failed
}
frames_and_globals_are_freed_at_every_allocation_boundary_and_trap test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
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)
module.entry = main
work :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = work }
try emit_at(&module, main, work, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 })
module.functions.items[usize(main)].blocks.items[usize(work)].terminator = ir.Terminator{ value = ir.ValueId(0) }
succeeded := false
failures usize := 0
for (usize(0)..usize(16)) |limit| {
state := AllocationState{ remaining = limit }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := run(allocator, &module, &store) catch |error| {
match error {
.out_of_memory: {}
else: return .expectation_failed
}
failures += 1
try testing.expect_equal(usize(0), state.live)
continue
}
defer deinit(&outcome)
succeeded = true
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.integer)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
}
try testing.expect(succeeded)
try testing.expect(failures > 0)
trap_id :: try diagnostics.add(&store, .invalid_escape, diagnostics.Span{ start = 0, end = 0 })
module.functions.items[usize(initializer)].blocks.items[usize(init_block)].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
trapped := try run(allocator, &module, &store)
defer deinit(&trapped)
try testing.expect_equal(trap_id, trapped.trap?)
deinit(&trapped)
try testing.expect_equal(usize(0), state.live)
}
evaluation_backing_survives_returns_and_releases_at_every_allocation_boundary test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.u8]
helper :: try add_function(&module, types[..], 0, .pointer)
try emit(&module, helper, ir.Instruction{
op = .constant, type = .u8, destination = ir.ValueId(0),
constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 42 } },
})
try emit(&module, helper, ir.Instruction{ op = .store_local, left = ir.ValueId(0) })
try emit(&module, helper, ir.Instruction{
op = .address_local, type = .address, pointee = .u8, destination = ir.ValueId(1),
})
try emit(&module, helper, ir.Instruction{
op = .pointer_from_address, type = .pointer, left = ir.ValueId(1), destination = ir.ValueId(2),
})
return_value(&module, helper, ir.ValueId(2))
main :: try add_function(&module, &[], 0, .pointer)
module.entry = main
try emit(&module, main, function_value(0, helper))
try emit(&module, main, ir.Instruction{
op = .call, type = .pointer, left = ir.ValueId(0), destination = ir.ValueId(1),
})
return_value(&module, main, ir.ValueId(1))
trap_id :: try diagnostics.add(&store, .invalid_escape, diagnostics.Span{ start = 0, end = 0 })
for [false, true] |trap| {
if (trap) module.functions.items[usize(main)].blocks.items[0].terminator =
ir.Terminator{ kind = .trap, diagnostic = trap_id }
succeeded := false
for (usize(0)..usize(64)) |limit| {
state := AllocationState{ remaining = limit }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := run_evaluation(allocator, &module, &store) catch |error| {
match error {
.out_of_memory: {}
else: return .expectation_failed
}
try testing.expect_equal(usize(0), state.live)
continue
}
defer deinit(&outcome)
if (trap) {
try testing.expect_equal(trap_id, outcome.trap?)
} else {
try testing.expect(outcome.trap == null)
byte :: evaluation_byte(&outcome, &module, outcome.value, 0)
try testing.expect(byte != null)
try testing.expect_equal(u64(42), byte?.data.unsigned)
try testing.expect(evaluation_byte(&outcome, &module, outcome.value, 1) == null)
}
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
succeeded = true
break
}
try testing.expect(succeeded)
}
}
runtime_diagnostic_allocation_failure_is_infrastructure_error_and_frees_frames test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
types [1]ir.Type :: [.int]
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))
diagnostic_state := AllocationState{ remaining = 0 }
diagnostic_allocator :: mem.Allocator{ context = &diagnostic_state, vtable = &limited_vtable }
store := diagnostics.init(diagnostic_allocator)
defer diagnostics.deinit(&store)
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
unexpected := run(allocator, &module, &store) catch |error| {
match error {
.out_of_memory: {}
else: return .expectation_failed
}
try testing.expect_equal(usize(0), state.live)
try testing.expect_equal(usize(0), diagnostic_state.live)
try testing.expect_equal(usize(0), diagnostics.count(&store))
return
}
deinit(&unexpected)
return .expectation_failed
}
@hide:file
add_block proc(module @mut ir.Module, function ir.FunctionId) ir.BlockId ! mem.AllocError {
body := &module.functions.items[usize(function)]
id :: ir.BlockId(body.blocks.items.len)
try arraylist.append(&body.blocks, ir.init_block(module.allocator))
return id
}
@hide:file
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 {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
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 })
try emit(&module, count, integer(1, 0))
try emit(&module, count, ir.Instruction{ op = .less_equal, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
module.functions.items[usize(count)].blocks.items[0].terminator = ir.Terminator{
kind = .branch, value = ir.ValueId(2), target = done, alternative = recurse,
}
module.functions.items[usize(count)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(1) }
try emit_at(&module, count, recurse, function_value(3, count))
try emit_at(&module, count, recurse, integer(4, 1))
try emit_at(&module, count, recurse, ir.Instruction{ op = .subtract, type = .int, destination = ir.ValueId(5), left = ir.ValueId(0), right = ir.ValueId(4) })
try arraylist.append(&module.functions.items[usize(count)].blocks.items[usize(recurse)].arguments, ir.ValueId(5))
try emit_at(&module, count, recurse, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(6), left = ir.ValueId(3), arguments_count = 1 })
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)
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 })
module.functions.items[usize(initializer)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
module.functions.items[usize(initializer)].entry = init_entry
try emit_at(&module, initializer, init_entry, function_value(0, count))
try emit_at(&module, initializer, init_entry, integer(1, 128))
try arraylist.append(&module.functions.items[usize(initializer)].blocks.items[usize(init_entry)].arguments, ir.ValueId(1))
try emit_at(&module, initializer, init_entry, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), arguments_count = 1 })
module.functions.items[usize(initializer)].blocks.items[usize(init_entry)].terminator = ir.Terminator{ kind = .jump, target = init_exit }
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)
main_entry :: try add_block(&module, main)
main_exit :: try add_block(&module, main)
module.entry = main
module.functions.items[usize(main)].entry = main_entry
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
try emit_at(&module, main, main_entry, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 })
try emit_at(&module, main, main_entry, function_value(1, count))
try emit_at(&module, main, main_entry, integer(2, 2))
try arraylist.append(&module.functions.items[usize(main)].blocks.items[usize(main_entry)].arguments, ir.ValueId(2))
try emit_at(&module, main, main_entry, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(3), left = ir.ValueId(1), arguments_count = 1 })
module.functions.items[usize(main)].blocks.items[usize(main_entry)].terminator = ir.Terminator{ kind = .jump, target = main_exit }
try emit_at(&module, main, main_exit, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(4), left = ir.ValueId(0), right = ir.ValueId(3) })
module.functions.items[usize(main)].blocks.items[usize(main_exit)].terminator = ir.Terminator{ value = ir.ValueId(4) }
state := AllocationState{ remaining = 2048 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := try run(allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(130), outcome.value.data.integer)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
try testing.expect_equal(usize(1), diagnostics.count(&store))
# trapping after returning through many heap frames also releases every live frame.
module.functions.items[usize(main)].blocks.items[usize(main_exit)].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
trapped := try run(allocator, &module, &store)
defer deinit(&trapped)
try testing.expect_equal(trap_id, trapped.trap?)
deinit(&trapped)
try testing.expect_equal(usize(0), state.live)
}
all_numeric_comparisons_dispatch_on_operand_type_not_bool_result_type test {
operations [6]ir.Op :: [.equal, .not_equal, .less, .less_equal, .greater, .greater_equal]
lefts [3]i64 :: [-2, 2, 3]
rights [3]i64 :: [3, 2, -2]
operand_types [2]ir.Type :: [.int, .float]
for (operand_types) |type| {
for (lefts) |left, pair| {
right :: rights[pair]
for (operations) |op| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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 } }
if (type == .float) {
left_value.data = ir.ValueData{ real = f64(left) }
right_value.data = ir.ValueData{ real = f64(right) }
}
try emit(&module, main, ir.Instruction{ op = .constant, type = type, destination = ir.ValueId(0), constant = left_value })
try emit(&module, main, ir.Instruction{ op = .constant, type = type, destination = ir.ValueId(1), constant = right_value })
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
return_value(&module, main, ir.ValueId(2))
expected :: match op {
.equal: left == right
.not_equal: left != right
.less: left < right
.less_equal: left <= right
.greater: left > right
.greater_equal: left >= right
else: unreachable
}
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(ir.Type.bool, outcome.value.type)
try testing.expect_equal(expected, outcome.value.data.boolean)
}
}
}
}
nan_comparisons_obey_ieee_rules_in_either_operand_position test {
operations [6]ir.Op :: [.equal, .not_equal, .less, .less_equal, .greater, .greater_equal]
for (operations) |op| {
for (usize(0)..usize(3)) |position| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool)
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 = 0 } },
})
try emit(&module, main, ir.Instruction{ op = .divide, type = .float, destination = ir.ValueId(1), left = ir.ValueId(0), right = ir.ValueId(0) })
left ir.ValueId :: if (position == 1) ir.ValueId(0) else ir.ValueId(1)
right ir.ValueId :: if (position == 0) ir.ValueId(0) else ir.ValueId(1)
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = left, right = right })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(ir.Type.bool, outcome.value.type)
try testing.expect_equal(op == .not_equal, outcome.value.data.boolean)
}
}
}
boolean_equality_and_branching_use_boolean_payloads test {
booleans [2]bool :: [false, true]
operations [2]ir.Op :: [.equal, .not_equal]
for (booleans) |left| {
for (booleans) |right| {
for (operations) |op| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .int)
module.entry = main
yes :: try add_block(&module, main)
no :: try add_block(&module, main)
try emit(&module, main, ir.Instruction{ op = .constant, type = .bool, destination = ir.ValueId(0), constant = ir.Value{ type = .bool, data = ir.ValueData{ boolean = left } } })
try emit(&module, main, ir.Instruction{ op = .constant, type = .bool, destination = ir.ValueId(1), constant = ir.Value{ type = .bool, data = ir.ValueData{ boolean = right } } })
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(2), target = yes, alternative = no }
try emit_at(&module, main, yes, integer(3, 42))
module.functions.items[usize(main)].blocks.items[usize(yes)].terminator = ir.Terminator{ value = ir.ValueId(3) }
try emit_at(&module, main, no, integer(4, 7))
module.functions.items[usize(main)].blocks.items[usize(no)].terminator = ir.Terminator{ value = ir.ValueId(4) }
equal :: left == right
expected_true :: if (op == .equal) equal else !equal
expected i64 :: if (expected_true) 42 else 7
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected, outcome.value.data.integer)
}
}
}
}
assign_local_replaces_initialized_values_without_producing_a_result test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
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) })
try emit(&module, main, integer(1, 42))
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(1) })
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(2), slot = 0 })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(ir.Type.int, outcome.value.type)
try testing.expect_equal(i64(42), outcome.value.data.integer)
try testing.expect_equal(usize(0), diagnostics.count(&store))
}
reset_local_invalidates_reads_and_assignments_at_the_instruction_span test {
operations [2]ir.Op :: [.load_local, .assign_local]
codes [2]diagnostics.Code :: [.read_before_initialization, .write_before_initialization]
for (operations) |op, index| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
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))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) })
try emit(&module, main, ir.Instruction{ op = .reset_local, slot = 0 })
destination ir.ValueId :: if (op == .load_local) ir.ValueId(1) else ir.no_value
try emit(&module, main, ir.Instruction{
op = op, type = .int, destination = destination, slot = 0, left = ir.ValueId(0), span = span,
})
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try expect_trap(outcome, &store, codes[index], span)
try testing.expect_equal(usize(1), diagnostics.count(&store))
}
}
premature_assignment_and_reset_traps_release_frames_at_all_allocation_boundaries test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [1]ir.Type :: [.int]
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))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) })
try emit(&module, main, ir.Instruction{ op = .reset_local, slot = 0 })
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(0), span = span })
failures := usize(0)
succeeded := false
for (usize(0)..usize(16)) |limit| {
state := AllocationState{ remaining = limit }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := run(allocator, &module, &store) catch |error| {
match error {
.out_of_memory: {}
else: return .expectation_failed
}
failures += 1
try testing.expect_equal(usize(0), state.live)
continue
}
defer deinit(&outcome)
succeeded = true
try expect_trap(outcome, &store, .write_before_initialization, span)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
}
try testing.expect(succeeded)
try testing.expect(failures > 0)
}
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)
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) })
diagnostic_state := AllocationState{ remaining = 0 }
diagnostic_allocator :: mem.Allocator{ context = &diagnostic_state, vtable = &limited_vtable }
store := diagnostics.init(diagnostic_allocator)
defer diagnostics.deinit(&store)
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
unexpected := run(allocator, &module, &store) catch |error| {
match error {
.out_of_memory: {}
else: return .expectation_failed
}
try testing.expect_equal(usize(0), state.live)
try testing.expect_equal(usize(0), diagnostic_state.live)
try testing.expect_equal(usize(0), diagnostics.count(&store))
return
}
deinit(&unexpected)
return .expectation_failed
}
logical_not_inverts_boolean_payload_and_composes_in_typed_ir test {
operands [2]bool :: [false, true]
expected [2]bool :: [true, false]
for (operands) |operand, index| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
main :: try add_function(&module, &[], 0, .bool)
module.entry = main
try emit(&module, main, ir.Instruction{
op = .constant,
type = .bool,
destination = ir.ValueId(0),
constant = ir.Value{ type = .bool, data = ir.ValueData{ boolean = operand } },
})
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(1), left = ir.ValueId(0) })
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(1) })
return_value(&module, main, ir.ValueId(1))
inverted := try run(mem.c_allocator, &module, &store)
defer deinit(&inverted)
try testing.expect(inverted.trap == null)
try testing.expect_equal(ir.Type.bool, inverted.value.type)
try testing.expect_equal(expected[index], inverted.value.data.boolean)
return_value(&module, main, ir.ValueId(2))
composed := try run(mem.c_allocator, &module, &store)
defer deinit(&composed)
try testing.expect(composed.trap == null)
try testing.expect_equal(operand, composed.value.data.boolean)
try testing.expect_equal(usize(0), diagnostics.count(&store))
}
}
@hide:file
unsigned proc(destination u32, value u64) ir.Instruction {
return ir.Instruction{
op = .constant, type = .usize, destination = ir.ValueId(destination),
constant = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = value } },
}
}
@hide:file
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
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))
try emit(module, function, ir.Instruction{
op = .array_literal, type = .array, array_type = shape, destination = ir.ValueId(destination),
arguments_start = start, arguments_count = elements.len,
})
}
nested_array_places_and_rvalues_copy_only_their_typed_leaf_segments test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
row :: try add_array(&module, .int, ir.no_array, 2)
matrix :: try add_array(&module, .array, row, 2)
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 emit(&module, main, integer(0, 1))
try emit(&module, main, integer(1, 2))
pair [2]u32 :: [0, 1]
try literal(&module, main, 2, row, pair[..])
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 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 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))
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(10), right = ir.ValueId(11) })
# a row load is a private copy, not a view of the subsequently mutated matrix.
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(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 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 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)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(matrix, outcome.value.array_type)
expected [4]i64 :: [1, 9, 1, 2]
for (expected) |value, i| try testing.expect_equal(value, outcome.value.data.elements[i].data.integer)
}
array_arguments_and_returns_are_private_values_before_frame_release test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
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)
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 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 })
try emit(&module, callee, unsigned(2, 0))
try emit(&module, callee, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
try emit(&module, callee, integer(4, 99))
try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) })
try emit(&module, callee, ir.Instruction{ op = .load_local, type = .array, array_type = shape, destination = ir.ValueId(5), slot = 1 })
# typed IR can address a parameter; even this mutation must not escape into the caller.
try emit(&module, callee, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(1), slot = 0 })
try emit(&module, callee, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
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)
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 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))
try emit(&module, main, ir.Instruction{ op = .index, type = .int, destination = ir.ValueId(5), left = ir.ValueId(1), right = ir.ValueId(4) })
try emit(&module, main, ir.Instruction{ op = .index, type = .int, destination = ir.ValueId(6), left = ir.ValueId(3), right = ir.ValueId(4) })
pair [2]u32 :: [5, 6]
try literal(&module, main, 7, pair_shape, pair[..])
return_value(&module, main, ir.ValueId(7))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.elements[0].data.integer)
try testing.expect_equal(i64(99), outcome.value.data.elements[1].data.integer)
}
lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
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)
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 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))
operand [1]u32 :: [0]
try literal(&module, second, 1, shape, operand[..])
return_value(&module, second, ir.ValueId(1))
initializers [2]ir.FunctionId :: [first, second]
for (initializers) |initializer| {
try arraylist.append(&module.globals, ir.Global{
type = .array, array_type = shape, initializer = initializer,
span = diagnostics.Span{ start = 0, end = 0 },
})
}
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 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
failures usize := 0
for (usize(0)..usize(40)) |limit| {
state := AllocationState{ remaining = limit }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := run(allocator, &module, &store) catch |error| {
if (error != .out_of_memory) return .expectation_failed
failures += 1
try testing.expect_equal(usize(0), state.live)
continue
}
defer deinit(&outcome)
succeeded = true
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.elements[0].data.integer)
try testing.expect_equal(usize(1), state.live)
deinit(&outcome)
try testing.expect_equal(ir.Type.void, outcome.value.type)
try testing.expect(outcome.owned_elements == null)
try testing.expect_equal(usize(0), state.live)
}
try testing.expect(succeeded)
try testing.expect(failures > 0)
# restarting a run clears global readiness, including address-triggered initialization cycles.
cycle_span :: diagnostics.Span{ start = 51, end = 59 }
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)
defer deinit(&cycle)
try expect_trap(cycle, &store, .initialization_cycle, cycle_span)
deinit(&cycle)
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.globals.items[1].initializer = second
trapped := try run(allocator, &module, &store)
defer deinit(&trapped)
try testing.expect_equal(trap_id, trapped.trap?)
deinit(&trapped)
try testing.expect_equal(usize(0), state.live)
}
array_bounds_check_zero_lengths_and_the_full_unsigned_word_before_addressing test {
lengths [2]usize :: [0, 1]
indices [3]u64 :: [0, 1, maxval!(u64)]
operations [2]ir.Op :: [.index, .index_address]
for (lengths) |length| {
for (indices) |index| {
for (operations) |op| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, length)
main :: try add_function(&module, &[], 0, .int)
_ = try ir.add_local(&module, main, .array, shape)
module.entry = main
try emit(&module, main, integer(0, 42))
operands [1]u32 :: [0]
try literal(&module, main, 1, shape, operands[..length])
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(1) })
try emit(&module, main, unsigned(2, index))
span :: diagnostics.Span{ start = 17, end = 29 }
base := ir.ValueId(1)
if (op == .index_address) {
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(3), slot = 0 })
base = ir.ValueId(3)
}
result_type ir.Type :: if (op == .index) .int else .address
try emit(&module, main, ir.Instruction{
op = op, type = result_type,
pointee = .int, destination = ir.ValueId(4), left = base, right = ir.ValueId(2), span = span,
})
if (op == .index_address) {
try emit(&module, main, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(0), left = ir.ValueId(4) })
return_value(&module, main, ir.ValueId(0))
} else return_value(&module, main, ir.ValueId(4))
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := try run(allocator, &module, &store)
defer deinit(&outcome)
if (length == 1 and index == 0) {
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(42), outcome.value.data.integer)
} else try expect_trap(outcome, &store, .index_out_of_bounds, span)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
}
}
}
}
zero_leaf_nested_arrays_return_a_shape_without_an_owned_allocation test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
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)
module.entry = main
module.functions.items[usize(main)].result_array_type = rows
try literal(&module, main, 0, empty, &[])
operands [3]u32 :: [0, 0, 0]
try literal(&module, main, 1, rows, operands[..])
return_value(&module, main, ir.ValueId(1))
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := try run(allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(rows, outcome.value.array_type)
try testing.expect_equal(usize(0), outcome.value.data.elements.len)
try testing.expect_equal(usize(0), state.live)
deinit(&outcome)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
try emit(&module, main, unsigned(2, 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))
module.functions.items[usize(main)].result_array_type = empty
indexed := try run(allocator, &module, &store)
defer deinit(&indexed)
try testing.expect(indexed.trap == null)
try testing.expect_equal(empty, indexed.value.array_type)
try testing.expect_equal(usize(0), indexed.value.data.elements.len)
deinit(&indexed)
try testing.expect_equal(usize(0), state.live)
}
@hide:file
UnsignedCase :: struct {
op ir.Op
left u64
right u64
expected ?u64
}
unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
wide_sum u64 :: 9223372036854775815
almost_maximum u64 :: 18446744073709551614
cases [11]UnsignedCase :: [
UnsignedCase{ op = .add, left = maxval!(u64), right = 0, expected = maxval!(u64) },
UnsignedCase{ op = .add, left = maxval!(u64), right = 1, expected = null },
UnsignedCase{ op = .add, left = 9223372036854775808, right = 7, expected = wide_sum },
UnsignedCase{ op = .subtract, left = 0, right = 1, expected = null },
UnsignedCase{ op = .subtract, left = maxval!(u64), right = 1, expected = almost_maximum },
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 1, expected = maxval!(u64) },
UnsignedCase{ op = .multiply, left = 9223372036854775808, right = 2, expected = null },
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 0, expected = u64(0) },
UnsignedCase{ op = .multiply, left = 0, right = maxval!(u64), expected = u64(0) },
UnsignedCase{ op = .multiply, left = 3, right = 14, expected = u64(42) },
UnsignedCase{ op = .remainder, left = maxval!(u64), right = 10, expected = u64(5) },
]
for (cases) |case| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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))
try emit(&module, main, unsigned(1, case.right))
try emit(&module, main, ir.Instruction{ op = case.op, type = .usize, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
if case.expected |expected| {
try testing.expect(outcome.trap == null)
try testing.expect_equal(ir.Type.usize, outcome.value.type)
try testing.expect_equal(expected, outcome.value.data.unsigned)
} else try expect_trap(outcome, &store, .integer_overflow, span)
}
operations [6]ir.Op :: [.equal, .not_equal, .less, .less_equal, .greater, .greater_equal]
expected [6]bool :: [false, true, false, false, true, true]
for (operations) |op, i| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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))
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(expected[i], outcome.value.data.boolean)
}
}
all_scalar_leaf_families_survive_aggregate_return_and_function_extraction test {
values [5]ir.Value :: [
ir.Value{ type = .bool, data = ir.ValueData{ boolean = true } },
ir.Value{ type = .float, data = ir.ValueData{ real = 3.25 } },
ir.Value{ type = .string, data = ir.ValueData{ string = ir.StringId(0) } },
ir.Value{ type = .usize, data = ir.ValueData{ unsigned = maxval!(u64) } },
ir.Value{ type = .function, data = ir.ValueData{ function = ir.FunctionId(0) } },
]
for (values) |scalar| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
if (scalar.type == .string) {
text :: "borrowed\x00text"
owned :: try mem.alloc(u8, module.allocator, text.len)
errdefer mem.free(module.allocator, owned)
memcopy!(owned, text)
try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len })
}
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
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)
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 })
operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..])
return_value(&module, main, ir.ValueId(1))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
leaf :: outcome.value.data.elements[0]
try testing.expect_equal(scalar.type, leaf.type)
match scalar.type {
.bool: try testing.expect(leaf.data.boolean)
.float: try testing.expect_equal(f64(3.25), leaf.data.real)
.string: try testing.expect_equal([]u8, "borrowed\x00text", ir.string_bytes(&module, leaf.data.string))
.usize: try testing.expect_equal(maxval!(u64), leaf.data.unsigned)
.function: {
try testing.expect_equal(target, leaf.data.function)
try emit(&module, main, unsigned(2, 0))
try emit(&module, main, ir.Instruction{ op = .index, type = .function, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
try emit(&module, main, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(4), left = ir.ValueId(3) })
return_value(&module, main, ir.ValueId(4))
called := try run(mem.c_allocator, &module, &store)
defer deinit(&called)
try testing.expect(called.trap == null)
try testing.expect_equal(i64(42), called.value.data.integer)
}
else: unreachable
}
}
}
array_address_initialization_flags_survive_reset_and_diagnostic_allocation_failure test {
operations [2]ir.Op :: [.load_address, .store_address]
codes [2]diagnostics.Code :: [.read_before_initialization, .write_before_initialization]
for (operations) |op, i| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
shape :: try add_array(&module, .int, ir.no_array, 1)
main :: try add_function(&module, &[], 0, .int)
_ = try ir.add_local(&module, main, .array, shape)
module.entry = main
try emit(&module, main, integer(0, 42))
operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..])
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(1) })
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(2), slot = 0 })
try emit(&module, main, unsigned(3, 0))
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 }
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 }
remaining usize :: if (attempt == 0) 0 else 64
diagnostic_state := AllocationState{ remaining = remaining }
diagnostic_allocator :: mem.Allocator{ context = &diagnostic_state, vtable = &limited_vtable }
store := diagnostics.init(diagnostic_allocator)
defer diagnostics.deinit(&store)
outcome := run(allocator, &module, &store) catch |error| {
if (error != .out_of_memory) return .expectation_failed
try testing.expect_equal(usize(0), attempt)
try testing.expect_equal(usize(0), state.live)
try testing.expect_equal(usize(0), diagnostic_state.live)
continue
}
defer deinit(&outcome)
try testing.expect_equal(usize(1), attempt)
try expect_trap(outcome, &store, codes[i], span)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
}
}
}
aggregate_loop_places_reuse_storage_without_allocation_per_iteration test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1)
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 emit(&module, main, integer(0, 0))
operand [1]u32 :: [0]
try literal(&module, main, 1, shape, operand[..])
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(1) })
try emit(&module, main, unsigned(2, 0))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(2) })
try emit(&module, main, unsigned(3, 1))
header :: try add_block(&module, main)
body :: try add_block(&module, main)
done :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = header }
try emit_at(&module, main, header, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(4), slot = 1 })
try emit_at(&module, main, header, ir.Instruction{ op = .less, type = .bool, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(3) })
module.functions.items[usize(main)].blocks.items[usize(header)].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(5), target = body, alternative = done }
try emit_at(&module, main, body, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(6), slot = 0 })
try emit_at(&module, main, body, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(7), left = ir.ValueId(6), right = ir.ValueId(2) })
try emit_at(&module, main, body, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(8), left = ir.ValueId(7) })
try emit_at(&module, main, body, integer(9, 1))
try emit_at(&module, main, body, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(10), left = ir.ValueId(8), right = ir.ValueId(9) })
try emit_at(&module, main, body, ir.Instruction{ op = .store_address, left = ir.ValueId(7), right = ir.ValueId(10) })
try arraylist.append(&module.functions.items[usize(main)].blocks.items[usize(body)].arguments, ir.ValueId(10))
try emit_at(&module, main, body, ir.Instruction{ op = .array_literal, type = .array, array_type = shape, destination = ir.ValueId(1), arguments_count = 1 })
try emit_at(&module, main, body, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(1) })
try emit_at(&module, main, body, unsigned(11, 1))
try emit_at(&module, main, body, ir.Instruction{ op = .add, type = .usize, destination = ir.ValueId(12), left = ir.ValueId(4), right = ir.ValueId(11) })
try emit_at(&module, main, body, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(12) })
module.functions.items[usize(main)].blocks.items[usize(body)].terminator = ir.Terminator{ kind = .jump, target = header }
try emit_at(&module, main, done, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(13), left = ir.ValueId(7) })
module.functions.items[usize(main)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(13) }
iterations [2]u64 :: [1, 10000]
allocations usize := 0
for (iterations) |count, i| {
module.functions.items[usize(main)].blocks.items[0].instructions.items[5] = unsigned(3, count)
state := AllocationState{ remaining = 64 }
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
outcome := try run(allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(count), outcome.value.data.integer)
deinit(&outcome)
try testing.expect_equal(usize(0), state.live)
consumed :: usize(64) - state.remaining
if (i == 0) allocations = consumed
else try testing.expect_equal(allocations, consumed)
}
}
recursive_array_backings_and_addresses_remain_fresh_across_stack_growth test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
shape :: try add_array(&module, .int, ir.no_array, 1)
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[..])
try emit(&module, recursive, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(1) })
try emit(&module, recursive, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(2), slot = 1 })
try emit(&module, recursive, unsigned(3, 0))
try emit(&module, recursive, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(4), left = ir.ValueId(2), right = ir.ValueId(3) })
try emit(&module, recursive, integer(5, 0))
try emit(&module, recursive, ir.Instruction{ op = .equal, type = .bool, destination = ir.ValueId(6), left = ir.ValueId(0), right = ir.ValueId(5) })
done :: try add_block(&module, recursive)
recurse :: try add_block(&module, recursive)
module.functions.items[usize(recursive)].blocks.items[0].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(6), target = done, alternative = recurse }
module.functions.items[usize(recursive)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(5) }
try emit_at(&module, recursive, recurse, integer(5, 1))
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .subtract, type = .int, destination = ir.ValueId(7), left = ir.ValueId(0), right = ir.ValueId(5) })
try emit_at(&module, recursive, recurse, function_value(8, recursive))
try arraylist.append(&module.functions.items[usize(recursive)].blocks.items[usize(recurse)].arguments, ir.ValueId(7))
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(9), left = ir.ValueId(8), arguments_count = 1 })
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)
module.entry = main
try emit(&module, main, function_value(0, recursive))
try emit(&module, main, integer(1, 64))
try arraylist.append(&module.functions.items[usize(main)].blocks.items[0].arguments, ir.ValueId(1))
try emit(&module, main, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), arguments_count = 1 })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(2080), outcome.value.data.integer)
}
inclusive_unsigned_range_reaches_the_maximum_without_incrementing_past_it test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
types [2]ir.Type :: [.usize, .usize]
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) })
try emit(&module, main, unsigned(1, 0))
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(1) })
try emit(&module, main, unsigned(2, maxval!(u64)))
try emit(&module, main, unsigned(3, 1))
header :: try add_block(&module, main)
body :: try add_block(&module, main)
advance :: try add_block(&module, main)
done :: try add_block(&module, main)
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = header }
try emit_at(&module, main, header, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(4), slot = 0 })
try emit_at(&module, main, header, ir.Instruction{ op = .less_equal, type = .bool, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(2) })
module.functions.items[usize(main)].blocks.items[usize(header)].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(5), target = body, alternative = done }
try emit_at(&module, main, body, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(6), slot = 1 })
try emit_at(&module, main, body, ir.Instruction{ op = .add, type = .usize, destination = ir.ValueId(7), left = ir.ValueId(6), right = ir.ValueId(3) })
try emit_at(&module, main, body, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(7) })
try emit_at(&module, main, body, ir.Instruction{ op = .equal, type = .bool, destination = ir.ValueId(8), left = ir.ValueId(4), right = ir.ValueId(2) })
module.functions.items[usize(main)].blocks.items[usize(body)].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(8), target = done, alternative = advance }
try emit_at(&module, main, advance, ir.Instruction{ op = .add, type = .usize, destination = ir.ValueId(0), left = ir.ValueId(4), right = ir.ValueId(3) })
try emit_at(&module, main, advance, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(0) })
module.functions.items[usize(main)].blocks.items[usize(advance)].terminator = ir.Terminator{ kind = .jump, target = header }
try emit_at(&module, main, done, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(1), slot = 1 })
module.functions.items[usize(main)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(1) }
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(u64(2), outcome.value.data.unsigned)
}
union_storage_overlays_nested_record_and_array_payloads_and_copies_by_value test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
record_fields [2]ir.RecordField :: [ir.RecordField{ type = .u8 }, ir.RecordField{ type = .u32 }]
record :: try ir.add_record(&module, record_fields[..])
bytes :: try add_array(&module, .u8, ir.no_array, 8)
variants [2]ir.RecordField :: [
ir.RecordField{ type = .record, array_type = record },
ir.RecordField{ type = .array, array_type = bytes },
]
shape :: try ir.add_union(&module, variants[..], false)
identity :: try add_function(&module, &[], 0, .record)
_ = try ir.add_parameter(&module, identity, .record, shape)
module.functions.items[usize(identity)].result_array_type = shape
try emit(&module, identity, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(0) })
return_value(&module, identity, ir.ValueId(0))
main :: try add_function(&module, &[], 0, .u32)
_ = try ir.add_local(&module, main, .record, shape)
module.entry = main
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(0), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 1 } } })
try emit(&module, main, ir.Instruction{ op = .constant, type = .u32, destination = ir.ValueId(1), constant = ir.Value{ type = .u32, data = ir.ValueData{ unsigned = 287454020 } } })
block :: &module.functions.items[usize(main)].blocks.items[0]
try arraylist.append(&block.arguments, ir.ValueId(0))
try arraylist.append(&block.arguments, ir.ValueId(1))
try emit(&module, main, ir.Instruction{ op = .record_literal, type = .record, array_type = record, destination = ir.ValueId(2), arguments_count = 2 })
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(3), left = ir.ValueId(2) })
try emit(&module, main, ir.Instruction{ op = .store_local, left = ir.ValueId(3) })
try emit(&module, main, function_value(4, identity))
try arraylist.append(&block.arguments, ir.ValueId(3))
try emit(&module, main, ir.Instruction{ op = .call, type = .record, array_type = shape, destination = ir.ValueId(5), left = ir.ValueId(4), arguments_start = 2, arguments_count = 1 })
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(6) })
try emit(&module, main, ir.Instruction{ op = .field_address, type = .address, pointee = .array, array_type = bytes, destination = ir.ValueId(7), left = ir.ValueId(6), slot = 1 })
try emit(&module, main, unsigned(8, 4))
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .u8, destination = ir.ValueId(9), left = ir.ValueId(7), right = ir.ValueId(8) })
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(10), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 153 } } })
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(9), right = ir.ValueId(10) })
try emit(&module, main, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(11) })
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(12), left = ir.ValueId(11) })
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(13), left = ir.ValueId(12), slot = 1 })
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(14), left = ir.ValueId(5) })
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(15), left = ir.ValueId(14), slot = 1 })
try emit(&module, main, ir.Instruction{ op = .subtract, type = .u32, destination = ir.ValueId(16), left = ir.ValueId(13), right = ir.ValueId(15) })
return_value(&module, main, ir.ValueId(16))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(u64(85), outcome.value.data.unsigned)
}
tagged_unions_preserve_actual_tags_and_trap_inactive_value_and_address_projections test {
operations [2]ir.Op :: [.field, .field_address]
for (operations) |operation| {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
tag :: ir.Value{ type = .i8, data = ir.ValueData{ integer = -1 } }
variants [2]ir.RecordField :: [
ir.RecordField{ type = .int, tag = tag },
ir.RecordField{ type = .int, tag = ir.Value{ type = .i8, data = ir.ValueData{ integer = 37 } } },
]
shape :: try ir.add_union(&module, variants[..], true)
main :: try add_function(&module, &[], 0, .i8)
_ = try ir.add_local(&module, main, .record, shape)
module.entry = main
try emit(&module, main, integer(0, 42))
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0), constant = tag })
try emit(&module, main, ir.Instruction{ op = .union_tag, type = .i8, destination = ir.ValueId(2), left = ir.ValueId(1) })
return_value(&module, main, ir.ValueId(2))
outcome := try run(mem.c_allocator, &module, &store)
defer deinit(&outcome)
try testing.expect(outcome.trap == null)
try testing.expect_equal(i64(-1), outcome.value.data.integer)
base := ir.ValueId(1)
if (operation == .field_address) {
try emit(&module, main, ir.Instruction{ op = .store_local, left = base })
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(3) })
base = ir.ValueId(3)
}
span :: diagnostics.Span{ start = 11, end = 15 }
type :: if (operation == .field) ir.Type.int else ir.Type.address
try emit(&module, main, ir.Instruction{ op = operation, type = type, pointee = .int, destination = ir.ValueId(4), left = base, slot = 1, span = span })
trapped := try run(mem.c_allocator, &module, &store)
defer deinit(&trapped)
try expect_trap(trapped, &store, .inactive_union_field, span)
}
}
union_scalar_reinterpretation_and_zero_leaf_payloads_keep_their_shapes test {
module := ir.init(mem.c_allocator)
defer ir.deinit(&module)
store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&store)
empty :: try ir.add_record(&module, &[])
variants [3]ir.RecordField :: [
ir.RecordField{ type = .float },
ir.RecordField{ type = .u64 },
ir.RecordField{ type = .record, array_type = empty },
]
shape :: try ir.add_union(&module, variants[..], false)
main :: try add_function(&module, &[], 0, .u64)
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 = 1.0 } } })
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0) })
try emit(&module, main, ir.Instruction{ op = .field, type = .u64, destination = ir.ValueId(2), left = ir.ValueId(1), slot = 1 })
return_value(&module, main, ir.ValueId(2))
bits := try run(mem.c_allocator, &module, &store)
defer deinit(&bits)
try testing.expect(bits.trap == null)
try testing.expect_equal(u64(4607182418800017408), bits.value.data.unsigned)
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = empty, destination = ir.ValueId(3), left = ir.ValueId(1), slot = 2 })
module.functions.items[usize(main)].result = .record
module.functions.items[usize(main)].result_array_type = empty
return_value(&module, main, ir.ValueId(3))
zero := try run(mem.c_allocator, &module, &store)
defer deinit(&zero)
try testing.expect(zero.trap == null)
try testing.expect_equal(empty, zero.value.array_type)
try testing.expect_equal(usize(0), zero.value.data.elements.len)
}