From f5605fd3eceae72568ec75b7f5dda62f83fc0bd7 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Mon, 15 Jun 2026 21:09:46 +0200 Subject: [PATCH] function pointers and callbacks --- LANGUAGE.md | 15 +- README.md | 36 +- TODO.md | 18 +- compiler/checker/checker.odin | 465 +++++++++++++++- compiler/cimport/cimport.odin | 30 ++ compiler/cimport/libclang.odin | 164 +++++- compiler/hir/hir.odin | 1 + compiler/ir/ir.odin | 1 + compiler/llvm/llvm.odin | 524 +++++++++++++++++-- compiler/loader/loader.odin | 172 +++++- compiler/lower/lower.odin | 45 +- compiler/parser/parser.odin | 54 +- compiler/types/types.odin | 212 +++++++- compiler_tests.odin | 131 ++++- examples/interop/header/app/main.bro | 10 + examples/interop/header/include/native.h | 30 ++ examples/interop/header/native.c | 11 + examples/interop/header_unsupported/main.bro | 7 + examples/interop/records/app/main.bro | 46 ++ examples/interop/records/include/native.h | 53 ++ examples/interop/records/native.c | 24 + 21 files changed, 1927 insertions(+), 122 deletions(-) create mode 100644 examples/interop/records/app/main.bro create mode 100644 examples/interop/records/include/native.h create mode 100644 examples/interop/records/native.c diff --git a/LANGUAGE.md b/LANGUAGE.md index 97aeebd..9499a94 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -24,7 +24,9 @@ - information-preserving and information-forgetting pointer-to-array decay and sentinel slice/pointer weakening; array values never implicitly decay - narrow immutable zero-terminated byte pointer conversion to `*c_char` and `[*;0]c_char`, without general `u8`/`c_char` interchange - optionals with trapping postfix `?`, `orelse`, and nullable pointer representation -- source-order native structs, keyed literals, and defined or opaque pointer-only `c_struct` +- source-order native structs, defined or opaque `c_struct`, and keyed record literals +- complete plain imported C structs and unions as runtime values; incomplete or unsupported-layout records remain pointer-only +- C function pointer types as pointer-sized runtime values, including manual `*c_func(...) T` spelling and nullable imported callback typedefs - postfix pointer dereference, general writable locations, function calls, assignments, and returns ### functions and packages @@ -33,12 +35,13 @@ - bodyful `c_func` definitions using the c calling convention - bodyless `c_func` declarations with exact, globally unique external symbol names - concrete-only foreign signatures -- Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes +- Apple Silicon C ABI scalar, pointer, and fixed-signature plain record/union lowering, including narrow integer extension attributes - directory packages with merged declarations - file-local relative imports, aliases, and qualified member access - relative `.h` imports as synthetic package namespaces -- transitive external C function prototypes, typedef chains, C scalars, and pointers to opaque C records +- transitive external C function prototypes, typedef chains, C scalars, fixed arrays, complete plain records/unions, and pointers to opaque C records - bodyless manual and imported C variadic declarations with target-aware default argument promotions +- passing concrete `c_func` declarations/definitions as C callback values and calling non-null C function pointers with postfix call syntax - reference-time diagnostics for unsupported imported C declarations ### compiler behavior @@ -60,9 +63,9 @@ ### scalar and compound types - tuples and native variadic functions -- C unions, C enums, and by-value C record ABI lowering +- C enums and non-plain C record layouts ### advanced c imports -- C enums, external variables, function pointers, callbacks, macros, and static inline functions -- target-specific by-value C record and union ABI lowering +- C enums, external variables, macros, and static inline functions +- additional target-specific C ABI lowering diff --git a/README.md b/README.md index 0fcb783..215e25c 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,38 @@ main :: func() void { } ``` -Header imports expose supported external functions, typedefs, C scalars, and -pointers to opaque records. They never add linker inputs; implementations must -still be supplied explicitly with the C-prefixed linking options. Set +Header imports expose supported external functions, typedefs, C scalars, fixed +arrays, complete plain structs and unions, function pointer typedefs, and +pointers to opaque records. Plain records can be constructed with keyed +literals, accessed by field, and passed or returned by value through fixed C +signatures on `aarch64-macos`. Unsupported or incomplete records remain +pointer-only. Header imports never add linker inputs; implementations must still +be supplied explicitly with the C-prefixed linking options. Set `BROLANG_LIBCLANG_PATH` when libclang is not installed in a standard location. +```bro +native :: import "../include/native.h" + +pair native.Pair :: native.echo_pair(native.Pair { left = 20, right = 22 }) +choice native.Choice :: native.Choice { integer = 42 } +``` + +Concrete `c_func` declarations and definitions can be passed to C function +pointer parameters. Imported C callback typedefs are nullable, so calling one +from Brolang requires an explicit unwrap: + +```bro +native :: import "../include/native.h" + +double :: c_func(value c_int) c_int { + return value + value +} + +call_mapper :: func(mapper native.Imported_Mapper) c_int { + return mapper?(21) +} +``` + Bodyless manual and imported C functions may be variadic: ```bro @@ -97,13 +124,14 @@ Current prototype features: - `#` comments - Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks - Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` -- Target-dependent atomic `c_*` primitive types, `c_func`, and pointer-only `c_struct` +- Target-dependent atomic `c_*` primitive types, `c_func`, and defined or opaque `c_struct` - Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs - String literals as immutable pointers to static zero-terminated byte arrays - Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals - Contextual integer constants and compile-time folding of addition and unary negation trees - Directory packages with merged declarations and file-local relative imports - Relative C header imports as synthetic package namespaces +- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering - Qualified imported globals and functions with package-aware symbol mangling - Demand-monomorphized Brolang and C-ABI functions - Bodyless concrete C function declarations with exact external symbol names diff --git a/TODO.md b/TODO.md index 954af59..237ef0f 100644 --- a/TODO.md +++ b/TODO.md @@ -27,7 +27,7 @@ - pointer-only `c_struct` support with target c layout - `Some :: c_struct { ... }`: defined c-layout struct - `Some :: c_struct`: opaque c-layout struct - - defer passing c structs by value until target ABI classification exists + - passing c structs by value was deferred until milestone 4.1 2. restricted c header imports (implemented) - treat an imported header as a synthetic, file-local package namespace @@ -55,8 +55,20 @@ - reject general `u8`/`c_char` interchange, slice-to-pointer coercion, and conversion to mutable c character pointers 4. advanced c interop - - by-value records and unions - - function pointers and callbacks + - by-value records and unions (implemented) + - complete plain imported structs/unions and manual `c_struct` values + - fixed C arrays inside imported records + - keyed struct literals and exactly-one-field union literals + - field reads/writes, storage, and fixed-signature calls/returns + - aarch64-macos small aggregate, homogeneous float aggregate, and indirect ABI lowering + - keep incomplete, bitfield, packed, flexible-array, qualified-field, and otherwise non-plain records pointer-only + - keep C variadic record arguments unsupported + - function pointers and callbacks (implemented) + - imported C function pointer typedefs lower to nullable pointer types + - manual `?*c_func(...) T` callback type spelling + - concrete `c_func` declarations/definitions can be passed as callback values + - postfix calls through non-null function pointers, including `callback?(...)` + - fixed and C-variadic callback ABI emission through LLVM indirect calls - external variables - macros and static inline functions - exporting brolang functions to c diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index facf6f9..9793f09 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -489,6 +489,71 @@ call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type { return type_from_syntax(function.params[index].type) } +callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type { + if index < 0 || index >= int(function_item.field_count) { + return types.INVALID + } + params := types.params_for(store, function_type) + if index >= len(params) { + return types.INVALID + } + return params[index].type +} + +valid_callable_arity :: proc(function_item: types.Node, count: int) -> bool { + return count >= int(function_item.field_count) if function_item.variadic else count == int(function_item.field_count) +} + +function_value_signature :: proc( + checker: ^Checker, + template: ast.Function_Id, +) -> (params: []types.Type, result: types.Type, ok: bool) { + if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { + return nil, types.INVALID, false + } + function := checker.ast_module.functions[template] + if !function.c_abi { + return nil, types.INVALID, false + } + result = type_from_syntax(function.result) + if !types.is_void(result) && !is_runtime_type(checker, result) { + return nil, types.INVALID, false + } + params = make([]types.Type, len(function.params), checker.allocator) + for param, index in function.params { + param_type := type_from_syntax(param.type) + if !is_runtime_type(checker, param_type) { + delete(params, checker.allocator) + return nil, types.INVALID, false + } + params[index] = param_type + } + return params, result, true +} + +function_pointer_type_for_template :: proc( + checker: ^Checker, + template: ast.Function_Id, + demanded: ^[dynamic]Spec_Id = nil, +) -> (types.Type, Spec_Id, bool) { + params, result, ok := function_value_signature(checker, template) + if !ok { + return types.INVALID, INVALID_SPEC, false + } + defer delete(params, checker.allocator) + function := checker.ast_module.functions[template] + function_type := types.function(&checker.module.types, params, result, true, function.variadic) + pointer_type := types.pointer(&checker.module.types, function_type, false, true) + spec := INVALID_SPEC + if demanded == nil { + spec = ensure_spec(checker, template, params) + } else { + spec = find_spec(checker, template, params) + mark_spec_demanded(checker, spec, demanded) + } + return pointer_type, spec, spec != INVALID_SPEC +} + contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool { for existing in names { if existing == name { @@ -518,6 +583,9 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as switch expr.kind { case .Call: append(&stack, ..expr.args) + if expr.left != ast.INVALID_EXPR { + append(&stack, expr.left) + } case .Array, .Struct_Literal, .Slice: append(&stack, ..expr.args) if expr.left != ast.INVALID_EXPR { @@ -600,11 +668,15 @@ validate_declarations :: proc(checker: ^Checker) { } if !function.has_body && function.c_abi { for param in function.params { - if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) != + param_type := type_from_syntax(param.type) + if add_unsupported_type_diagnostic(checker, param.span, param_type) != source.INVALID_DIAGNOSTIC { continue } - if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) { + if types.contains_c_struct_by_value(param_type, &checker.module.types) { + continue + } + if !types.is_c_signature_type(param_type, &checker.module.types) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, param.span, @@ -615,6 +687,7 @@ validate_declarations :: proc(checker: ^Checker) { } result := type_from_syntax(function.result) if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC && + !types.contains_c_struct_by_value(result, &checker.module.types) && !types.is_c_signature_type(result, &checker.module.types, true) { checker.template_diagnostics[function_id] = source.addf( checker.diagnostics, @@ -683,17 +756,43 @@ validate_type_nodes :: proc(checker: ^Checker) { ) } } - if item.kind == .Struct { + if item.kind == .Struct || item.kind == .Union { + if item.c_layout && !item.opaque && item.field_count == 0 { + source.add( + checker.diagnostics, + source.Span{}, + "c_struct definitions require at least one field", + ) + } for field in types.fields_for(&checker.module.types, id) { - if types.contains_c_struct_by_value(field.type, &checker.module.types) { + if !types.is_runtime_value(field.type, &checker.module.types) { source.add( checker.diagnostics, source.Span{}, - "C records may only appear behind pointers", + "record fields must have runtime value types", + ) + } else if item.c_layout && !types.is_c_record_field_type(field.type, &checker.module.types) { + source.add( + checker.diagnostics, + source.Span{}, + "c_struct fields must have C-layout-compatible types", ) } } } + if item.kind == .Function { + if !item.c_abi { + source.add(checker.diagnostics, source.Span{}, "only c_func function pointer types are supported") + } + for param in types.params_for(&checker.module.types, id) { + if types.is_void(param.type) || !types.is_c_signature_type(param.type, &checker.module.types) { + source.add(checker.diagnostics, source.Span{}, "function pointer parameters must be concrete C signature types") + } + } + if !types.is_c_signature_type(item.child, &checker.module.types, true) { + source.add(checker.diagnostics, source.Span{}, "function pointer results must be concrete C signature types or void") + } + } } } @@ -893,7 +992,8 @@ infer_compound_expr :: proc( _ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded) } target_pkg, available := expr_package(checker, expr, pkg, file) - return types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID + value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID + return types.resolve_alias(value, store) case .Keyed: return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) case: @@ -994,6 +1094,20 @@ infer_expr :: proc( } } } + if !types.is_valid(last) { + target_pkg, available := expr_package(checker, expr, pkg, file) + if available { + template := find_template(checker, expr.name, target_pkg) + if template != ast.INVALID_FUNCTION && + len(checker.ast_module.functions[template].unsupported_reason) == 0 && + checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC { + pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded) + if ok { + last = pointer_type + } + } + } + } _ = pop(&stack) case .Negate: stack[frame_index].stage = 5 @@ -1002,14 +1116,60 @@ infer_expr :: proc( stack[frame_index].stage = 1 append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION}) case .Call: + if expr.left != ast.INVALID_EXPR { + callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) + _, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) + if !ok { + last = types.INVALID + _ = pop(&stack) + continue + } + stack[frame_index].left = function_type + stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator) + stack[frame_index].stage = 6 + if len(expr.args) > 0 { + append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION}) + } else if valid_callable_arity(function_item, 0) { + last = function_item.child + delete(stack[frame_index].args, checker.allocator) + stack[frame_index].args = nil + _ = pop(&stack) + } + continue + } target_pkg, available := expr_package(checker, expr, pkg, file) template := ast.INVALID_FUNCTION if available { template = find_template(checker, expr.name, target_pkg) } if template == ast.INVALID_FUNCTION { - last = types.INVALID - _ = pop(&stack) + callee_type := types.INVALID + if !symbol.is_valid(expr.qualifier) { + callee_type = find_infer_local(locals, expr.name) + } + if !types.is_valid(callee_type) && available { + global := find_global(checker, expr.name, target_pkg) + if global != ast.INVALID_GLOBAL { + callee_type = checker.global_types[global] + } + } + _, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) + if !ok { + last = types.INVALID + _ = pop(&stack) + continue + } + stack[frame_index].left = function_type + stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator) + stack[frame_index].stage = 6 + if len(expr.args) > 0 { + append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION}) + } else if valid_callable_arity(function_item, 0) { + last = function_item.child + delete(stack[frame_index].args, checker.allocator) + stack[frame_index].args = nil + _ = pop(&stack) + } continue } if len(checker.ast_module.functions[template].unsupported_reason) > 0 { @@ -1091,6 +1251,25 @@ infer_expr :: proc( stack[frame_index].args = nil _ = pop(&stack) } + if frame.stage == 6 { + if frame.arg_index < len(expr.args) { + stack[frame_index].args[frame.arg_index] = last + stack[frame_index].arg_index += 1 + if frame.arg_index+1 < len(expr.args) { + append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION}) + continue + } + } + function_item, ok := types.node(&checker.module.types, frame.left) + if ok && function_item.kind == .Function && valid_callable_arity(function_item, len(expr.args)) { + last = function_item.child + } else { + last = types.INVALID + } + delete(stack[frame_index].args, checker.allocator) + stack[frame_index].args = nil + _ = pop(&stack) + } } return last } @@ -1548,6 +1727,65 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb return 0, {}, false } +build_function_value :: proc( + checker: ^Checker, + template: ast.Function_Id, + span: source.Span, + expected: types.Type, +) -> hir.Expr_Id { + if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) { + return hir.INVALID_EXPR + } + function := checker.ast_module.functions[template] + if len(function.unsupported_reason) > 0 { + id := source.addf( + checker.diagnostics, + span, + "C declaration '%s' is unavailable: %s", + symbol_text(checker, function.name), + function.unsupported_reason, + ) + return invalid_hir_expr(checker, span, id) + } + if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC { + return invalid_hir_expr(checker, span, checker.template_diagnostics[template]) + } + params, result, ok := function_value_signature(checker, template) + if !ok { + id := source.addf( + checker.diagnostics, + span, + "function '%s' cannot be used as a C callback; expected a concrete c_func", + symbol_text(checker, function.name), + ) + return invalid_hir_expr(checker, span, id) + } + defer delete(params, checker.allocator) + function_type := types.function(&checker.module.types, params, result, true, function.variadic) + pointer_type := types.pointer(&checker.module.types, function_type, false, true) + spec := find_spec(checker, template, params) + if spec == INVALID_SPEC { + id := source.addf( + checker.diagnostics, + span, + "could not resolve callback specialization of '%s'", + symbol_text(checker, function.name), + ) + return invalid_hir_expr(checker, span, id, pointer_type) + } + function_id := checker.specs[spec].hir_id + assert(function_id != hir.INVALID_FUNCTION) + return add_hir_expr(checker, hir.Expr{ + kind=.Function, + span=span, + type=pointer_type, + target=hir.function_ref(function_id), + left=hir.INVALID_EXPR, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) +} + build_nested_expr :: proc( checker: ^Checker, expr_id: ast.Expr_Id, @@ -1776,16 +2014,18 @@ build_compound_expr :: proc( case .Struct_Literal: target_pkg, available := expr_package(checker, expr, pkg, file, true) struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID - if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) { - id := source.addf(checker.diagnostics, expr.span, "unknown or opaque struct type '%s'", symbol_text(checker, expr.name)) - return invalid_hir_expr(checker, expr.span, id) - } - if types.is_c_struct(struct_type, store) { - id := source.add(checker.diagnostics, expr.span, "c_struct values cannot be constructed by value") + struct_type = types.resolve_alias(struct_type, store) + if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) { + id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name)) return invalid_hir_expr(checker, expr.span, id) } fields := types.fields_for(store, struct_type) - values := make([]hir.Expr_Id, len(fields), checker.allocator) + union_record := types.is_union(struct_type, store) + if union_record && len(expr.args) != 1 { + id := source.add(checker.diagnostics, expr.span, "union literal requires exactly one field initializer") + return invalid_hir_expr(checker, expr.span, id, struct_type) + } + values := make([]hir.Expr_Id, 1 if union_record else len(fields), checker.allocator) initialized := make([]bool, len(fields), checker.allocator) defer delete(initialized, checker.allocator) for &value in values { @@ -1803,18 +2043,35 @@ build_compound_expr :: proc( continue } initialized[index] = true - values[index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file) - values[index] = coerce_expr(checker, values[index], field.type, keyed_expr.span) + value_index := 0 if union_record else index + values[value_index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file) + values[value_index] = coerce_expr(checker, values[value_index], field.type, keyed_expr.span) } - for field, index in fields { - if values[index] == hir.INVALID_EXPR { + if !union_record { + for field, index in fields { + if values[index] != hir.INVALID_EXPR { + continue + } id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name))) delete(values, checker.allocator) return invalid_hir_expr(checker, expr.span, id, struct_type) } } + active_field: i64 + if union_record { + if values[0] == hir.INVALID_EXPR { + delete(values, checker.allocator) + return invalid_hir_expr(checker, expr.span, source.add(checker.diagnostics, expr.span, "union literal requires a known field"), struct_type) + } + for value, index in initialized { + if value { + active_field = i64(index) + break + } + } + } return add_hir_expr(checker, hir.Expr{ - kind=.Struct, span=expr.span, type=struct_type, args=values, + kind=.Struct, span=expr.span, type=struct_type, args=values, integer=active_field, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) @@ -1938,11 +2195,16 @@ build_expr :: proc( target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, }) } else { - id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) - if id == source.INVALID_DIAGNOSTIC { - id = add_name_resolution_diagnostic(checker, expr, target_pkg) + template := find_template(checker, expr.name, target_pkg) + if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi { + last = build_function_value(checker, template, expr.span, frame.expected) + } else { + id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) + if id == source.INVALID_DIAGNOSTIC { + id = add_name_resolution_diagnostic(checker, expr, target_pkg) + } + last = invalid_hir_expr(checker, expr.span, id) } - last = invalid_hir_expr(checker, expr.span, id) } } _ = pop(&stack) @@ -1953,6 +2215,11 @@ build_expr :: proc( stack[frame_index].stage = 1 append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) case .Call: + if expr.left != ast.INVALID_EXPR { + stack[frame_index].stage = 6 + append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) + continue + } target_pkg, available := expr_package(checker, expr, pkg, file, true) if !available { id := add_package_resolution_diagnostic(checker, expr, file) @@ -1962,12 +2229,63 @@ build_expr :: proc( } template := find_template(checker, expr.name, target_pkg) if template == ast.INVALID_FUNCTION { - id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) - if id == source.INVALID_DIAGNOSTIC { - id = add_call_resolution_diagnostic(checker, expr, target_pkg) + callee := hir.INVALID_EXPR + callee_from_global := false + if !symbol.is_valid(expr.qualifier) { + if local, ok := find_build_local(locals, expr.name); ok { + callee = add_hir_expr(checker, hir.Expr{ + kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id), + left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + } + if callee == hir.INVALID_EXPR { + if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL { + hir_global := hir.Global_Id(global) + add_unique_global(global_reads, hir_global) + callee = add_hir_expr(checker, hir.Expr{ + kind=.Global, span=expr.span, type=checker.global_types[global], + target=hir.global_ref(hir_global), left=hir.INVALID_EXPR, + right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, + }) + callee_from_global = true + } + } + if callee == hir.INVALID_EXPR { + id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) + if id == source.INVALID_DIAGNOSTIC { + id = add_call_resolution_diagnostic(checker, expr, target_pkg) + } + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + _, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types) + if !ok { + id := add_call_resolution_diagnostic(checker, expr, target_pkg) if callee_from_global else + source.add(checker.diagnostics, expr.span, "call target is not a function pointer") + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + if !valid_callable_arity(function_item, len(expr.args)) { + message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else + "function pointer expects %d arguments, got %d" + id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args)) + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + stack[frame_index].left = callee + stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator) + stack[frame_index].stage = 7 + if len(expr.args) > 0 { + arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0) + if !is_runtime_type(checker, arg_expected) { + arg_expected = types.INVALID + } + append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION}) } - last = invalid_hir_expr(checker, expr.span, id) - _ = pop(&stack) continue } if len(checker.ast_module.functions[template].unsupported_reason) > 0 { @@ -2143,6 +2461,95 @@ build_expr :: proc( } _ = pop(&stack) } + if frame.stage == 6 { + callee := last + _, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types) + if !ok { + id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer") + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + if !valid_callable_arity(function_item, len(expr.args)) { + message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else + "function pointer expects %d arguments, got %d" + id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args)) + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + stack[frame_index].left = callee + stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator) + stack[frame_index].stage = 7 + if len(expr.args) > 0 { + arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0) + if !is_runtime_type(checker, arg_expected) { + arg_expected = types.INVALID + } + append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION}) + } + continue + } + if frame.stage == 7 { + if frame.arg_index < len(expr.args) { + stack[frame_index].built_args[frame.arg_index] = last + stack[frame_index].arg_index += 1 + if frame.arg_index+1 < len(expr.args) { + next := frame.arg_index+1 + callee_type := checker.module.exprs[frame.left].type + _, function_item, function_type, _ := types.function_pointer(callee_type, &checker.module.types) + arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next) + if !is_runtime_type(checker, arg_expected) { + arg_expected = types.INVALID + } + append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION}) + continue + } + } + callee_type := checker.module.exprs[frame.left].type + _, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types) + if !ok { + id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer") + delete(stack[frame_index].built_args, checker.allocator) + stack[frame_index].built_args = nil + last = invalid_hir_expr(checker, expr.span, id) + _ = pop(&stack) + continue + } + fixed_count := int(function_item.field_count) + for index in 0.. Result { } destroy_result :: proc(result: ^Result) { + for type_item in result.types { + delete(type_item.params, result.allocator) + } for record in result.records { delete(record.name, result.allocator) delete(record.identity, result.allocator) + for field in record.fields { + delete(field.name, result.allocator) + } + delete(record.fields) + delete(record.reason, result.allocator) } for alias in result.aliases { delete(alias.name, result.allocator) diff --git a/compiler/cimport/libclang.odin b/compiler/cimport/libclang.odin index eba95b8..22bf4d5 100644 --- a/compiler/cimport/libclang.odin +++ b/compiler/cimport/libclang.odin @@ -43,16 +43,25 @@ Api :: struct { get_cursor_spelling: proc "c"(CXCursor) -> CXString, get_cursor_usr: proc "c"(CXCursor) -> CXString, get_cursor_linkage: proc "c"(CXCursor) -> i32, + get_cursor_definition: proc "c"(CXCursor) -> CXCursor, + is_cursor_definition: proc "c"(CXCursor) -> u32, get_cursor_type: proc "c"(CXCursor) -> CXType, get_typedef_underlying_type: proc "c"(CXCursor) -> CXType, get_type_declaration: proc "c"(CXType) -> CXCursor, get_canonical_type: proc "c"(CXType) -> CXType, get_pointee_type: proc "c"(CXType) -> CXType, + get_array_element_type: proc "c"(CXType) -> CXType, + get_array_size: proc "c"(CXType) -> i64, get_result_type: proc "c"(CXType) -> CXType, get_num_arg_types: proc "c"(CXType) -> i32, get_arg_type: proc "c"(CXType, u32) -> CXType, + is_function_type_variadic: proc "c"(CXType) -> u32, is_const_qualified_type: proc "c"(CXType) -> u32, is_volatile_qualified_type: proc "c"(CXType) -> u32, + cursor_is_bitfield: proc "c"(CXCursor) -> u32, + cursor_get_offset_of_field: proc "c"(CXCursor) -> i64, + type_get_size_of: proc "c"(CXType) -> i64, + type_get_align_of: proc "c"(CXType) -> i64, cursor_is_variadic: proc "c"(CXCursor) -> u32, get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32, get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic, @@ -70,6 +79,7 @@ CXCursor_FunctionDecl :: i32(8) CXCursor_VarDecl :: i32(9) CXCursor_TypedefDecl :: i32(20) CXCursor_MacroDefinition :: i32(501) +CXCursor_FieldDecl :: i32(6) CXLinkage_External :: i32(4) @@ -97,6 +107,10 @@ CXType_Enum :: i32(106) CXType_Typedef :: i32(107) CXType_FunctionNoProto :: i32(110) CXType_FunctionProto :: i32(111) +CXType_ConstantArray :: i32(112) +CXType_IncompleteArray :: i32(114) +CXType_VariableArray :: i32(115) +CXType_DependentSizedArray :: i32(116) CXType_Elaborated :: i32(119) CXType_Attributed :: i32(163) @@ -135,16 +149,25 @@ load_api_from :: proc(path: string) -> (Api, bool) { load_proc(&api, "clang_getCursorSpelling", &api.get_cursor_spelling) && load_proc(&api, "clang_getCursorUSR", &api.get_cursor_usr) && load_proc(&api, "clang_getCursorLinkage", &api.get_cursor_linkage) && + load_proc(&api, "clang_getCursorDefinition", &api.get_cursor_definition) && + load_proc(&api, "clang_isCursorDefinition", &api.is_cursor_definition) && load_proc(&api, "clang_getCursorType", &api.get_cursor_type) && load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) && load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) && load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) && load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) && + load_proc(&api, "clang_getArrayElementType", &api.get_array_element_type) && + load_proc(&api, "clang_getArraySize", &api.get_array_size) && load_proc(&api, "clang_getResultType", &api.get_result_type) && load_proc(&api, "clang_getNumArgTypes", &api.get_num_arg_types) && load_proc(&api, "clang_getArgType", &api.get_arg_type) && + load_proc(&api, "clang_isFunctionTypeVariadic", &api.is_function_type_variadic) && load_proc(&api, "clang_isConstQualifiedType", &api.is_const_qualified_type) && load_proc(&api, "clang_isVolatileQualifiedType", &api.is_volatile_qualified_type) && + load_proc(&api, "clang_Cursor_isBitField", &api.cursor_is_bitfield) && + load_proc(&api, "clang_Cursor_getOffsetOfField", &api.cursor_get_offset_of_field) && + load_proc(&api, "clang_Type_getSizeOf", &api.type_get_size_of) && + load_proc(&api, "clang_Type_getAlignOf", &api.type_get_align_of) && load_proc(&api, "clang_Cursor_isVariadic", &api.cursor_is_variadic) && load_proc(&api, "clang_getNumDiagnostics", &api.get_num_diagnostics) && load_proc(&api, "clang_getDiagnostic", &api.get_diagnostic) && @@ -228,10 +251,99 @@ add_record :: proc(ctx: ^Context, declaration: CXCursor, preferred_name: string) name = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator) } index := u32(len(ctx.result.records)) - append(&ctx.result.records, Record{name=name, identity=identity}) + append(&ctx.result.records, Record{ + name=name, + identity=identity, + kind=.Union if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl else .Struct, + reason=fmt.aprintf("", allocator=ctx.allocator), + }) + ctx.result.records[index].fields.allocator = ctx.allocator return index } +Record_Field_Context :: struct { + ctx: ^Context, + record: u32, +} + +visit_record_field :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 { + context = runtime.default_context() + field_ctx := (^Record_Field_Context)(client_data) + ctx := field_ctx.ctx + if ctx.api.get_cursor_kind(cursor) != CXCursor_FieldDecl { + return CXChildVisit_Continue + } + if len(ctx.result.records[field_ctx.record].reason) > 0 { + return CXChildVisit_Continue + } + name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator) + if len(name) == 0 { + delete(name, ctx.allocator) + delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) + ctx.result.records[field_ctx.record].reason = fmt.aprintf("anonymous C record fields are not supported", allocator=ctx.allocator) + return CXChildVisit_Continue + } + field_type := ctx.api.get_cursor_type(cursor) + if ctx.api.cursor_is_bitfield(cursor) != 0 { + delete(name, ctx.allocator) + delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) + ctx.result.records[field_ctx.record].reason = fmt.aprintf("C bitfields are not supported", allocator=ctx.allocator) + return CXChildVisit_Continue + } + if ctx.api.is_const_qualified_type(field_type) != 0 || + ctx.api.is_volatile_qualified_type(field_type) != 0 { + delete(name, ctx.allocator) + delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) + ctx.result.records[field_ctx.record].reason = fmt.aprintf("qualified C record fields are not supported", allocator=ctx.allocator) + return CXChildVisit_Continue + } + if field_type.kind == CXType_IncompleteArray || + field_type.kind == CXType_VariableArray || + field_type.kind == CXType_DependentSizedArray { + delete(name, ctx.allocator) + delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) + ctx.result.records[field_ctx.record].reason = fmt.aprintf("flexible or variable C array fields are not supported", allocator=ctx.allocator) + return CXChildVisit_Continue + } + translated := translate_type(ctx, field_type) + offset_bits := ctx.api.cursor_get_offset_of_field(cursor) + if translated == INVALID_TYPE || offset_bits < 0 || offset_bits%8 != 0 { + delete(name, ctx.allocator) + delete(ctx.result.records[field_ctx.record].reason, ctx.allocator) + ctx.result.records[field_ctx.record].reason = fmt.aprintf("C record field type or layout is not supported", allocator=ctx.allocator) + return CXChildVisit_Continue + } + append(&ctx.result.records[field_ctx.record].fields, Field{name=name, type=translated, offset=u64(offset_bits/8)}) + return CXChildVisit_Continue +} + +populate_record :: proc(ctx: ^Context, index: u32, declaration: CXCursor) { + if ctx.result.records[index].complete || len(ctx.result.records[index].reason) > 0 { + return + } + definition := declaration + if ctx.api.is_cursor_definition(definition) == 0 { + definition = ctx.api.get_cursor_definition(declaration) + } + if ctx.api.is_cursor_definition(definition) == 0 { + return + } + record_type := ctx.api.get_cursor_type(definition) + size := ctx.api.type_get_size_of(record_type) + alignment := ctx.api.type_get_align_of(record_type) + if size < 0 || alignment <= 0 { + delete(ctx.result.records[index].reason, ctx.allocator) + ctx.result.records[index].reason = fmt.aprintf("C record size or alignment is not supported", allocator=ctx.allocator) + return + } + ctx.result.records[index].kind = .Union if ctx.api.get_cursor_kind(definition) == CXCursor_UnionDecl else .Struct + ctx.result.records[index].size = u64(size) + ctx.result.records[index].alignment = u32(alignment) + field_ctx := Record_Field_Context{ctx=ctx, record=index} + _ = ctx.api.visit_children(definition, visit_record_field, &field_ctx) + ctx.result.records[index].complete = len(ctx.result.records[index].reason) == 0 +} + translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := "", depth := 0) -> Type_Id { if depth > 64 || value.kind == CXType_Invalid || ctx.api.is_volatile_qualified_type(value) != 0 { return INVALID_TYPE @@ -259,18 +371,52 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := "" if child == INVALID_TYPE { return INVALID_TYPE } + mutable := ctx.api.is_const_qualified_type(pointee) == 0 + if pointee.kind == CXType_FunctionProto { + mutable = false + } return add_type(ctx, Type{ kind=.Pointer, child=child, - mutable=ctx.api.is_const_qualified_type(pointee) == 0, + mutable=mutable, }) - case CXType_Record: - declaration := ctx.api.get_type_declaration(value) - if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl { + case CXType_ConstantArray: + count := ctx.api.get_array_size(value) + child := translate_type(ctx, ctx.api.get_array_element_type(value), "", depth+1) + if count <= 0 || child == INVALID_TYPE { return INVALID_TYPE } + return add_type(ctx, Type{kind=.Array, child=child, count=u64(count), mutable=true}) + case CXType_Record: + declaration := ctx.api.get_type_declaration(value) record := add_record(ctx, declaration, preferred_record_name) + populate_record(ctx, record, declaration) return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) + case CXType_FunctionProto: + result := translate_type(ctx, ctx.api.get_result_type(value), "", depth+1) + if result == INVALID_TYPE { + return INVALID_TYPE + } + count := ctx.api.get_num_arg_types(value) + if count < 0 { + return INVALID_TYPE + } + params: [dynamic]Type_Id + params.allocator = ctx.allocator + for index in 0.. i32 { reason = "typedef underlying type is not supported" } add_alias(ctx, name, value, reason) - case CXCursor_StructDecl: + case CXCursor_StructDecl, CXCursor_UnionDecl: if len(name) > 0 { record := add_record(ctx, cursor, name) + populate_record(ctx, record, cursor) value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) add_alias(ctx, name, value) } - case CXCursor_UnionDecl: - add_unsupported(ctx, name, "C unions are not supported") case CXCursor_EnumDecl: add_unsupported(ctx, name, "C enums are not supported") case CXCursor_VarDecl: diff --git a/compiler/hir/hir.odin b/compiler/hir/hir.odin index d135aa4..352378c 100644 --- a/compiler/hir/hir.odin +++ b/compiler/hir/hir.odin @@ -83,6 +83,7 @@ Expr_Kind :: enum u8 { Optional_Some, Local, Global, + Function, Address, Deref, Index, diff --git a/compiler/ir/ir.odin b/compiler/ir/ir.odin index 30c7431..ecb1a60 100644 --- a/compiler/ir/ir.odin +++ b/compiler/ir/ir.odin @@ -73,6 +73,7 @@ Opcode :: enum u8 { None, Optional_Some, Load_Global, + Function_Address, Address_Global, Address_Of, Alloca, diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin index 3289d11..3d06bc4 100644 --- a/compiler/llvm/llvm.odin +++ b/compiler/llvm/llvm.odin @@ -22,6 +22,118 @@ Emitter :: struct { allocator: mem.Allocator, } +C_Record_ABI_Kind :: enum u8 { + None, + Small_Integer, + Integer_Pair, + Homogeneous_Float, + Indirect, +} + +C_Record_ABI :: struct { + kind: C_Record_ABI_Kind, + size: u64, + alignment: int, + float_type: types.Type, + float_count: int, +} + +hfa_walk :: proc(value: types.Type, store: ^types.Store, scalar: ^types.Type, count: ^int, depth := 0) -> bool { + if depth > 64 || count^ > 4 { + return false + } + if types.is_float(value, store.selected) { + repr := types.representation(value, store.selected) + if scalar^ == types.INVALID { + scalar^ = repr + } + if scalar^ != repr { + return false + } + count^ += 1 + return count^ <= 4 + } + item, ok := types.node(store, value) + if !ok || item.kind == .Union { + return false + } + if item.kind == .Array { + for _ in 0.. C_Record_ABI { + if !types.is_record(value, store) { + return {} + } + result := C_Record_ABI{ + size=types.size(value, store, store.selected), + alignment=types.alignment_of(value, store, store.selected), + } + scalar := types.INVALID + count := 0 + if !types.is_union(value, store) && hfa_walk(value, store, &scalar, &count) && count > 0 { + result.kind = .Homogeneous_Float + result.float_type = scalar + result.float_count = count + return result + } + if result.size <= 8 { + result.kind = .Small_Integer + } else if result.size <= 16 { + result.kind = .Integer_Pair + } else { + result.kind = .Indirect + } + return result +} + +c_abi_param_type :: proc(value: types.Type, store: ^types.Store) -> string { + abi := c_record_abi(value, store) + switch abi.kind { + case .None: + return llvm_type(value, store) + case .Small_Integer: + return "i64" + case .Integer_Pair: + return "[2 x i64]" + case .Homogeneous_Float: + return fmt.tprintf("[%d x %s]", abi.float_count, llvm_type(abi.float_type, store)) + case .Indirect: + return "ptr" + } + return llvm_type(value, store) +} + +c_abi_result_type :: proc(value: types.Type, store: ^types.Store) -> string { + abi := c_record_abi(value, store) + switch abi.kind { + case .None, .Homogeneous_Float: + return llvm_type(value, store) + case .Small_Integer: + return fmt.tprintf("i%d", abi.size*8) + case .Integer_Pair: + return "[2 x i64]" + case .Indirect: + return "void" + } + return llvm_type(value, store) +} + llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string { if types.is_void(value) { return "void" @@ -40,7 +152,7 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string { return "ptr" } return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store)) - case .Struct: + case .Struct, .Union: return fmt.tprintf("%%bro.type.%d", value) } selected := store.selected if store != nil else target.DEFAULT @@ -60,6 +172,9 @@ function_result_type :: proc(function: ir.Function, store: ^types.Store) -> stri if function.is_main { return "i32" } + if function.calling_convention == .C { + return c_abi_result_type(function.result, store) + } return llvm_type(function.result, store) } @@ -111,7 +226,7 @@ valid_value :: proc( } switch instructions[value_id].op { case .Param, .Const, .String, .Aggregate, .None, .Optional_Some, - .Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse, + .Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse, .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call: return true @@ -289,11 +404,70 @@ emit_call_args :: proc( } } +emit_pack_c_record_arg :: proc( + emitter: ^Emitter, + instructions: []ir.Instruction, + value: ir.Instruction_Id, + value_type: types.Type, + call_index, arg_index: int, +) -> string { + abi := c_record_abi(value_type, &emitter.module.types) + if abi.kind == .None { + return fmt.tprintf("%%v%d", value) + } + if abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " %%abi_arg_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, llvm_type(value_type, &emitter.module.types), abi.alignment) + fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(value_type, &emitter.module.types)) + write_operand(&emitter.builder, instructions, value, value_type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", ptr %%abi_arg_slot%d_%d\n", call_index, arg_index) + return fmt.tprintf("%%abi_arg_slot%d_%d", call_index, arg_index) + } + abi_type := c_abi_param_type(value_type, &emitter.module.types) + temp_alignment := max(abi.alignment, 8) + fmt.sbprintf(&emitter.builder, " %%abi_arg_value_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, llvm_type(value_type, &emitter.module.types), abi.alignment) + fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(value_type, &emitter.module.types)) + write_operand(&emitter.builder, instructions, value, value_type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", ptr %%abi_arg_value_slot%d_%d\n", call_index, arg_index) + fmt.sbprintf(&emitter.builder, " %%abi_arg_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, abi_type, temp_alignment) + fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%abi_arg_slot%d_%d\n", abi_type, call_index, arg_index) + fmt.sbprintf( + &emitter.builder, + " call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_arg_slot%d_%d, ptr align %d %%abi_arg_value_slot%d_%d, i64 %d, i1 false)\n", + temp_alignment, call_index, arg_index, abi.alignment, call_index, arg_index, abi.size, + ) + fmt.sbprintf(&emitter.builder, " %%abi_arg%d_%d = load %s, ptr %%abi_arg_slot%d_%d\n", call_index, arg_index, abi_type, call_index, arg_index) + return fmt.tprintf("%%abi_arg%d_%d", call_index, arg_index) +} + +emit_unpack_c_record :: proc( + emitter: ^Emitter, + value_type: types.Type, + abi_type, abi_name, result_name: string, + tag: int, +) { + abi := c_record_abi(value_type, &emitter.module.types) + if abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %s\n", result_name, llvm_type(value_type, &emitter.module.types), abi_name) + return + } + temp_alignment := max(abi.alignment, 8) + fmt.sbprintf(&emitter.builder, " %%abi_unpack_source_slot%d = alloca %s, align %d\n", tag, abi_type, temp_alignment) + fmt.sbprintf(&emitter.builder, " store %s %s, ptr %%abi_unpack_source_slot%d\n", abi_type, abi_name, tag) + fmt.sbprintf(&emitter.builder, " %%abi_unpack_slot%d = alloca %s, align %d\n", tag, llvm_type(value_type, &emitter.module.types), abi.alignment) + fmt.sbprintf( + &emitter.builder, + " call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_unpack_slot%d, ptr align %d %%abi_unpack_source_slot%d, i64 %d, i1 false)\n", + abi.alignment, tag, temp_alignment, tag, abi.size, + ) + fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %%abi_unpack_slot%d\n", result_name, llvm_type(value_type, &emitter.module.types), tag) +} + emit_instruction_stream :: proc( emitter: ^Emitter, instructions: []ir.Instruction, function: ir.Function, global_initializer := false, + sret_name := "", ) -> ir.Instruction_Id { return_value := ir.INVALID_INSTRUCTION after_return := false @@ -326,6 +500,8 @@ emit_instruction_stream :: proc( expected_count = int(item.count) } else if ok && item.kind == .Struct { expected_count = int(item.field_count) + } else if ok && item.kind == .Union { + expected_count = 1 } else { emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type") continue @@ -335,6 +511,22 @@ emit_instruction_stream :: proc( continue } type_name := llvm_type(instruction.type, &emitter.module.types) + if item.kind == .Union { + fields := types.fields_for(&emitter.module.types, instruction.type) + field_index := int(instruction.integer) + if field_index < 0 || field_index >= len(fields) || + !valid_value(instructions, instruction.args[0], fields[field_index].type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid union aggregate operands") + continue + } + fmt.sbprintf(&emitter.builder, " %%union_slot%d = alloca %s, align %d\n", instruction_index, type_name, types.alignment_of(instruction.type, &emitter.module.types, emitter.module.target)) + fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%union_slot%d\n", type_name, instruction_index) + fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(fields[field_index].type, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.args[0], fields[field_index].type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", ptr %%union_slot%d\n", instruction_index) + fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%union_slot%d\n", instruction_index, type_name, instruction_index) + continue + } total := len(instruction.args) + (1 if item.kind == .Array && item.has_sentinel else 0) if total == 0 { fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name) @@ -425,6 +617,14 @@ emit_instruction_stream :: proc( global_id, ) } + case .Function_Address: + function_id := ir.as_function(instruction.target) + if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid function reference") + continue + } + target := emitter.module.functions[function_id] + fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr @%s, ptr null\n", instruction_index, target.link_name) case .Address_Global: global_id := ir.as_global(instruction.target) if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) || @@ -529,11 +729,15 @@ emit_instruction_stream :: proc( emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference") continue } - fmt.sbprintf( - &emitter.builder, - " %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", - instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index, - ) + if types.is_union(base_type, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 0\n", instruction_index, instruction.a) + } else { + fmt.sbprintf( + &emitter.builder, + " %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", + instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index, + ) + } case .Load: if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot") @@ -868,7 +1072,138 @@ emit_instruction_stream :: proc( strings.write_string(&emitter.builder, "\n") case .Call: function_id := ir.as_function(instruction.target) - if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) { + if function_id == ir.INVALID_FUNCTION { + if !valid_instruction(instructions, instruction.a) || + !valid_value(instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call target") + continue + } + callee := instructions[instruction.a] + _, function_item, function_type, ok := types.function_pointer(callee.type, &emitter.module.types) + if !ok { + emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call target") + continue + } + param_fields := types.params_for(&emitter.module.types, function_type) + valid_args := (len(instruction.args) >= len(param_fields) if function_item.variadic else + len(instruction.args) == len(param_fields)) && + (!function_item.variadic || function_item.c_abi) + if valid_args { + for arg, index in instruction.args { + expected := param_fields[index].type if index < len(param_fields) && valid_instruction(instructions, arg) else + (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) + if index >= len(param_fields) && + (!types.is_c_vararg_type(expected, &emitter.module.types) || + !types.equal(types.c_vararg_promotion(expected, emitter.module.target), expected)) { + valid_args = false + break + } + if !valid_value(instructions, arg, expected, &emitter.module.types) { + valid_args = false + break + } + } + } + if !valid_args || !types.equal(instruction.type, function_item.child) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call operands") + continue + } + arg_names := make([]string, len(instruction.args), context.temp_allocator) + for arg, index in instruction.args { + if function_item.c_abi && index < len(param_fields) && + types.is_record(param_fields[index].type, &emitter.module.types) { + arg_names[index] = emit_pack_c_record_arg( + emitter, instructions, arg, param_fields[index].type, instruction_index, index, + ) + } + } + result_abi := C_Record_ABI{} + if function_item.c_abi { + result_abi = c_record_abi(function_item.child, &emitter.module.types) + } + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " %%abi_result_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), result_abi.alignment) + strings.write_string(&emitter.builder, " ") + } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { + fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index) + } else if !types.is_void(instruction.type) { + fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) + } else { + strings.write_string(&emitter.builder, " ") + } + strings.write_string(&emitter.builder, "call ") + if !function_item.c_abi { + strings.write_string(&emitter.builder, "fastcc ") + } + if function_item.c_abi { + extension := c_abi_extension(function_item.child, emitter.module.target) + if len(extension) > 0 { + fmt.sbprintf(&emitter.builder, "%s ", extension) + } + strings.write_string(&emitter.builder, c_abi_result_type(function_item.child, &emitter.module.types)) + } else { + strings.write_string(&emitter.builder, llvm_type(function_item.child, &emitter.module.types)) + } + if function_item.variadic { + strings.write_string(&emitter.builder, " (") + wrote_type := false + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, "ptr") + wrote_type = true + } + for param in param_fields { + if wrote_type { + strings.write_string(&emitter.builder, ", ") + } + strings.write_string(&emitter.builder, c_abi_param_type(param.type, &emitter.module.types)) + wrote_type = true + } + if wrote_type { + strings.write_string(&emitter.builder, ", ") + } + strings.write_string(&emitter.builder, "...)") + } + strings.write_string(&emitter.builder, " ") + write_operand(&emitter.builder, instructions, instruction.a, callee.type, &emitter.module.types) + strings.write_string(&emitter.builder, "(") + wrote_arg := false + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, "ptr sret(%s) align %d %%abi_result_slot%d", llvm_type(function_item.child, &emitter.module.types), result_abi.alignment, instruction_index) + wrote_arg = true + } + for arg, index in instruction.args { + if wrote_arg { + strings.write_string(&emitter.builder, ", ") + } + arg_type := param_fields[index].type if index < len(param_fields) else instructions[arg].type + fixed := index < len(param_fields) + if function_item.c_abi && fixed && types.is_record(arg_type, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, "%s %s", c_abi_param_type(arg_type, &emitter.module.types), arg_names[index]) + } else { + fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types)) + if function_item.c_abi && fixed { + extension := c_abi_extension(arg_type, emitter.module.target) + if len(extension) > 0 { + fmt.sbprintf(&emitter.builder, "%s ", extension) + } + } + write_operand(&emitter.builder, instructions, arg, arg_type, &emitter.module.types) + } + wrote_arg = true + } + strings.write_string(&emitter.builder, ")\n") + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), instruction_index) + } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { + emit_unpack_c_record( + emitter, function_item.child, c_abi_result_type(function_item.child, &emitter.module.types), + fmt.tprintf("%%abi_result%d", instruction_index), fmt.tprintf("%%v%d", instruction_index), + 100000+instruction_index, + ) + } + continue + } + if int(function_id) >= len(emitter.module.functions) { emit_recovery_value(emitter, instruction_index, instruction, "invalid function specialization") continue } @@ -900,7 +1235,25 @@ emit_instruction_stream :: proc( emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands") continue } - if !types.is_void(instruction.type) { + arg_names := make([]string, len(instruction.args), context.temp_allocator) + for arg, index in instruction.args { + if target.calling_convention == .C && index < len(target.param_types) && + types.is_record(target.param_types[index], &emitter.module.types) { + arg_names[index] = emit_pack_c_record_arg( + emitter, instructions, arg, target.param_types[index], instruction_index, index, + ) + } + } + result_abi := C_Record_ABI{} + if target.calling_convention == .C { + result_abi = c_record_abi(target.result, &emitter.module.types) + } + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " %%abi_result_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(target.result, &emitter.module.types), result_abi.alignment) + strings.write_string(&emitter.builder, " ") + } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { + fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index) + } else if !types.is_void(instruction.type) { fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) } else { strings.write_string(&emitter.builder, " ") @@ -912,23 +1265,59 @@ emit_instruction_stream :: proc( emit_function_result(&emitter.builder, target, &emitter.module.types) if target.variadic { strings.write_string(&emitter.builder, " (") + wrote_type := false + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, "ptr") + wrote_type = true + } for param_type, index in target.param_types { - if index > 0 { + if wrote_type || index > 0 { strings.write_string(&emitter.builder, ", ") } - strings.write_string(&emitter.builder, llvm_type(param_type, &emitter.module.types)) + strings.write_string(&emitter.builder, c_abi_param_type(param_type, &emitter.module.types)) + wrote_type = true } - if len(target.param_types) > 0 { + if wrote_type { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, "...)") } fmt.sbprintf(&emitter.builder, " @%s(", target.link_name) - emit_call_args( - &emitter.builder, instructions, instruction.args, target.param_types, - &emitter.module.types, target.calling_convention == .C, - ) + wrote_arg := false + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, "ptr sret(%s) align %d %%abi_result_slot%d", llvm_type(target.result, &emitter.module.types), result_abi.alignment, instruction_index) + wrote_arg = true + } + for arg, index in instruction.args { + if wrote_arg { + strings.write_string(&emitter.builder, ", ") + } + arg_type := target.param_types[index] if index < len(target.param_types) else instructions[arg].type + fixed := index < len(target.param_types) + if target.calling_convention == .C && fixed && types.is_record(arg_type, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, "%s %s", c_abi_param_type(arg_type, &emitter.module.types), arg_names[index]) + } else { + fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types)) + if target.calling_convention == .C && fixed { + extension := c_abi_extension(arg_type, emitter.module.target) + if len(extension) > 0 { + fmt.sbprintf(&emitter.builder, "%s ", extension) + } + } + write_operand(&emitter.builder, instructions, arg, arg_type, &emitter.module.types) + } + wrote_arg = true + } strings.write_string(&emitter.builder, ")\n") + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(target.result, &emitter.module.types), instruction_index) + } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { + emit_unpack_c_record( + emitter, target.result, c_abi_result_type(target.result, &emitter.module.types), + fmt.tprintf("%%abi_result%d", instruction_index), fmt.tprintf("%%v%d", instruction_index), + 100000+instruction_index, + ) + } case .Trap: message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source") emit_trap_call(emitter, message) @@ -937,9 +1326,31 @@ emit_instruction_stream :: proc( return_value = instruction.a continue } - fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types)) - write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) - strings.write_string(&emitter.builder, "\n") + result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{} + if result_abi.kind == .Indirect { + fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(function.result, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", ptr %s\n ret void\n", sret_name) + } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { + abi_type := c_abi_result_type(function.result, &emitter.module.types) + temp_alignment := max(result_abi.alignment, 8) + fmt.sbprintf(&emitter.builder, " %%abi_return_value_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(function.result, &emitter.module.types), result_abi.alignment) + fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(function.result, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) + fmt.sbprintf(&emitter.builder, ", ptr %%abi_return_value_slot%d\n", instruction_index) + fmt.sbprintf(&emitter.builder, " %%abi_return_slot%d = alloca %s, align %d\n", instruction_index, abi_type, temp_alignment) + fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%abi_return_slot%d\n", abi_type, instruction_index) + fmt.sbprintf( + &emitter.builder, + " call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_return_slot%d, ptr align %d %%abi_return_value_slot%d, i64 %d, i1 false)\n", + temp_alignment, instruction_index, result_abi.alignment, instruction_index, result_abi.size, + ) + fmt.sbprintf(&emitter.builder, " %%abi_return%d = load %s, ptr %%abi_return_slot%d\n ret %s %%abi_return%d\n", instruction_index, abi_type, instruction_index, abi_type, instruction_index) + } else { + fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types)) + write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) + strings.write_string(&emitter.builder, "\n") + } after_return = true case .Return_Void: if global_initializer { @@ -982,7 +1393,7 @@ emit_globals :: proc(emitter: ^Emitter) { emit_types :: proc(emitter: ^Emitter) { for item, index in emitter.module.types.nodes { - if item.kind != .Struct { + if item.kind != .Struct && item.kind != .Union { continue } id := types.DYNAMIC_START+types.Type(index) @@ -991,6 +1402,34 @@ emit_types :: proc(emitter: ^Emitter) { strings.write_string(&emitter.builder, "opaque\n") continue } + if item.kind == .Union { + fields := types.fields_for(&emitter.module.types, id) + carrier := types.INVALID + carrier_size: u64 + carrier_alignment := 0 + for field in fields { + field_alignment := types.alignment_of(field.type, &emitter.module.types, emitter.module.target) + field_size := types.size(field.type, &emitter.module.types, emitter.module.target) + if field_alignment > carrier_alignment || + (field_alignment == carrier_alignment && field_size > carrier_size) { + carrier = field.type + carrier_size = field_size + carrier_alignment = field_alignment + } + } + total_size := types.size(id, &emitter.module.types, emitter.module.target) + if !types.is_valid(carrier) { + fmt.sbprintf(&emitter.builder, "[%d x i8]\n", total_size) + continue + } + strings.write_string(&emitter.builder, "{ ") + strings.write_string(&emitter.builder, llvm_type(carrier, &emitter.module.types)) + if carrier_size < total_size { + fmt.sbprintf(&emitter.builder, ", [%d x i8]", total_size-carrier_size) + } + strings.write_string(&emitter.builder, " }\n") + continue + } strings.write_string(&emitter.builder, "{ ") for field, field_index in types.fields_for(&emitter.module.types, id) { if field_index > 0 { @@ -1108,24 +1547,38 @@ emit_functions :: proc(emitter: ^Emitter) { } emit_function_result(&emitter.builder, function, &emitter.module.types) fmt.sbprintf(&emitter.builder, " @%s(", function.link_name) + result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{} + wrote_param := false + if result_abi.kind == .Indirect { + fmt.sbprintf( + &emitter.builder, "ptr sret(%s) align %d", + llvm_type(function.result, &emitter.module.types), result_abi.alignment, + ) + if function.implementation != .Declaration { + strings.write_string(&emitter.builder, " %abi_sret") + } + wrote_param = true + } for param_type, index in function.param_types { - if index > 0 { + if wrote_param || index > 0 { strings.write_string(&emitter.builder, ", ") } - if function.implementation == .Declaration { - fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types)) - } else { - fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types)) - } - if function.calling_convention == .C { + type_name := c_abi_param_type(param_type, &emitter.module.types) if function.calling_convention == .C else llvm_type(param_type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, "%s", type_name) + if function.calling_convention == .C && !types.is_record(param_type, &emitter.module.types) { extension := c_abi_extension(param_type, emitter.module.target) if len(extension) > 0 { fmt.sbprintf(&emitter.builder, " %s", extension) } } if function.implementation != .Declaration { - fmt.sbprintf(&emitter.builder, " %%v%d", index) + if function.calling_convention == .C && types.is_record(param_type, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%abi_p%d", index) + } else { + fmt.sbprintf(&emitter.builder, " %%v%d", index) + } } + wrote_param = true } if function.variadic { if len(function.param_types) > 0 { @@ -1138,7 +1591,18 @@ emit_functions :: proc(emitter: ^Emitter) { continue } strings.write_string(&emitter.builder, ") {\nentry:\n") - _ = emit_instruction_stream(emitter, function.instructions, function) + if function.calling_convention == .C { + for param_type, index in function.param_types { + if !types.is_record(param_type, &emitter.module.types) { + continue + } + emit_unpack_c_record( + emitter, param_type, c_abi_param_type(param_type, &emitter.module.types), + fmt.tprintf("%%abi_p%d", index), fmt.tprintf("%%v%d", index), 200000+index, + ) + } + } + _ = emit_instruction_stream(emitter, function.instructions, function, sret_name="%abi_sret") strings.write_string(&emitter.builder, "}\n\n") } } @@ -1163,7 +1627,7 @@ emit_messages :: proc(emitter: ^Emitter) { } emit_declarations :: proc(emitter: ^Emitter) { - strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\n") + strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\n") widths := [?]int{8, 16, 32, 64} for bits in widths { strings.write_string(&emitter.builder, "declare { i") diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin index 1d6b764..ed173be 100644 --- a/compiler/loader/loader.odin +++ b/compiler/loader/loader.odin @@ -168,6 +168,26 @@ translate_c_type :: proc( pointer := types.pointer(&state.module.type_store, child, item.mutable, true) translated = types.optional(&state.module.type_store, pointer) } + case .Array: + child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping) + if types.is_valid(child) { + translated = types.array(&state.module.type_store, child, item.count, item.mutable) + } + case .Function: + params := make([]types.Type, len(item.params), state.allocator) + for param, index in item.params { + params[index] = translate_c_type(state, result, param, pkg, record_mapping, type_mapping) + if !types.is_valid(params[index]) { + delete(params, state.allocator) + type_mapping[value] = types.INVALID + return types.INVALID + } + } + result_type := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping) + if types.is_valid(result_type) { + translated = types.function(&state.module.type_store, params, result_type, true, item.variadic) + } + delete(params, state.allocator) case .Record: if int(item.record) < len(record_mapping) { translated = record_mapping[item.record] @@ -189,6 +209,93 @@ function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, resul return true } +RECORD_DEPENDENCY_PENDING :: "C record contains an incomplete or unsupported record field" + +record_layout_reason :: proc( + store: ^types.Store, + record: cimport.Record, + fields: []types.Field, + selected: target.Target, +) -> string { + if len(fields) == 0 { + if record.size > 0 { + return "anonymous C record fields are not supported" + } + return "empty C records are not supported" + } + if len(fields) != len(record.fields) || record.alignment == 0 { + return "C record metadata is incomplete" + } + size: u64 + alignment: u64 = 1 + if record.kind == .Union { + for field in fields { + if !types.is_runtime_value(field.type, store) { + return RECORD_DEPENDENCY_PENDING + } + if field.offset != 0 { + return "non-natural C record layouts are not supported" + } + size = max(size, types.size(field.type, store, selected)) + alignment = max(alignment, u64(types.alignment_of(field.type, store, selected))) + } + } else { + offset: u64 + for field in fields { + if !types.is_runtime_value(field.type, store) { + return RECORD_DEPENDENCY_PENDING + } + field_alignment := u64(types.alignment_of(field.type, store, selected)) + offset = (offset+field_alignment-1)/field_alignment*field_alignment + if field.offset != offset { + if field.offset < offset { + return "packed C records are not supported" + } + return "non-natural C record layouts are not supported" + } + offset += types.size(field.type, store, selected) + alignment = max(alignment, field_alignment) + } + size = offset + } + size = (size+alignment-1)/alignment*alignment + if u64(record.alignment) > alignment { + return "over-aligned C records are not supported" + } + if u64(record.alignment) < alignment || record.size < size { + return "packed C records are not supported" + } + if record.size != size { + return "non-natural C record layouts are not supported" + } + return "" +} + +c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id, depth := 0) -> string { + if depth > 64 || value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) { + return "" + } + item := result.types[value] + #partial switch item.kind { + case .Pointer: + return "" + case .Array: + return c_record_by_value_reason(result, item.child, depth+1) + case .Record: + if int(item.record) >= len(result.records) { + return "C record metadata is incomplete" + } + record := result.records[item.record] + if len(record.reason) > 0 { + return record.reason + } + if !record.complete { + return "incomplete C records are pointer-only" + } + } + return "" +} + load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id { canonical, ok := filepath.abs(path, state.allocator) if !ok { @@ -229,7 +336,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as name = fmt.tprintf("__c_record_%d", len(state.record_types)) } record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name))) - _ = types.define_struct(&state.module.type_store, record_type, nil, true, true) + _ = types.define_record(&state.module.type_store, record_type, nil, true, true, record.kind == .Union) append(&state.record_identities, strings.clone(record.identity, state.allocator)) append(&state.record_types, record_type) } @@ -238,6 +345,42 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as type_mapping := make([]types.Type, len(result.types), state.allocator) defer delete(type_mapping, state.allocator) + for _ in 0.. 0 { + continue + } + record_type := record_mapping[record_index] + item, ok := types.node(&state.module.type_store, record_type) + if !ok || (item.declared && !item.opaque) { + continue + } + fields := make([]types.Field, len(record.fields), state.allocator) + for field, field_index in record.fields { + fields[field_index] = types.Field{ + name=u32(symbol.intern(state.symbols, field.name)), + type=translate_c_type(state, &result, field.type, pkg_id, record_mapping, type_mapping), + offset=field.offset, + } + } + layout_reason := record_layout_reason(&state.module.type_store, record, fields, state.selected) + if len(layout_reason) == 0 { + changed = types.define_record( + &state.module.type_store, record_type, fields, true, false, + record.kind == .Union, record.size, record.alignment, + ) || changed + } else if layout_reason != RECORD_DEPENDENCY_PENDING && len(record.reason) == 0 { + delete(result.records[record_index].reason, result.allocator) + result.records[record_index].reason = fmt.aprintf("%s", layout_reason, allocator=result.allocator) + } + delete(fields, state.allocator) + } + if !changed { + break + } + } + for alias in result.aliases { name := symbol.intern(state.symbols, alias.name) id := types.named(&state.module.type_store, u32(pkg_id), u32(name)) @@ -263,17 +406,28 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as } function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping) unsupported_reason := function.reason + if len(unsupported_reason) == 0 { + for param_type in function.params { + if reason := c_record_by_value_reason(&result, param_type); len(reason) > 0 { + unsupported_reason = reason + break + } + } + } + if len(unsupported_reason) == 0 { + unsupported_reason = c_record_by_value_reason(&result, function.result) + } if len(unsupported_reason) == 0 { for param in params { if types.contains_c_struct_by_value(param.type, &state.module.type_store) { - unsupported_reason = "C records passed by value are not supported" + unsupported_reason = "C record parameter is incomplete or has an unsupported layout" break } } } if len(unsupported_reason) == 0 && types.contains_c_struct_by_value(function_result, &state.module.type_store) { - unsupported_reason = "C records returned by value are not supported" + unsupported_reason = "C record result is incomplete or has an unsupported layout" } name := symbol.intern(state.symbols, function.name) duplicate := false @@ -522,7 +676,7 @@ canonical_type :: proc( mapping[index] = value if !types.is_valid(resolved) else resolved return mapping[index] } - if item.kind == .Struct { + if item.kind == .Struct || item.kind == .Union { mapping[index] = value fields := types.fields_for(&module.type_store, value) for &field in fields { @@ -530,6 +684,16 @@ canonical_type :: proc( } return value } + if item.kind == .Function { + params := make([]types.Type, int(item.field_count), context.temp_allocator) + for param, param_index in types.params_for(&module.type_store, value) { + params[param_index] = canonical_type(module, param.type, mapping, visiting) + } + result := canonical_type(module, item.child, mapping, visiting) + resolved := types.function(&module.type_store, params, result, item.c_abi, item.variadic) + mapping[index] = resolved + return resolved + } if types.is_valid(item.child) { item.child = canonical_type(module, item.child, mapping, visiting) } diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index 2dd315d..2b24cf9 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -156,7 +156,7 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi args[index] = lower_nested_expr(state, arg) } return append_instruction(state, ir.Instruction{ - op=.Aggregate, span=expr.span, type=expr.type, args=args, + op=.Aggregate, span=expr.span, type=expr.type, args=args, integer=expr.integer, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) @@ -332,6 +332,19 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { }) } _ = pop(&stack) + case .Function: + function := hir.as_function(expr.target) + if function == hir.INVALID_FUNCTION || int(function) >= len(state.hir_module.functions) { + last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) + } else { + last = append_instruction(state, ir.Instruction{ + op=.Function_Address, span=expr.span, type=expr.type, + target=ir.function_ref(ir.Function_Id(function)), + a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + _ = pop(&stack) case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer: stack[frame_index].stage = 1 append(&stack, Lower_Expr_Frame{expr=expr.left}) @@ -343,7 +356,17 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { append(&stack, Lower_Expr_Frame{expr=expr.left}) case .Call: function := hir.as_function(expr.target) - if function == hir.INVALID_FUNCTION || int(function) >= len(state.hir_module.functions) { + if function == hir.INVALID_FUNCTION { + if expr.left == hir.INVALID_EXPR { + last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) + _ = pop(&stack) + continue + } + stack[frame_index].stage = 6 + append(&stack, Lower_Expr_Frame{expr=expr.left}) + continue + } + if int(function) >= len(state.hir_module.functions) { last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) _ = pop(&stack) continue @@ -356,6 +379,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { } continue } + if frame.stage == 6 { + stack[frame_index].left = last + stack[frame_index].args = make([]ir.Instruction_Id, len(expr.args), state.allocator) + stack[frame_index].stage = 4 + if len(expr.args) > 0 { + append(&stack, Lower_Expr_Frame{expr=expr.args[0]}) + } + continue + } if frame.stage == 5 { last = append_instruction(state, ir.Instruction{ op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF, @@ -405,9 +437,14 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { continue } } + function := hir.as_function(expr.target) + callee := frame.left + if function != hir.INVALID_FUNCTION { + callee = ir.INVALID_INSTRUCTION + } last = append_instruction(state, ir.Instruction{ - op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(hir.as_function(expr.target))), - a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC, + op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(function)), + a=callee, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC, }) stack[frame_index].args = nil _ = pop(&stack) diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 132963a..cc3bea9 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -90,7 +90,7 @@ is_type_token :: proc(kind: token.Kind) -> bool { .Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint, .Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong, .Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble, - .Keyword_Void, .Identifier, .Question, .At, .Star, .Left_Bracket: + .Keyword_Void, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket: return true } return false @@ -291,6 +291,25 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax { case .Keyword_Void: advance(parser) return types.VOID + case .Keyword_C_Func: + advance(parser) + if _, ok := allow(parser, .Left_Paren); !ok { + source.add(parser.diagnostics, current(parser).span, "expected '(' after c_func type") + return types.INVALID + } + params, variadic := parse_params(parser) + if _, ok := allow(parser, .Right_Paren); !ok { + source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters") + } + result := parse_type(parser) + param_types := make([]types.Type, len(params), parser.module.allocator) + for param, index in params { + param_types[index] = param.type + } + function_type := types.function(&parser.module.type_store, param_types, result, true, variadic) + delete(param_types, parser.module.allocator) + delete(params, parser.module.allocator) + return function_type case .Identifier: first := advance(parser) name := first @@ -334,11 +353,7 @@ skip_parenthesized :: proc(parser: ^Parser) -> source.Span { return span_from(start.span, end.span) } -parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id { - if nesting >= MAX_EXPRESSION_NESTING { - span := skip_parenthesized(parser) - return invalid_expr(parser, span, "expression nesting exceeds 256 levels") - } +parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.Token) { left_paren := advance(parser) parser.delimiter_depth += 1 defer parser.delimiter_depth -= 1 @@ -359,6 +374,15 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok source.add(parser.diagnostics, current(parser).span, "expected ')' after call arguments") right_paren = left_paren } + return args[:], right_paren +} + +parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id { + if nesting >= MAX_EXPRESSION_NESTING { + span := skip_parenthesized(parser) + return invalid_expr(parser, span, "expression nesting exceeds 256 levels") + } + args, right_paren := parse_call_args(parser, nesting) return add_expr(parser, ast.Expr{ kind=.Call, span=source.Span{file=name.span.file, start=first.span.start, end=right_paren.span.end}, @@ -735,6 +759,24 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int } continue } + if current(parser).kind == .Left_Paren { + if nesting >= MAX_EXPRESSION_NESTING { + span := skip_parenthesized(parser) + left = invalid_expr(parser, span, "expression nesting exceeds 256 levels") + continue + } + left_expr := parser.module.exprs[left] + args, right_paren := parse_call_args(parser, nesting) + left = add_expr(parser, ast.Expr{ + kind=.Call, + span=span_from(left_expr.span, right_paren.span), + args=args, + left=left, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + continue + } left_power, right_power, ok := infix_binding_power(current(parser).kind) if !ok || left_power < minimum_binding_power { break diff --git a/compiler/types/types.odin b/compiler/types/types.odin index b52da9a..ccbede9 100644 --- a/compiler/types/types.odin +++ b/compiler/types/types.odin @@ -58,9 +58,11 @@ Kind :: enum u8 { Pointer, Slice, Optional, + Function, Named, Alias, Struct, + Union, } Node :: struct { @@ -68,8 +70,10 @@ Node :: struct { child: Type, count: u64, sentinel: u64, + explicit_size: u64, field_start: u32, field_count: u32, + explicit_alignment: u32, pkg: u32, name: u32, qualifier: u32, @@ -78,14 +82,17 @@ Node :: struct { many: bool, has_sentinel: bool, inferred_count: bool, + c_abi: bool, + variadic: bool, c_layout: bool, opaque: bool, declared: bool, } Field :: struct { - name: u32, - type: Type, + name: u32, + type: Type, + offset: u64, } Store :: struct { @@ -118,7 +125,7 @@ clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store { } intern :: proc(store: ^Store, candidate: Node) -> Type { - if candidate.kind != .Struct && candidate.kind != .Named { + if candidate.kind != .Struct && candidate.kind != .Union && candidate.kind != .Named { for existing, index in store.nodes { if existing == candidate { return DYNAMIC_START+Type(index) @@ -133,7 +140,7 @@ intern :: proc(store: ^Store, candidate: Node) -> Type { named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type { normalized_file := file if qualifier != 0 else u32(0) for existing, index in store.nodes { - if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) && + if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct || existing.kind == .Union) && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && existing.file == normalized_file { return DYNAMIC_START+Type(index) @@ -144,7 +151,7 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type { for existing, index in store.nodes { - if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) && + if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct || existing.kind == .Union) && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier { return DYNAMIC_START+Type(index) } @@ -164,25 +171,53 @@ define_alias :: proc(store: ^Store, id, child: Type) -> bool { return true } -define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool { +define_record :: proc( + store: ^Store, + id: Type, + fields: []Field, + c_layout, opaque: bool, + is_union := false, + explicit_size: u64 = 0, + explicit_alignment: u32 = 0, +) -> bool { existing, ok := node(store, id) - if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared { + if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) || + (existing.declared && !existing.opaque) { return false } index := int(id-DYNAMIC_START) - store.nodes[index].kind = .Struct + store.nodes[index].kind = .Union if is_union else .Struct store.nodes[index].c_layout = c_layout store.nodes[index].opaque = opaque store.nodes[index].declared = true + store.nodes[index].explicit_size = explicit_size + store.nodes[index].explicit_alignment = explicit_alignment store.nodes[index].field_start = u32(len(store.fields)) store.nodes[index].field_count = u32(len(fields)) append(&store.fields, ..fields) return true } +define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool { + return define_record(store, id, fields, c_layout, opaque) +} + fields_for :: proc(store: ^Store, value: Type) -> []Field { item, ok := node(store, value) - if !ok || item.kind != .Struct { + if !ok || (item.kind != .Struct && item.kind != .Union) { + return nil + } + start := int(item.field_start) + end := start+int(item.field_count) + if start < 0 || end > len(store.fields) { + return nil + } + return store.fields[start:end] +} + +params_for :: proc(store: ^Store, value: Type) -> []Field { + item, ok := node(store, value) + if !ok || item.kind != .Function { return nil } start := int(item.field_start) @@ -350,7 +385,7 @@ is_concrete :: proc(value: Type, store: ^Store = nil) -> bool { value_kind == .Slice || value_kind == .Optional { return true } - if value_kind == .Struct { + if value_kind == .Struct || value_kind == .Union { item, ok := node(store, value) return ok && item.declared } @@ -373,10 +408,64 @@ is_optional :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Optional } +is_function :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Function +} + is_struct :: proc(value: Type, store: ^Store) -> bool { return kind(value, store) == .Struct } +is_record :: proc(value: Type, store: ^Store) -> bool { + value_kind := kind(value, store) + return value_kind == .Struct || value_kind == .Union +} + +is_union :: proc(value: Type, store: ^Store) -> bool { + return kind(value, store) == .Union +} + +resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type { + if depth > 64 { + return INVALID + } + item, ok := node(store, value) + if !ok || item.kind != .Alias { + return value + } + return resolve_alias(item.child, store, depth+1) +} + +is_c_record_field_type :: proc(value: Type, store: ^Store, depth := 0) -> bool { + if depth > 256 { + return false + } + resolved := resolve_alias(value, store) + if is_concrete_scalar(resolved) || is_pointer(resolved, store) || is_optional_pointer(resolved, store) { + return true + } + item, ok := node(store, resolved) + if !ok { + return false + } + if item.kind == .Array { + return item.count > 0 && !item.has_sentinel && !item.inferred_count && + is_c_record_field_type(item.child, store, depth+1) + } + if item.kind != .Struct && item.kind != .Union { + return false + } + if !item.c_layout || !item.declared || item.opaque || item.field_count == 0 { + return false + } + for field in fields_for(store, resolved) { + if !is_c_record_field_type(field.type, store, depth+1) { + return false + } + } + return true +} + is_optional_pointer :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Optional && is_pointer(item.child, store) @@ -390,9 +479,9 @@ is_runtime_value :: proc(value: Type, store: ^Store) -> bool { if value_kind == .Slice || value_kind == .Array || value_kind == .Optional { return !contains_c_struct_by_value(value, store) } - if value_kind == .Struct { + if value_kind == .Struct || value_kind == .Union { item, ok := node(store, value) - return ok && item.declared && !item.opaque && !contains_c_struct_by_value(value, store) + return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0) } return false } @@ -409,7 +498,7 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo return false } if item.kind == .Struct { - if item.c_layout { + if item.opaque || (item.c_layout && item.field_count == 0) { return true } for field in fields_for(store, value) { @@ -419,6 +508,9 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo } return false } + if item.kind == .Union { + return item.opaque || (item.c_layout && item.field_count == 0) + } if item.kind == .Array || item.kind == .Slice || item.kind == .Optional { return contains_c_struct_by_value(item.child, store, depth+1) } @@ -429,7 +521,8 @@ is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) -> if allow_void && is_void(value) { return true } - return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) + return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) || + (is_c_struct(value, store) && is_runtime_value(value, store)) } is_c_integer_promotion_candidate :: proc(value: Type) -> bool { @@ -518,9 +611,21 @@ container :: proc(value: Type, store: ^Store) -> (Node, bool) { return array_node, true } +function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) { + pointer_node, pointer_ok := node(store, value) + if !pointer_ok || pointer_node.kind != .Pointer { + return {}, {}, INVALID, false + } + function_node, function_ok := node(store, pointer_node.child) + if !function_ok || function_node.kind != .Function { + return {}, {}, INVALID, false + } + return pointer_node, function_node, pointer_node.child, true +} + is_c_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) - return ok && item.kind == .Struct && item.c_layout + return ok && (item.kind == .Struct || item.kind == .Union) && item.c_layout } pointer :: proc( @@ -572,6 +677,47 @@ optional :: proc(store: ^Store, child: Type) -> Type { return intern(store, Node{kind=.Optional, child=child}) } +function_params_equal :: proc(store: ^Store, item: Node, params: []Type) -> bool { + if item.field_count != u32(len(params)) { + return false + } + start := int(item.field_start) + end := start+int(item.field_count) + if start < 0 || end > len(store.fields) { + return false + } + for param, index in params { + if store.fields[start+index].type != param { + return false + } + } + return true +} + +function :: proc(store: ^Store, params: []Type, result: Type, c_abi, variadic: bool) -> Type { + for existing, index in store.nodes { + if existing.kind == .Function && + existing.child == result && + existing.c_abi == c_abi && + existing.variadic == variadic && + function_params_equal(store, existing, params) { + return DYNAMIC_START+Type(index) + } + } + start := len(store.fields) + for param in params { + append(&store.fields, Field{type=param}) + } + return intern(store, Node{ + kind=.Function, + child=result, + field_start=u32(start), + field_count=u32(len(params)), + c_abi=c_abi, + variadic=variadic, + }) +} + with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type { item, ok := node(store, value) if !ok || item.kind != .Array { @@ -638,7 +784,7 @@ can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool { is_opaque_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) - return ok && item.kind == .Struct && item.opaque + return ok && (item.kind == .Struct || item.kind == .Union) && item.opaque } size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { @@ -660,7 +806,13 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { child_size := size(item.child, store, selected) child_align := u64(alignment_of(item.child, store, selected)) return ((child_size+1+child_align-1)/child_align)*child_align + case .Function: + return 0 case .Struct: + item, _ := node(store, value) + if item.explicit_size > 0 { + return item.explicit_size + } offset: u64 max_align: u64 = 1 for field in fields_for(store, value) { @@ -670,6 +822,18 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 { max_align = max(max_align, field_align) } return (offset+max_align-1)/max_align*max_align + case .Union: + item, _ := node(store, value) + if item.explicit_size > 0 { + return item.explicit_size + } + result: u64 + max_align: u64 = 1 + for field in fields_for(store, value) { + result = max(result, size(field.type, store, selected)) + max_align = max(max_align, u64(alignment_of(field.type, store, selected))) + } + return (result+max_align-1)/max_align*max_align case: return 0 } @@ -683,7 +847,23 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> return target.pointer_bits(selected)/8 case .Array, .Optional: return alignment_of(child_type(value, store), store, selected) + case .Function: + return 1 case .Struct: + item, _ := node(store, value) + if item.explicit_alignment > 0 { + return int(item.explicit_alignment) + } + result := 1 + for field in fields_for(store, value) { + result = max(result, alignment_of(field.type, store, selected)) + } + return result + case .Union: + item, _ := node(store, value) + if item.explicit_alignment > 0 { + return int(item.explicit_alignment) + } result := 1 for field in fields_for(store, value) { result = max(result, alignment_of(field.type, store, selected)) diff --git a/compiler_tests.odin b/compiler_tests.odin index 9de68ff..90bfb68 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -204,6 +204,34 @@ main :: func() void {} nullable_child.many && nullable_child.has_sentinel) } +@(test) +parser_accepts_c_function_pointer_types :: proc(t: ^testing.T) { + text := `take :: c_func(callback ?*c_func(value c_int) c_int) void +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) + module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&module) + + optional, optional_ok := types.node(&module.type_store, module.functions[0].params[0].type) + pointer, pointer_ok := types.node(&module.type_store, optional.child) + function, function_ok := types.node(&module.type_store, pointer.child) + params := types.params_for(&module.type_store, pointer.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, function_ok && function.kind == .Function && function.c_abi && !function.variadic) + testing.expect(t, function.child == types.C_INT) + testing.expect_value(t, len(params), 1) + testing.expect(t, params[0].type == types.C_INT) +} + @(test) parser_diagnoses_malformed_sentinel_pointer_types :: proc(t: ^testing.T) { text := `bad :: func(value [*0]u8) void {} @@ -1026,13 +1054,18 @@ main :: func() void { @(test) c_variadic_restrictions_and_extra_argument_types_are_diagnosed :: proc(t: ^testing.T) { - text := `foreign :: c_func(...) void + text := `Record :: c_struct { + value c_int +} +foreign :: c_func(...) void requires :: c_func(value c_int, ...) void native :: func(...) void bodyful :: c_func(...) void {} main :: func() void { values [1]u8 :: [1] + record Record :: Record { value = 1 } foreign(values) + foreign(record) requires() } ` @@ -1072,15 +1105,21 @@ variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T } @(test) -c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) { +c_structs_are_by_value_and_may_be_opaque :: proc(t: ^testing.T) { text := `Defined :: c_struct { value c_int } Opaque :: c_struct +Empty :: c_struct {} +Bad :: c_struct { + values []i32 +} read :: c_func(value @Defined) c_int -bad_param :: c_func(value Defined) void -bad_result :: c_func() Defined -main :: func() void {} +pass :: c_func(value Defined) Defined +bad_opaque :: c_func(value Opaque) void +main :: func() void { + _ = pass(Defined { value = 1 }) +} ` source_file := source.Source{path="test.bro", text=text} diagnostics := source.init_diagnostics(&source_file) @@ -1094,14 +1133,72 @@ main :: func() void {} hir_module := checker.check(&ast_module, &diagnostics, &symbols) defer hir.destroy_module(&hir_module) - found_param := false - found_result := false + found_opaque := false + found_bad_layout := false + found_empty := 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") + found_opaque = found_opaque || strings.contains(diagnostic.message, "cannot be passed by value") + 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_param) - testing.expect(t, found_result) + testing.expect(t, found_opaque) + testing.expect(t, found_bad_layout) + testing.expect(t, found_empty) +} + +@(test) +aarch64_c_record_abi_classifies_fixed_parameters_and_results :: proc(t: ^testing.T) { + text := `Small :: c_struct { + left c_int + right c_int +} +Medium :: c_struct { + first c_int + second c_int + third c_int +} +Hfa :: c_struct { + x c_float + y c_float +} +Large :: c_struct { + first c_long + second c_long + third c_long +} +small :: c_func(value Small) Small +medium :: c_func(value Medium) Medium +hfa :: c_func(value Hfa) Hfa +large :: c_func(value Large) Large +main :: func() void { + _ = small(Small { left = 1, right = 2 }) + _ = medium(Medium { first = 1, second = 2, third = 3 }) + _ = hfa(Hfa { x = 1.0, y = 2.0 }) + _ = large(Large { first = 1, second = 2, third = 3 }) +} +` + 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, "declare i64 @small(i64)")) + testing.expect(t, strings.contains(llvm_text, "declare [2 x i64] @medium([2 x i64])")) + testing.expect(t, strings.contains(llvm_text, "@hfa([2 x float])")) + testing.expect(t, strings.contains(llvm_text, "declare void @large(ptr sret(")) + testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64")) } @(test) @@ -1697,6 +1794,18 @@ restricted_c_header_imports_compile_and_link :: proc(t: ^testing.T) { testing.expect_value(t, state.exit_code, 0) } +@(test) +by_value_c_records_and_unions_compile_and_link :: proc(t: ^testing.T) { + output := "/tmp/brolang-test-records" + defer _ = os.remove(output) + arguments := []linker.Argument{{kind=.Input, value="examples/interop/records/native.c"}} + c_options := cimport.Options{include_paths=[]string{"examples/interop/records/include"}} + status := compiler_core.compile_package("examples/interop/records/app", output, arguments, target.DEFAULT, c_options) + testing.expect_value(t, status, 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 1) +} + @(test) unsupported_c_header_members_diagnose_only_when_referenced :: proc(t: ^testing.T) { output := "/tmp/brolang-test-header-unsupported" diff --git a/examples/interop/header/app/main.bro b/examples/interop/header/app/main.bro index ba95eec..f4397a1 100644 --- a/examples/interop/header/app/main.bro +++ b/examples/interop/header/app/main.bro @@ -4,12 +4,22 @@ pass_alias :: func(value native.imported_int_alias) native.imported_int { return value } +double_value :: c_func(value c_int) c_int { + return value + value +} + +call_mapper :: func(mapper native.Imported_Mapper) c_int { + return mapper?(21) +} + main :: func() void { _ = native.imported_add(pass_alias(20), 22) _ = native.imported_scalar(3, 4) _ = native.child_value(7) _ = native.imported_read(native.imported_handle()?) _ = native.imported_string("hello") + _ = native.imported_apply(double_value, 21) + _ = call_mapper(double_value) _ = native.configured_value(9) signed i8 :: -2 unsigned u16 :: 3 diff --git a/examples/interop/header/include/native.h b/examples/interop/header/include/native.h index ffef1b5..97fdc34 100644 --- a/examples/interop/header/include/native.h +++ b/examples/interop/header/include/native.h @@ -7,12 +7,35 @@ typedef int imported_int; typedef imported_int imported_int_alias; typedef struct Imported_Handle Imported_Handle; typedef void (*Imported_Callback)(int value); +typedef int (*Imported_Mapper)(int value); typedef struct Imported_Value { int value; } Imported_Value; typedef union Imported_Union { int value; } Imported_Union; +typedef struct Imported_Bitfield { + unsigned value : 1; +} Imported_Bitfield; +typedef struct __attribute__((packed)) Imported_Packed { + char first; + int second; +} Imported_Packed; +typedef struct Imported_Flexible { + int count; + int values[]; +} Imported_Flexible; +typedef struct Imported_Qualified { + const int value; +} Imported_Qualified; +typedef struct Imported_Overaligned { + _Alignas(16) int value; +} Imported_Overaligned; +typedef struct Imported_Anonymous { + struct { + int value; + }; +} Imported_Anonymous; typedef enum Imported_Enum { IMPORTED_ENUM_VALUE, } Imported_Enum; @@ -22,6 +45,7 @@ unsigned long imported_scalar(unsigned char value, unsigned long extra); Imported_Handle *imported_handle(void); int imported_read(const Imported_Handle *handle); int imported_string(const char *value); +int imported_apply(Imported_Mapper mapper, int value); Imported_Value imported_by_value(Imported_Value value); int imported_volatile(volatile int *value); _Bool imported_bool(_Bool value); @@ -37,5 +61,11 @@ int configured_value(int value); #define IMPORTED_MACRO 42 int imported_variadic(int marker, ...); +Imported_Bitfield imported_bitfield(Imported_Bitfield value); +Imported_Packed imported_packed(Imported_Packed value); +Imported_Flexible imported_flexible(Imported_Flexible value); +Imported_Qualified imported_qualified(Imported_Qualified value); +Imported_Overaligned imported_overaligned(Imported_Overaligned value); +Imported_Anonymous imported_anonymous(Imported_Anonymous value); #endif diff --git a/examples/interop/header/native.c b/examples/interop/header/native.c index e6165d7..ba7d5c3 100644 --- a/examples/interop/header/native.c +++ b/examples/interop/header/native.c @@ -36,6 +36,17 @@ int imported_string(const char *value) { return 0; } +int imported_apply(Imported_Mapper mapper, int value) { + if (mapper == NULL) { + abort(); + } + int result = mapper(value); + if (result != value * 2) { + abort(); + } + return result; +} + int imported_variadic(int marker, ...) { va_list args; va_start(args, marker); diff --git a/examples/interop/header_unsupported/main.bro b/examples/interop/header_unsupported/main.bro index 5c08c33..1311461 100644 --- a/examples/interop/header_unsupported/main.bro +++ b/examples/interop/header_unsupported/main.bro @@ -3,10 +3,17 @@ native :: import "../header/include/native.h" use_callback :: c_func(value native.Imported_Callback) void use_union :: c_func(value native.Imported_Union) void use_enum :: c_func(value native.Imported_Enum) void +use_opaque :: c_func(value native.Imported_Handle) void main :: func() void { _ = native.IMPORTED_MACRO _ = native.imported_by_value() + _ = native.imported_bitfield() + _ = native.imported_packed() + _ = native.imported_flexible() + _ = native.imported_qualified() + _ = native.imported_overaligned() + _ = native.imported_anonymous() _ = native.imported_volatile() _ = native.imported_bool() _ = native.imported_global diff --git a/examples/interop/records/app/main.bro b/examples/interop/records/app/main.bro new file mode 100644 index 0000000..a027132 --- /dev/null +++ b/examples/interop/records/app/main.bro @@ -0,0 +1,46 @@ +native :: import "../include/native.h" + +Manual :: c_struct { + value c_int +} + +mirror_manual :: c_func(value Manual) Manual { + return value +} + +mirror_large :: c_func(value native.Large) native.Large { + return value +} + +native_pair :: func(value native.Pair) native.Pair { + return value +} + +global_pair native.Pair :: native.Pair { left = 1, right = 2 } + +main :: func() i32 { + pair native.Pair = native_pair(global_pair) + pair.left = 10 + pair = native.echo_pair(pair) + pairs [1]native.Pair :: [pair] + _ = native_pair(pairs[0]) + + triple native.Triple :: native.echo_triple(native.Triple { first = 3, second = 4, third = 5 }) + floats native.Floats :: native.echo_floats(native.Floats { x = 6.0, y = 7.0 }) + large native.Large :: mirror_large(native.echo_large(native.Large { values = [8, 9, 10] })) + nested native.Nested :: native.echo_nested(native.Nested { + pair = native.Pair { left = 11, right = 12 }, + tail = 13, + }) + arrays native.Arrays :: native.echo_arrays(native.Arrays { values = [14, 15, 16] }) + choice native.Choice = native.Choice { decimal = 1.0 } + choice.integer = 17 + choice = native.echo_choice(choice) + forward native.Forward :: native.echo_forward(native.Forward { value = 19 }) + manual Manual :: mirror_manual(Manual { value = 18 }) + _ = manual.value + _ = forward.value + + _ = native.verify_records(pair, triple, floats, large, nested, arrays, choice) + return 1 +} diff --git a/examples/interop/records/include/native.h b/examples/interop/records/include/native.h new file mode 100644 index 0000000..18e724c --- /dev/null +++ b/examples/interop/records/include/native.h @@ -0,0 +1,53 @@ +#ifndef BROLANG_RECORDS_H +#define BROLANG_RECORDS_H + +typedef struct Pair { + int left; + int right; +} Pair; + +typedef struct Triple { + int first; + int second; + int third; +} Triple; + +typedef struct Floats { + float x; + float y; +} Floats; + +typedef struct Large { + long values[3]; +} Large; + +typedef struct Nested { + Pair pair; + int tail; +} Nested; + +typedef struct Arrays { + int values[3]; +} Arrays; + +typedef union Choice { + long integer; + double decimal; +} Choice; + +typedef struct Forward Forward; +struct Forward { + int value; +}; + +Pair echo_pair(Pair value); +Triple echo_triple(Triple value); +Floats echo_floats(Floats value); +Large echo_large(Large value); +Nested echo_nested(Nested value); +Arrays echo_arrays(Arrays value); +Choice echo_choice(Choice value); +Forward echo_forward(Forward value); +int verify_records(Pair pair, Triple triple, Floats floats, Large large, Nested nested, Arrays arrays, Choice choice); + +#endif diff --git a/examples/interop/records/native.c b/examples/interop/records/native.c new file mode 100644 index 0000000..52b3fff --- /dev/null +++ b/examples/interop/records/native.c @@ -0,0 +1,24 @@ +#include "include/native.h" +#include + +Pair echo_pair(Pair value) { return value; } +Triple echo_triple(Triple value) { return value; } +Floats echo_floats(Floats value) { return value; } +Large echo_large(Large value) { return value; } +Nested echo_nested(Nested value) { return value; } +Arrays echo_arrays(Arrays value) { return value; } +Choice echo_choice(Choice value) { return value; } +Forward echo_forward(Forward value) { return value; } + +int verify_records(Pair pair, Triple triple, Floats floats, Large large, Nested nested, Arrays arrays, Choice choice) { + if (!(pair.left == 10 && pair.right == 2 && + triple.first == 3 && triple.second == 4 && triple.third == 5 && + floats.x == 6.0f && floats.y == 7.0f && + large.values[0] == 8 && large.values[1] == 9 && large.values[2] == 10 && + nested.pair.left == 11 && nested.pair.right == 12 && nested.tail == 13 && + arrays.values[0] == 14 && arrays.values[1] == 15 && arrays.values[2] == 16 && + choice.integer == 17)) { + abort(); + } + return 1; +}