whole-function comptime folding for zero-runtime value calls

This commit is contained in:
2026-07-22 23:11:48 +02:00
parent 21ff291788
commit 8b50eb7606
24 changed files with 564 additions and 260 deletions
+100 -17
View File
@@ -3370,7 +3370,8 @@ milestone_39_stable_values_and_richer_formatting_compile_and_run :: proc(t: ^tes
}
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, score_count, 2)
// score has only comptime parameters, so both calls materialize without HIR functions.
testing.expect_value(t, score_count, 0)
testing.expect_value(t, count_substring_occurrences(llvm_text, "define internal fastcc i32 @bro__p0__read_carrier__"), 1)
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
@@ -4407,8 +4408,8 @@ main func() void {}
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(hir_module.functions), 2)
testing.expect(t, strings.contains(llvm_text, "@bro__p0__make("))
testing.expect_value(t, len(hir_module.functions), 1)
testing.expect(t, !strings.contains(llvm_text, "@bro__p0__make("))
testing.expect(t, !strings.contains(llvm_text, "@bro__p0__unused_native("))
testing.expect(t, !strings.contains(llvm_text, "@unused_foreign("))
}
@@ -4511,6 +4512,91 @@ main func() i32 {
testing.expect(t, types.equal(hir_module.globals[1].type, types.I8))
}
@(test)
zero_runtime_calls_fold_with_runtime_fallback :: proc(t: ^testing.T) {
text := `runtime_value i32 = 41
folded func() i32 { return 42 }
by_value func($N usize) usize { return N }
backed func() []i32 {
values [2]mut i32 = [3, 4]
return values[..]
}
fallback func() i32 { return runtime_value }
undefined_result func() i32 {
value i32 = undefined
return value
}
main func() i32 {
if folded() != 42 { return 1 }
if by_value(7) != 7 { return 2 }
view :: backed()
if view[0] != 3 or view[1] != 4 { return 3 }
_ = undefined_result()
return fallback()
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
folded_found := false
by_value_found := false
backed_found := false
fallback_found := false
undefined_found := false
for function in hir_module.functions {
name := symbol.resolve(&symbols, function.name)
folded_found = folded_found || name == "folded"
by_value_found = by_value_found || name == "by_value"
backed_found = backed_found || name == "backed"
fallback_found = fallback_found || name == "fallback"
undefined_found = undefined_found || name == "undefined_result"
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, !folded_found)
testing.expect(t, !by_value_found)
testing.expect(t, !backed_found)
testing.expect(t, fallback_found)
testing.expect(t, undefined_found)
}
@(test)
zero_runtime_fold_preserves_reached_compile_error :: proc(t: ^testing.T) {
text := `fail func() i32 {
compile_error!("folded failure")
return 0
}
main func() i32 { return fail() }
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, "folded failure")
}
testing.expect(t, found)
}
@(test)
comptime_value_params_specialize_by_value_and_omit_runtime_args :: proc(t: ^testing.T) {
text := `make_array func($N usize) [N]u8 {
@@ -4990,10 +5076,6 @@ missing func() i32 {
if true {
}
}
escape_slice func() []i32 {
values [2]mut i32 = [1, 2]
return values[..]
}
GLOBAL :: 1
main func() void {
runtime i32 = 1
@@ -5027,7 +5109,6 @@ main func() void {
view []i32 = values[1..]
yield view
}
_ = $escape_slice()
_ = $spin()
_ = $missing()
_ = ${
@@ -5051,7 +5132,6 @@ main func() void {
runtime_only_count := 0
pointer_errors := 0
slice_errors := 0
found_expired := false
found_quota := false
found_missing := false
found_yield := false
@@ -5061,7 +5141,6 @@ main func() void {
runtime_only_count += 1 if strings.contains(message, "runtime-only") else 0
pointer_errors += 1 if strings.contains(message, "only immutable pointers to whole comptime arrays can materialize as runtime memory") else 0
slice_errors += 1 if strings.contains(message, "only immutable full-array comptime slices can materialize as runtime memory") else 0
found_expired = found_expired || strings.contains(message, "expired storage")
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
found_missing = found_missing || strings.contains(message, "did not return a value")
found_yield = found_yield || strings.contains(message, "a value block must end with an explicit 'yield'")
@@ -5070,7 +5149,6 @@ main func() void {
testing.expect(t, runtime_only_count >= 2)
testing.expect(t, pointer_errors >= 4)
testing.expect(t, slice_errors >= 2)
testing.expect(t, found_expired)
testing.expect(t, found_quota)
testing.expect(t, found_missing)
testing.expect(t, found_yield)
@@ -11419,7 +11497,8 @@ main func() i32 {
}
}
}
testing.expect_value(t, call_count, 2)
// make_array() is a zero-runtime value call and is materialized directly.
testing.expect_value(t, call_count, 1)
testing.expect_value(t, extract_count, 3)
testing.expect_value(t, select_count, 2)
testing.expect(t, pointer_add_count >= 1)
@@ -12026,8 +12105,9 @@ compound_assignment_evaluates_lvalue_once :: proc(t: ^testing.T) {
// A compound assignment to an indexed lvalue must compute the element address
// once and reuse it for the load and the store, rather than re-lowering the
// lvalue (which would re-evaluate any side-effecting index subexpression).
text := `bump func() usize {
return 1
text := `index usize = 1
bump func() usize {
return index
}
main func() i32 {
values [3]mut i32 = [10, 20, 30]
@@ -12076,11 +12156,13 @@ compound_assignment_evaluates_nested_locations_once :: proc(t: ^testing.T) {
text := `Box :: struct {
value i32
}
row_index usize = 0
column_index usize = 1
row func() usize {
return 0
return row_index
}
column func() usize {
return 1
return column_index
}
pointer_for func(value @mut i32) @mut i32 {
return value
@@ -13690,6 +13772,7 @@ contextual_inference_rejects_local_constant_that_does_not_fit :: proc(t: ^testin
contextual_inference_flows_through_numeric_arithmetic :: proc(t: ^testing.T) {
text := `take_u16 func(_ u16) void {}
take_f32 func(_ f32) void {}
runtime_seed f32 = 0
G :: 10
H u16 :: G + 2
GF :: 1.5
@@ -13700,7 +13783,7 @@ get func() f32 {
seed f32 :: 2.0
c :: seed + 3.0
d :: 4.0 + seed
return c + d
return c + d + runtime_seed
}
main func() void {
a :: 10