feat: execute Bro-compatible integer range loops
This commit is contained in:
+84
-5
@@ -18,7 +18,7 @@ Storage :: struct {
|
||||
|
||||
@hide:file
|
||||
LoopTargets :: struct {
|
||||
guard ir.BlockId
|
||||
continue_target ir.BlockId
|
||||
exit ir.BlockId
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ lower_type proc(type typecheck.Type) ir.Type {
|
||||
.string: .string
|
||||
.void: .void
|
||||
.function: .function
|
||||
.none, .invalid: .invalid
|
||||
.none, .invalid, .range: .invalid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,6 +267,19 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! mem.AllocError
|
||||
if (conditional.else_block) |alternative| try predeclare_locals(state, alternative)
|
||||
}
|
||||
.while_stmt: try predeclare_locals(state, ast.while_stmt(state.tree, statement).body)
|
||||
.for_stmt: {
|
||||
loop :: ast.for_stmt(state.tree, statement)
|
||||
function := &state.module.functions.items[state.function]
|
||||
slot :: u32(function.locals.items.len)
|
||||
try arraylist.append(&function.locals, ir.Type.int)
|
||||
state.storage[usize(statement)] = Storage{ kind = .local, index = slot }
|
||||
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
|
||||
if (loop.index) |index| {
|
||||
state.storage[usize(index)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
|
||||
try arraylist.append(&function.locals, ir.Type.int)
|
||||
}
|
||||
try predeclare_locals(state, loop.body)
|
||||
}
|
||||
else: {}
|
||||
}
|
||||
}
|
||||
@@ -365,7 +378,7 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
|
||||
span = ast.node_span(state.tree, loop.condition),
|
||||
}
|
||||
previous_loop :: state.loop
|
||||
state.loop = LoopTargets{ guard = guard, exit = exit }
|
||||
state.loop = LoopTargets{ continue_target = guard, exit = exit }
|
||||
defer state.loop = previous_loop
|
||||
state.block = body
|
||||
try lower_block(state, loop.body, true)
|
||||
@@ -378,18 +391,80 @@ lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagn
|
||||
state.terminated = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
loop :: ast.for_stmt(state.tree, id)
|
||||
if (ast.expr_kind(state.tree, loop.iterable) != .range) {
|
||||
_ = try lower_expression(state, loop.iterable)
|
||||
if (!state.terminated) _ = trap_type(state, id)
|
||||
return
|
||||
}
|
||||
interval :: ast.range_expr(state.tree, loop.iterable)
|
||||
start :: try lower_expression(state, interval.start)
|
||||
if (state.terminated) return
|
||||
end :: try lower_expression(state, interval.end)
|
||||
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
|
||||
slot :: state.storage[usize(id)].index
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, type = .int, left = start, slot = slot, span = interval.span })
|
||||
origin :: state.block
|
||||
condition :: try add_block(state)
|
||||
body :: try add_block(state)
|
||||
update :: try add_block(state)
|
||||
increment :: try add_block(state)
|
||||
exit :: try add_block(state)
|
||||
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
||||
kind = .jump, target = condition, span = loop.span,
|
||||
}
|
||||
state.block = condition
|
||||
current :: try emit(state, ir.Instruction{ op = .load_local, type = .int, slot = slot, span = interval.span })
|
||||
comparison ir.Op :: if (interval.inclusive) .less_equal else .less
|
||||
condition_value :: try emit(state, ir.Instruction{ op = comparison, type = .bool, left = current, right = end, span = interval.span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = condition_value, target = body, alternative = exit, span = interval.span,
|
||||
}
|
||||
previous_loop :: state.loop
|
||||
state.loop = LoopTargets{ continue_target = update, exit = exit }
|
||||
defer state.loop = previous_loop
|
||||
state.block = body
|
||||
if (loop.capture) |capture| _ = trap_binding(state, capture)
|
||||
if (!state.terminated) try lower_block(state, loop.body, true)
|
||||
if (!state.terminated) {
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = update, span = loop.span,
|
||||
}
|
||||
}
|
||||
state.block = update
|
||||
state.terminated = false
|
||||
last :: try emit(state, ir.Instruction{ op = .load_local, type = .int, slot = slot, span = interval.span })
|
||||
finished :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = last, right = end, span = interval.span })
|
||||
# continue reaches this guard too, so an inclusive maximum never adds one.
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span,
|
||||
}
|
||||
state.block = increment
|
||||
one :: try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, interval.span)
|
||||
next :: try emit(state, ir.Instruction{ op = .add, type = .int, left = last, right = one, span = interval.span })
|
||||
_ = try emit(state, ir.Instruction{ op = .store_local, type = .int, left = next, slot = slot, span = interval.span })
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = condition, span = loop.span,
|
||||
}
|
||||
state.block = exit
|
||||
state.terminated = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
tree :: state.tree
|
||||
match ast.stmt_kind(tree, id) {
|
||||
.if_stmt: try lower_if(state, id)
|
||||
.while_stmt: try lower_while(state, id)
|
||||
.for_stmt: try lower_for(state, id)
|
||||
.break_stmt, .continue_stmt: {
|
||||
if (trap_type(state, id)) return
|
||||
if (state.loop) |targets| {
|
||||
target := targets.exit
|
||||
# continue restarts the whole guard, before any short-circuit branches.
|
||||
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.guard
|
||||
# each loop supplies its own guard or guarded-update entry.
|
||||
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.continue_target
|
||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||
kind = .jump, target = target, span = ast.node_span(tree, id),
|
||||
}
|
||||
@@ -491,6 +566,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
||||
invalid :: ast.invalid(tree, id)
|
||||
trap(state, invalid.diagnostic, invalid.span)
|
||||
}
|
||||
.range: {
|
||||
# ranges have no physical value; only lower_for decomposes an iterable.
|
||||
if (!trap_type(state, id)) unreachable
|
||||
}
|
||||
.literal_int, .literal_float: {
|
||||
if (trap_type(state, id)) return ir.no_value
|
||||
return try lower_number(state, id)
|
||||
|
||||
@@ -439,6 +439,15 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
|
||||
`main proc() string {
|
||||
` computed float :: factor * 2
|
||||
` remaining := 2
|
||||
` for 0..=2 |item| {
|
||||
` saved :: identity(text)
|
||||
` for 0..2 |_| {
|
||||
` nested :: identity(saved)
|
||||
` identity(nested)
|
||||
` if (item == 1) continue else break
|
||||
` }
|
||||
` if (item < 2) continue else break
|
||||
` }
|
||||
` while compose(remaining > 0 and enabled, compose(!false, compose(true or false, compose(true, compose(true, true))))) {
|
||||
` pass :: identity(text)
|
||||
` identity(pass)
|
||||
@@ -1117,8 +1126,16 @@ lazy_branchful_global_initializers_and_calls_release_every_runtime_allocation te
|
||||
`first :: true and later
|
||||
`later :: false or !false
|
||||
`both proc(left bool, right bool) bool { return left and right }
|
||||
`bound proc(flag bool, value int) int {
|
||||
` if flag { return value }
|
||||
` return 0
|
||||
`}
|
||||
`main proc() bool {
|
||||
` n := 3
|
||||
` for bound(first or false, 0)..bound(both(first, true and later), 3) |i| {
|
||||
` local :: both(first and first, later or false)
|
||||
` if (local and i < 2) continue else break
|
||||
` }
|
||||
` while both(first and first, both(false or later, !false and later)) and n > 0 {
|
||||
` n = n - 1
|
||||
` if (n > 0) continue else break
|
||||
@@ -1299,3 +1316,221 @@ traps_before_loop_controls_keep_the_original_error test {
|
||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
}
|
||||
|
||||
owned_integer_ranges_execute_exclusive_inclusive_empty_signed_and_discard_forms test {
|
||||
sources [13][]u8 :: [
|
||||
"main proc() int {\n sum := 0\n for 0..7 |i| { sum = sum + i }\n return sum\n}\n",
|
||||
"main proc() int {\n sum := 0\n for (0..=6) |i| { sum = sum + i }\n return sum\n}\n",
|
||||
"main proc() int {\n sum := 0\n for (-3)..3 |i| { sum = sum + i }\n return sum\n}\n",
|
||||
"main proc() int {\n sum := 0\n for (-3)..=3 |i| { sum = sum + i }\n return sum\n}\n",
|
||||
"main proc() int {\n sum := 0\n for 7..7 |i| { sum = sum + i }\n return sum\n}\n",
|
||||
"main proc() int {\n sum := 0\n for 7..=7 |i| { sum = sum + i }\n return sum\n}\n",
|
||||
"main proc() int {\n count := 0\n for 7..2 |_| { count = count + 1 }\n for 7..=2 |_| { count = count + 1 }\n return count\n}\n",
|
||||
"main proc() int {\n count := 0\n for (0..4) |_| count = count + 1\n return count\n}\n",
|
||||
"main proc() int {\n count := 0\n for 9223372036854775807..=9223372036854775807 |i| {\n count = count + 1\n if (count > 2) break\n continue\n }\n return count\n}\n",
|
||||
"main proc() int {\n count := 0\n for 9223372036854775806..=9223372036854775807 |_| {\n count = count + 1\n if (count > 3) break\n continue\n }\n return count\n}\n",
|
||||
"main proc() int {\n count := 0\n for 9223372036854775806..9223372036854775807 |i| {\n count = count + 1\n if (count > 2) break\n continue\n }\n return count\n}\n",
|
||||
"main proc() int {\n for (-9223372036854775808)..(-9223372036854775807) |i| { return i }\n return 0\n}\n",
|
||||
"main proc() int {\n sum := 0\n for 0..(3 + 1) |i| { sum = sum + i }\n return sum\n}\n",
|
||||
]
|
||||
expected [13]i64 :: [21, 21, -3, 0, 0, 7, 0, 4, 1, 2, 1, minval!(i64), 6]
|
||||
for (sources) |source, index| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
range_bounds_calls_and_globals_are_frozen_before_branchful_body_calls test {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
source ::
|
||||
`seed :: choose(true or false, identity(0))
|
||||
`limit :: choose(false or true, identity(4))
|
||||
`identity proc(value int) int { return value }
|
||||
`choose proc(flag bool, value int) int {
|
||||
` if flag { return value }
|
||||
` return 99
|
||||
`}
|
||||
`main proc() int {
|
||||
` first := seed
|
||||
` last := limit
|
||||
` sum := 0
|
||||
` for choose(true and !false, identity(first))..choose(false or true, choose(true or false, last)) |i| {
|
||||
` sum = choose(i == 0 or i > 0, identity(sum + i))
|
||||
` first = 100
|
||||
` last = 0
|
||||
` }
|
||||
` return sum + 36
|
||||
`}
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
|
||||
range_break_continue_and_nested_for_while_restore_lexical_targets test {
|
||||
sources [3][]u8 :: [
|
||||
"main proc() int {\n sum := 0\n count := 0\n for 0..10 |i| {\n count = count + 1\n if (count > 12) break\n if (i == 2 or i == 4) continue\n if (i == 6) break\n sum = sum + i\n }\n return sum + 33\n}\n",
|
||||
"main proc() int {\n sum := 0\n count := 0\n for 0..3 |outer| {\n count = count + 1\n if (count > 4) break\n inner := 3\n while inner > 0 {\n inner = inner - 1\n if (inner == 1) continue\n if (inner == 0) break\n sum = sum + 7\n }\n if (outer < 2) continue else break\n }\n return sum + 21\n}\n",
|
||||
"main proc() int {\n outer := 3\n sum := 0\n while outer > 0 {\n outer = outer - 1\n count := 0\n for 0..3 |i| {\n count = count + 1\n if (count > 4) break\n for 0..3 |j| {\n if (j == 0) continue\n sum = sum + 7\n break\n }\n if (i == 0) continue else break\n }\n if (outer > 0) continue else break\n }\n return sum\n}\n",
|
||||
]
|
||||
for (sources) |source| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
}
|
||||
}
|
||||
|
||||
range_body_and_arm_locals_reset_before_continued_later_declarations test {
|
||||
sources [6][]u8 :: [
|
||||
"main proc() int {\n for 0..3 |i| {\n if i == 2 { return later }\n if (i == 1) continue\n later :: 42\n continue\n }\n return 0\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n if i == 2 { later = 42 }\n if (i == 1) continue\n later := 0\n continue\n }\n return 0\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n if true {\n if i == 2 { return later }\n if (i == 1) continue\n later :: 42\n continue\n }\n }\n return 0\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n if true {\n if i == 2 { later = 42 }\n if (i == 1) continue\n later := 0\n continue\n }\n }\n return 0\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n if false {} else {\n if i == 2 { return later }\n if (i == 1) continue\n later :: 42\n continue\n }\n }\n return 0\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n if false {} else {\n if i == 2 { later = 42 }\n if (i == 1) continue\n later := 0\n continue\n }\n }\n return 0\n}\n",
|
||||
]
|
||||
codes [6]diagnostics.Code :: [
|
||||
.read_before_initialization, .write_before_initialization,
|
||||
.read_before_initialization, .write_before_initialization,
|
||||
.read_before_initialization, .write_before_initialization,
|
||||
]
|
||||
for (sources) |source, index| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
}
|
||||
|
||||
untaken_range_errors_and_errors_after_controls_remain_diagnostic_only test {
|
||||
sources [10][]u8 :: [
|
||||
"main proc() int {\n for 0..0 |i| { return unknown }\n return 42\n}\n",
|
||||
"main proc() int {\n for 3..2 |i| { i = 1 }\n return 42\n}\n",
|
||||
"main proc() int {\n i :: 0\n for 0..0 |i| {}\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n continue\n missing\n }\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n break\n missing\n }\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..3 |i| {\n return 42\n break\n continue\n missing\n }\n return 0\n}\n",
|
||||
"unused proc() int {\n for 0..1 |i| { return unknown }\n return 0\n}\nmain proc() int { return 42 }\n",
|
||||
"main proc() int {\n if false { for true..1 |i| {} }\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..0 |int| {}\n return 42\n}\n",
|
||||
"unsupported :: 0..1\nmain proc() int {\n for 0..0 |_| { local :: 0..1 }\n return 42\n}\n",
|
||||
]
|
||||
for (sources) |source| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
count :: diagnostics.count(&diagnostic_store)
|
||||
try testing.expect(count > 0)
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
}
|
||||
|
||||
demanded_range_header_capture_and_body_errors_keep_original_diagnostic_identity test {
|
||||
sources [10][]u8 :: [
|
||||
"main proc() int {\n for true..1 |i| {}\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..1 |i| { i = 1 }\n return 42\n}\n",
|
||||
"main proc() int {\n i :: 0\n for 0..1 |i| {}\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..1 |i| {\n missing\n continue\n }\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..1 |i| {\n missing\n break\n }\n return 42\n}\n",
|
||||
"main proc() int {\n for 1 |i| {}\n return 42\n}\n",
|
||||
"main proc() int {\n 0..1\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..1 |@i| {}\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..1 |i, _| {}\n return 42\n}\n",
|
||||
"main proc() int {\n local :: 0..1\n return 42\n}\n",
|
||||
]
|
||||
codes [10]diagnostics.Code :: [
|
||||
.invalid_range_bound, .invalid_assignment_target, .duplicate_name,
|
||||
.unknown_name, .unknown_name, .invalid_for_iterable, .range_value_not_supported,
|
||||
.range_pointer_capture, .range_index_capture, .range_value_not_supported,
|
||||
]
|
||||
for (sources) |source, index| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||
try testing.expect_equal(codes[index], original.code)
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||
}
|
||||
}
|
||||
|
||||
range_bounds_evaluate_left_to_right_before_header_errors_and_body_overflow_keeps_span test {
|
||||
sources [4][]u8 :: [
|
||||
"first :: fail()\nsecond :: absent\nfail proc() int { return 9223372036854775807 + 1 }\nmain proc() int {\n for first..second |i| {}\n return 42\n}\n",
|
||||
"fail proc() int { return 9223372036854775807 + 1 }\nmain proc() int {\n for fail()..1 |@i| {}\n return 42\n}\n",
|
||||
"main proc() int {\n for 0..1 |i| { return 9223372036854775807 + i + 1 }\n return 0\n}\n",
|
||||
"main proc() int {\n for first_absent..second_absent |i| {}\n return 42\n}\n",
|
||||
]
|
||||
for (sources) |source, index| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
before :: diagnostics.count(&diagnostic_store)
|
||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||
code diagnostics.Code :: if (index == 3) .unknown_name else .integer_overflow
|
||||
try expect_trap(outcome, &diagnostic_store, code)
|
||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||
span []u8 :: match index {
|
||||
0, 1: "9223372036854775807 + 1"
|
||||
2: "9223372036854775807 + i + 1"
|
||||
3: "first_absent"
|
||||
else: unreachable
|
||||
}
|
||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], span))
|
||||
if (index == 3) try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||
else try testing.expect_equal(diagnostics.DiagnosticId(before), outcome.trap?)
|
||||
}
|
||||
}
|
||||
|
||||
range_runtime_allocations_do_not_grow_with_iterations test {
|
||||
sources [2][]u8 :: [
|
||||
"main proc() int {\n count := 0\n for 0..1 |i| {\n local :: i\n count = count + 1\n if (count > 1) break\n if (local >= 0) continue\n later :: 0\n }\n return count\n}\n",
|
||||
"main proc() int {\n count := 0\n for 0..10000 |i| {\n local :: i\n count = count + 1\n if (count > 10000) break\n if (local >= 0) continue\n later :: 0\n }\n return count\n}\n",
|
||||
]
|
||||
expected [2]i64 :: [1, 10000]
|
||||
allocations := usize(0)
|
||||
for (sources) |source, index| {
|
||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&diagnostic_store)
|
||||
module := try compile_owned(source, &diagnostic_store)
|
||||
defer ir.deinit(&module)
|
||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||
state := AllocationState{ remaining = 64 }
|
||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||
outcome :: try interpreter.run(allocator, &module, &diagnostic_store)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
||||
try testing.expect_equal(usize(0), state.live)
|
||||
if (index == 0) allocations = usize(64) - state.remaining
|
||||
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user