allocator interface (first pass)
This commit is contained in:
+256
-9
@@ -298,7 +298,7 @@ parser_accepts_native_function_pointer_types :: proc(t: ^testing.T) {
|
||||
text := `Error :: enum {
|
||||
bad
|
||||
}
|
||||
take func(callback ?*func(value i32) i32, fallible *func() i32 ! Error) void
|
||||
take func(callback ?@func(value i32) i32, fallible @func() i32 ! Error) void
|
||||
main func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -320,12 +320,12 @@ main func() void {}
|
||||
fallible, fallible_ok := types.node(&module.type_store, fallible_function.child)
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, optional_ok && optional.kind == .Optional)
|
||||
testing.expect(t, pointer_ok && pointer.kind == .Pointer && pointer.many && !pointer.mutable)
|
||||
testing.expect(t, pointer_ok && pointer.kind == .Pointer && !pointer.many && !pointer.mutable)
|
||||
testing.expect(t, function_ok && function.kind == .Function && !function.c_abi && !function.variadic)
|
||||
testing.expect(t, function.child == types.I32)
|
||||
testing.expect_value(t, len(params), 1)
|
||||
testing.expect(t, params[0].type == types.I32)
|
||||
testing.expect(t, fallible_pointer_ok && fallible_pointer.kind == .Pointer)
|
||||
testing.expect(t, fallible_pointer_ok && fallible_pointer.kind == .Pointer && !fallible_pointer.many)
|
||||
testing.expect(t, fallible_function_ok && fallible_function.kind == .Function && !fallible_function.c_abi)
|
||||
testing.expect(t, fallible_ok && fallible.kind == .Fallible && fallible.child == types.I32)
|
||||
}
|
||||
@@ -1526,11 +1526,12 @@ variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_structs_are_by_value_and_may_be_opaque :: proc(t: ^testing.T) {
|
||||
c_structs_are_by_value_and_bodyless_c_struct_uses_opaque :: proc(t: ^testing.T) {
|
||||
text := `Defined :: c_struct {
|
||||
value c_int
|
||||
}
|
||||
Opaque :: c_struct
|
||||
Opaque :: opaque
|
||||
Bodyless :: c_struct
|
||||
Empty :: c_struct {}
|
||||
Bad :: c_struct {
|
||||
values []i32
|
||||
@@ -1555,18 +1556,117 @@ main func() void {
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found_opaque := false
|
||||
found_bodyless := false
|
||||
found_bad_layout := false
|
||||
found_empty := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_opaque = found_opaque || strings.contains(diagnostic.message, "cannot be passed by value")
|
||||
found_bodyless = found_bodyless || strings.contains(diagnostic.message, "use 'opaque'")
|
||||
found_bad_layout = found_bad_layout || strings.contains(diagnostic.message, "C-layout-compatible")
|
||||
found_empty = found_empty || strings.contains(diagnostic.message, "at least one field")
|
||||
}
|
||||
testing.expect(t, found_opaque)
|
||||
testing.expect(t, found_bodyless)
|
||||
testing.expect(t, found_bad_layout)
|
||||
testing.expect(t, found_empty)
|
||||
}
|
||||
|
||||
@(test)
|
||||
opaque_anyopaque_and_ptr_cast_compile_and_lower :: proc(t: ^testing.T) {
|
||||
text := `Handle :: opaque
|
||||
take func(value ?*mut anyopaque) void {}
|
||||
use_handle func(handle ?@mut Handle) void {}
|
||||
main func() void {
|
||||
values [2]mut u8 = [1, 2]
|
||||
raw ?*mut anyopaque = (&values).ptr
|
||||
bytes ?*mut u8 = ptr_cast(u8, raw)
|
||||
take(bytes)
|
||||
if bytes |p| {
|
||||
p[1] = 5
|
||||
}
|
||||
|
||||
one u8 = 1
|
||||
single ?@mut anyopaque = &one
|
||||
typed ?@mut u8 = ptr_cast(u8, single)
|
||||
if typed |p| {
|
||||
p^ = 2
|
||||
}
|
||||
|
||||
handle ?@mut Handle = none
|
||||
use_handle(handle)
|
||||
}
|
||||
`
|
||||
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)
|
||||
|
||||
hir_casts := 0
|
||||
for expr in hir_module.exprs {
|
||||
hir_casts += 1 if expr.kind == .Pointer_Cast else 0
|
||||
}
|
||||
ir_casts := 0
|
||||
for function in ir_module.functions {
|
||||
for instruction in function.instructions {
|
||||
ir_casts += 1 if instruction.op == .Pointer_Cast else 0
|
||||
}
|
||||
}
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, hir_casts, 2)
|
||||
testing.expect_value(t, ir_casts, 2)
|
||||
}
|
||||
|
||||
@(test)
|
||||
anyopaque_by_value_and_invalid_ptr_casts_are_rejected :: proc(t: ^testing.T) {
|
||||
text := `Callback :: alias c_func() void
|
||||
main func() void {
|
||||
raw ?*mut anyopaque = none
|
||||
value anyopaque = undefined
|
||||
_ = ptr_cast(void, raw)
|
||||
_ = ptr_cast(anyopaque, raw)
|
||||
_ = ptr_cast(Callback, raw)
|
||||
_ = ptr_cast(u8, 1)
|
||||
_ = ptr_cast(1, raw)
|
||||
}
|
||||
`
|
||||
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_by_value := false
|
||||
found_bad_target := false
|
||||
found_bad_operand := false
|
||||
found_target_type := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_by_value = found_by_value || strings.contains(diagnostic.message, "could not infer a concrete type")
|
||||
found_bad_target = found_bad_target || strings.contains(diagnostic.message, "ptr_cast target must be a sized runtime object type")
|
||||
found_bad_operand = found_bad_operand || strings.contains(diagnostic.message, "ptr_cast operand must be a pointer")
|
||||
found_target_type = found_target_type || strings.contains(diagnostic.message, "ptr_cast target must be a type")
|
||||
}
|
||||
testing.expect(t, found_by_value)
|
||||
testing.expect(t, found_bad_target)
|
||||
testing.expect(t, found_bad_operand)
|
||||
testing.expect(t, found_target_type)
|
||||
}
|
||||
|
||||
@(test)
|
||||
aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) {
|
||||
text := `Small :: c_struct {
|
||||
@@ -2448,7 +2548,7 @@ main func() void {
|
||||
@(test)
|
||||
native_function_pointer_type_restrictions_are_diagnosed :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
callback *func(...) void = undefined
|
||||
callback @func(...) void = undefined
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
@@ -2812,6 +2912,140 @@ milestone_24_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
field_function_pointer_calls_lower_as_indirect_calls :: proc(t: ^testing.T) {
|
||||
text := `Callbacks :: struct {
|
||||
call @func(value i32) i32
|
||||
value i32
|
||||
}
|
||||
plus_one func(value i32) i32 {
|
||||
return value + 1
|
||||
}
|
||||
run func(callbacks Callbacks) i32 {
|
||||
return callbacks.call(callbacks.value)
|
||||
}
|
||||
main func() i32 {
|
||||
callbacks Callbacks = Callbacks { call = plus_one, value = 41 }
|
||||
return run(callbacks) - 42
|
||||
}
|
||||
`
|
||||
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)
|
||||
|
||||
indirect_calls := 0
|
||||
for expr in hir_module.exprs {
|
||||
if expr.kind == .Call && expr.left != hir.INVALID_EXPR {
|
||||
indirect_calls += 1
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, len(ir_module.functions) > 0)
|
||||
testing.expect(t, indirect_calls > 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
field_function_pointer_calls_reject_non_callable_fields :: proc(t: ^testing.T) {
|
||||
text := `Box :: struct {
|
||||
value i32
|
||||
}
|
||||
main func() void {
|
||||
box Box = Box { value = 1 }
|
||||
box.value()
|
||||
}
|
||||
`
|
||||
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, "call target is not a function pointer")
|
||||
}
|
||||
testing.expect(t, found)
|
||||
}
|
||||
|
||||
@(test)
|
||||
allocator_contract_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-mem-allocator"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/programs/mem_allocator", output, nil, target.DEFAULT, cimport.Options{}, ".")
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 0)
|
||||
}
|
||||
|
||||
@(test)
|
||||
allocator_contract_heap_global_lowers :: proc(t: ^testing.T) {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
ast_module, loaded := loader.load("examples/programs/mem_allocator", &sources, &diagnostics, &symbols, context.allocator, context.allocator, cimport.Options{}, target.DEFAULT, ".")
|
||||
defer ast.destroy_module(&ast_module)
|
||||
testing.expect(t, loaded)
|
||||
|
||||
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)
|
||||
|
||||
found_heap := false
|
||||
found_anyopaque_context := false
|
||||
found_alloc_callback := false
|
||||
found_free_callback := false
|
||||
for global in hir_module.globals {
|
||||
if symbol.resolve(&symbols, global.name) != "heap" {
|
||||
continue
|
||||
}
|
||||
found_heap = true
|
||||
for field in types.fields_for(&hir_module.types, global.type) {
|
||||
name := symbol.resolve(&symbols, symbol.Id(field.name))
|
||||
callback_pointer, _, _, callable := types.function_pointer(field.type, &hir_module.types)
|
||||
if name == "context" {
|
||||
optional_item, optional_ok := types.node(&hir_module.types, field.type)
|
||||
if optional_ok && optional_item.kind == .Optional {
|
||||
pointer_item, pointer_ok := types.node(&hir_module.types, optional_item.child)
|
||||
found_anyopaque_context = pointer_ok &&
|
||||
pointer_item.kind == .Pointer &&
|
||||
pointer_item.mutable &&
|
||||
pointer_item.many &&
|
||||
pointer_item.child == types.ANYOPAQUE
|
||||
}
|
||||
}
|
||||
found_alloc_callback = found_alloc_callback || name == "alloc" && callable && !callback_pointer.many
|
||||
found_free_callback = found_free_callback || name == "free" && callable && !callback_pointer.many
|
||||
}
|
||||
}
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect(t, len(ir_module.functions) > 0)
|
||||
testing.expect(t, found_heap)
|
||||
testing.expect(t, found_anyopaque_context)
|
||||
testing.expect(t, found_alloc_callback)
|
||||
testing.expect(t, found_free_callback)
|
||||
}
|
||||
|
||||
@(test)
|
||||
milestone_25_heap_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-heap"
|
||||
@@ -8571,7 +8805,7 @@ main func() i32 {
|
||||
|
||||
@(test)
|
||||
distinct_types_reject_implicit_conversions_operators_and_invalid_backings :: proc(t: ^testing.T) {
|
||||
text := `Opaque :: c_struct
|
||||
text := `Opaque :: opaque
|
||||
UserID :: distinct u32
|
||||
OtherID :: distinct u32
|
||||
BadInt :: distinct int
|
||||
@@ -8881,13 +9115,16 @@ translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) {
|
||||
result := cimport.init_result(context.allocator)
|
||||
|
||||
// type table: [0]=c_int, [1]=c_ulong, [2]=Record(Pair),
|
||||
// [3]=Function(c_int)->c_int, [4]=Pointer->Function (callback)
|
||||
// [3]=Function(c_int)->c_int, [4]=Pointer->Function (callback),
|
||||
// [5]=void, [6]=Pointer->void
|
||||
append(&result.types, cimport.Type{kind = .C_Int, child = cimport.INVALID_TYPE})
|
||||
append(&result.types, cimport.Type{kind = .C_Ulong, child = cimport.INVALID_TYPE})
|
||||
append(&result.types, cimport.Type{kind = .Record, record = 0, child = cimport.INVALID_TYPE})
|
||||
func_params := []cimport.Type_Id{cimport.Type_Id(0)}
|
||||
append(&result.types, cimport.Type{kind = .Function, params = func_params, child = cimport.Type_Id(0)})
|
||||
append(&result.types, cimport.Type{kind = .Pointer, child = cimport.Type_Id(3)})
|
||||
append(&result.types, cimport.Type{kind = .Void, child = cimport.INVALID_TYPE})
|
||||
append(&result.types, cimport.Type{kind = .Pointer, child = cimport.Type_Id(5), mutable = true})
|
||||
|
||||
// record 0: Pair { left c_int; right c_int }
|
||||
pair_fields: [dynamic]cimport.Field
|
||||
@@ -8900,13 +9137,20 @@ translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) {
|
||||
append(&choice_fields, cimport.Field{name = "tag", type = cimport.Type_Id(0)})
|
||||
append(&result.records, cimport.Record{name = "Choice", fields = choice_fields, kind = .Union, complete = true})
|
||||
|
||||
// typedef aliases: a scalar and a callback function pointer
|
||||
// record 2: incomplete struct -> opaque
|
||||
append(&result.records, cimport.Record{name = "Handle", kind = .Struct, complete = false})
|
||||
|
||||
// typedef aliases: a scalar, a callback function pointer, and a C void pointer
|
||||
append(&result.aliases, cimport.Alias{name = "Size", type = cimport.Type_Id(1)})
|
||||
append(&result.aliases, cimport.Alias{name = "Mapper", type = cimport.Type_Id(4)})
|
||||
append(&result.aliases, cimport.Alias{name = "RawPtr", type = cimport.Type_Id(6)})
|
||||
|
||||
add_params := []cimport.Type_Id{cimport.Type_Id(0), cimport.Type_Id(0)}
|
||||
add_param_names := []string{"a", "b"}
|
||||
append(&result.functions, cimport.Function{name = "imported_add", params = add_params, param_names = add_param_names, result = cimport.Type_Id(0)})
|
||||
raw_params := []cimport.Type_Id{cimport.Type_Id(6)}
|
||||
raw_param_names := []string{"ptr"}
|
||||
append(&result.functions, cimport.Function{name = "consume_raw", params = raw_params, param_names = raw_param_names, result = cimport.Type_Id(5)})
|
||||
|
||||
append(&result.macros, cimport.Macro_Constant{
|
||||
name = "MAX_LEN",
|
||||
@@ -8938,8 +9182,11 @@ translate_c_emits_native_bindings_and_round_trips :: proc(t: ^testing.T) {
|
||||
testing.expect(t, strings.contains(output, "Size :: alias c_ulong"))
|
||||
// Function-pointer types carry no parameter names, so the callback renders `_`.
|
||||
testing.expect(t, strings.contains(output, "Mapper :: alias ?*c_func(_ c_int) c_int"))
|
||||
testing.expect(t, strings.contains(output, "RawPtr :: alias ?*mut anyopaque"))
|
||||
testing.expect(t, strings.contains(output, "Handle :: opaque"))
|
||||
// Real C parameter names are used when present.
|
||||
testing.expect(t, strings.contains(output, "imported_add c_func(a c_int, b c_int) c_int"))
|
||||
testing.expect(t, strings.contains(output, "consume_raw c_func(ptr ?*mut anyopaque) void"))
|
||||
testing.expect(t, strings.contains(output, "MAX_LEN c_int :: 256"))
|
||||
testing.expect(t, strings.contains(output, "# unsupported in bindings: C union 'Choice'"))
|
||||
testing.expect(t, strings.contains(output, "# unsupported in bindings: external variable 'some_global'"))
|
||||
|
||||
Reference in New Issue
Block a user