whole-function comptime folding for zero-runtime value calls
This commit is contained in:
+100
-17
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user