c interop type foundation

This commit is contained in:
2026-06-12 18:10:52 +02:00
parent d2f0d16795
commit 4e860b033e
27 changed files with 3523 additions and 380 deletions
+159 -22
View File
@@ -14,6 +14,7 @@ import "./compiler/lower"
import "./compiler/parser"
import "./compiler/source"
import "./compiler/symbol"
import "./compiler/target"
import "./compiler/token"
import "./compiler/types"
import "core:fmt"
@@ -86,9 +87,10 @@ main :: func() void { _ = value }
compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) {
testing.expect_value(t, size_of(source.Span), 12)
testing.expect_value(t, size_of(token.Token), 24)
testing.expect_value(t, size_of(ast.Expr), 64)
testing.expect_value(t, size_of(hir.Expr), 88)
testing.expect_value(t, size_of(ir.Instruction), 88)
testing.expect(t, size_of(ast.Expr) <= 64)
testing.expect(t, size_of(hir.Expr) <= 88)
testing.expect(t, size_of(ir.Instruction) <= 88)
testing.expect_value(t, size_of(types.Type), 4)
source_index, source_ok := source.source_index(source.Source_Id(0), 1)
testing.expect_value(t, source_index, 0)
@@ -195,8 +197,8 @@ main :: func() void { _ = give() }
@(test)
parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) {
text := `foreign :: c func(value i32) i32
defined :: c func(value i32) i32
text := `foreign :: c_func(value i32) i32
defined :: c_func(value i32) i32
{
return value
}
@@ -229,7 +231,7 @@ main :: func() void {}
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
text := `c :: 5
x :: c
foreign :: c func() i32
foreign :: c_func() i32
broken :: c 5
main :: func() void {}
`
@@ -403,6 +405,8 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
"thing",
"--link",
"helper.o",
"--target",
"aarch64-macos",
})
defer delete(options.link_arguments)
@@ -415,15 +419,18 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
testing.expect_value(t, options.link_arguments[1].kind, linker.Kind.Library_Path)
testing.expect_value(t, options.link_arguments[2].kind, linker.Kind.Library)
testing.expect_value(t, options.link_arguments[3].value, "helper.o")
testing.expect_value(t, target.name(options.target), "aarch64-macos")
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"})
_, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
_, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"})
testing.expect(t, !unknown_valid)
testing.expect(t, !incomplete_valid)
testing.expect(t, !duplicate_output_valid)
testing.expect(t, !duplicate_empty_output_valid)
testing.expect(t, !invalid_target)
}
@(test)
@@ -453,7 +460,7 @@ main :: func() void {}
}
@(test)
parser_rejects_chained_package_access :: proc(t: ^testing.T) {
parser_accepts_chained_field_access :: proc(t: ^testing.T) {
text := `main :: func() void {
_ = first.second.value
}
@@ -468,7 +475,7 @@ parser_rejects_chained_package_access :: proc(t: ^testing.T) {
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
testing.expect(t, len(diagnostics.items) > 0)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test)
@@ -566,7 +573,7 @@ main :: func() void {
@(test)
pipeline_emits_specialized_calling_conventions_and_checked_add :: proc(t: ^testing.T) {
text := `sum_c :: c func(a, b int) int {
text := `sum_c :: c_func(a, b int) int {
return a + b
}
sum_bro :: func(a, b int) int {
@@ -597,16 +604,16 @@ main :: func() void {
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, llvm_text, second_llvm_text)
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p0__sum_c__i8__i8"))
testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p0__sum_c__i8__i8"))
testing.expect(t, strings.contains(llvm_text, "define internal fastcc i8 @bro__p0__sum_bro__i8__i8"))
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
}
@(test)
pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) {
text := `used :: c func(a, b i32) i32
unused :: c func() i32
bodyful :: c func(value i32) i32 {
text := `used :: c_func(a, b i32) i32
unused :: c_func() i32
bodyful :: c_func(value i32) i32 {
return value
}
main :: func() void {
@@ -637,9 +644,126 @@ main :: func() void {
testing.expect(t, strings.contains(llvm_text, "define i32 @bro_c__p0__bodyful__i32"))
}
@(test)
c_primitives_remain_distinct_with_apple_silicon_representations :: proc(t: ^testing.T) {
testing.expect(t, types.C_CHAR != types.C_SCHAR)
testing.expect(t, types.C_SCHAR != types.C_UCHAR)
testing.expect(t, types.C_INT != types.I32)
testing.expect(t, types.C_ULONG != types.U64)
testing.expect_value(t, types.representation(types.C_CHAR), types.I8)
testing.expect_value(t, types.representation(types.C_SCHAR), types.I8)
testing.expect_value(t, types.representation(types.C_UCHAR), types.U8)
testing.expect_value(t, types.representation(types.C_SHORT), types.I16)
testing.expect_value(t, types.representation(types.C_USHORT), types.U16)
testing.expect_value(t, types.representation(types.C_INT), types.I32)
testing.expect_value(t, types.representation(types.C_UINT), types.U32)
testing.expect_value(t, types.representation(types.C_LONG), types.I64)
testing.expect_value(t, types.representation(types.C_ULONG), types.U64)
testing.expect_value(t, types.representation(types.C_LONGLONG), types.I64)
testing.expect_value(t, types.representation(types.C_ULONGLONG), types.U64)
testing.expect_value(t, types.representation(types.C_FLOAT), types.F32)
testing.expect_value(t, types.representation(types.C_DOUBLE), types.F64)
testing.expect_value(t, types.representation(types.C_LONGDOUBLE), types.F64)
testing.expect_value(t, target.llvm_triple(target.DEFAULT), "arm64-apple-macosx13.0.0")
}
@(test)
interop_foundation_emits_compounds_and_narrow_c_abi_attributes :: proc(t: ^testing.T) {
text := `Point :: struct {
x i32
y i32
}
signed :: c_func(value c_char) c_char
unsigned :: c_func(value c_uchar) c_uchar
exact :: c_func(value u32) u32
fallback :: func() i32 {
return 9
}
main :: func() void {
c :: 1
values [2;0]mut u8 = [1, 2]
point Point :: Point{x = 3, y = 4}
maybe ?i32 = 5
_ = c
_ = values[2]
_ = values.ptr + 1
_ = values.len
_ = values[0..2]
_ = "hello".ptr
_ = "hello".len
_ = point.x
_ = maybe?
_ = maybe orelse 0
_ = maybe orelse fallback()
_ = signed(1)
_ = unsigned(1)
_ = exact(1)
}
`
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)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, strings.contains(llvm_text, "target triple = \"arm64-apple-macosx13.0.0\""))
testing.expect(t, strings.contains(llvm_text, "declare signext i8 @signed(i8 signext)"))
testing.expect(t, strings.contains(llvm_text, "declare zeroext i8 @unsigned(i8 zeroext)"))
testing.expect(t, strings.contains(llvm_text, "declare i32 @exact(i32)"))
testing.expect(t, strings.contains(llvm_text, "@bro.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\""))
testing.expect(t, strings.contains(llvm_text, "getelementptr [3 x i8]"))
testing.expect(t, strings.contains(llvm_text, "attempted to unwrap none"))
testing.expect(t, strings.contains(llvm_text, "orelse_fallback"))
testing.expect(t, strings.contains(llvm_text, "orelse_some"))
}
@(test)
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
text := `Defined :: c_struct {
value c_int
}
Opaque :: c_struct
read :: c_func(value @Defined) c_int
bad_param :: c_func(value Defined) void
bad_result :: c_func() Defined
main :: func() void {}
`
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_param := false
found_result := false
for diagnostic in diagnostics.items {
found_param = found_param || strings.contains(diagnostic.message, "cannot be passed by value")
found_result = found_result || strings.contains(diagnostic.message, "cannot be returned by value")
}
testing.expect(t, found_param)
testing.expect(t, found_result)
}
@(test)
invalid_foreign_declarations_are_eagerly_diagnosed_and_calls_trap :: proc(t: ^testing.T) {
text := `bad :: c func(value int) int
text := `bad :: c_func(value int) int
native :: func() i32
main :: func() void {
_ = bad(1)
@@ -667,7 +791,7 @@ main :: func() void {
for diagnostic in diagnostics.items {
found_parameter = found_parameter || strings.contains(diagnostic.message, "requires concrete parameter types")
found_result = found_result || strings.contains(diagnostic.message, "requires a concrete or void result type")
found_native = found_native || strings.contains(diagnostic.message, "must use 'c func'")
found_native = found_native || strings.contains(diagnostic.message, "must use 'c_func'")
}
testing.expect(t, found_parameter)
testing.expect(t, found_result)
@@ -707,7 +831,7 @@ duplicate_foreign_symbols_across_packages_are_poisoned :: proc(t: ^testing.T) {
@(test)
bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) {
text := "main :: c func() i32\n"
text := "main :: c_func() i32\n"
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
@@ -974,7 +1098,7 @@ eager_global_calls_root_specializations :: proc(t: ^testing.T) {
unused_native :: func() i32 {
return 9
}
unused_foreign :: c func() i32
unused_foreign :: c_func() i32
value i32 :: make()
main :: func() void {}
`
@@ -1040,16 +1164,16 @@ long_generic_call_chain_reaches_a_fixed_point :: proc(t: ^testing.T) {
builder := strings.builder_make()
defer strings.builder_destroy(&builder)
for index in 0 ..< 70 {
fmt.sbprintf(&builder, "f%d :: func(value int) int ", index)
fmt.sbprintf(&builder, "fn%d :: func(value int) int ", index)
strings.write_string(&builder, "{ return ")
if index == 69 {
strings.write_string(&builder, "value")
} else {
fmt.sbprintf(&builder, "f%d(value)", index+1)
fmt.sbprintf(&builder, "fn%d(value)", index+1)
}
strings.write_string(&builder, " }\n")
}
strings.write_string(&builder, "main :: func() i32 { return f0(1) }\n")
strings.write_string(&builder, "main :: func() i32 { return fn0(1) }\n")
source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
diagnostics := source.init_diagnostics(&source_file)
@@ -1195,6 +1319,17 @@ foreign_function_links_from_c_source :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 42)
}
@(test)
interop_foundation_matches_zig_compiled_apple_silicon_c_fixture :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-interop-foundation"
defer _ = os.remove(output)
arguments := []linker.Argument{{kind=.Input, value="examples/interop/foundation/native.c"}}
status := compiler_core.compile_package("examples/interop/foundation", output, arguments)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 1)
}
@(test)
foreign_function_links_from_object :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-foreign-object"
@@ -1442,6 +1577,8 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
"/usr/bin/env",
"zig",
"cc",
"-target",
"aarch64-macos",
"-Wno-override-module",
"module.ll",
"native.c",
@@ -2127,8 +2264,8 @@ package_llvm_is_deterministic_and_symbols_include_package_ids :: proc(t: ^testin
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, llvm_text, second_llvm_text)
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p1__same"))
testing.expect(t, strings.contains(llvm_text, "define i8 @bro_c__p2__same"))
testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p1__same"))
testing.expect(t, strings.contains(llvm_text, "define signext i8 @bro_c__p2__same"))
testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
}