function pointers and callbacks

This commit is contained in:
2026-06-15 21:09:46 +02:00
parent 3b7c3fcbd0
commit f5605fd3ec
21 changed files with 1927 additions and 122 deletions
+9 -6
View File
@@ -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
+32 -4
View File
@@ -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
+15 -3
View File
@@ -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
+436 -29
View File
@@ -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..<min(fixed_count, len(stack[frame_index].built_args)) {
expected_arg := callable_arg_expected(function_type, function_item, &checker.module.types, index)
stack[frame_index].built_args[index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
expected_arg,
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
for index in fixed_count..<len(stack[frame_index].built_args) {
arg := stack[frame_index].built_args[index]
stack[frame_index].built_args[index] = promote_c_vararg_expr(
checker,
arg,
checker.module.exprs[arg].span,
)
}
result := function_item.child
if !types.is_valid(result) {
id := source.add(checker.diagnostics, expr.span, "could not resolve function pointer result type")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=hir.INVALID_REF,
left=frame.left, right=hir.INVALID_EXPR, args=stack[frame_index].built_args,
diagnostic=source.INVALID_DIAGNOSTIC,
})
stack[frame_index].built_args = nil
}
_ = pop(&stack)
}
}
return last
}
+30
View File
@@ -24,19 +24,41 @@ Type_Kind :: enum u8 {
C_Double,
C_Longdouble,
Pointer,
Array,
Function,
Record,
}
Type :: struct {
kind: Type_Kind,
child: Type_Id,
params: []Type_Id,
record: u32,
count: u64,
mutable: bool,
variadic: bool,
}
Record_Kind :: enum u8 {
Struct,
Union,
}
Field :: struct {
name: string,
type: Type_Id,
offset: u64,
}
Record :: struct {
name: string,
identity: string,
fields: [dynamic]Field,
size: u64,
alignment: u32,
kind: Record_Kind,
complete: bool,
reason: string,
}
Alias :: struct {
@@ -82,9 +104,17 @@ init_result :: proc(allocator := context.allocator) -> 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)
+155 -9
View File
@@ -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..<count {
param := translate_type(ctx, ctx.api.get_arg_type(value, u32(index)), "", depth+1)
append(&params, param)
if param == INVALID_TYPE {
delete(params)
return INVALID_TYPE
}
}
return add_type(ctx, Type{
kind=.Function,
child=result,
params=params[:],
variadic=ctx.api.is_function_type_variadic(value) != 0,
})
case CXType_Typedef:
declaration := ctx.api.get_type_declaration(value)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
@@ -282,7 +428,8 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
return INVALID_TYPE
}
return translate_type(ctx, canonical, preferred_record_name, depth+1)
case CXType_Enum, CXType_FunctionNoProto, CXType_FunctionProto:
case CXType_Enum, CXType_FunctionNoProto,
CXType_IncompleteArray, CXType_VariableArray, CXType_DependentSizedArray:
return INVALID_TYPE
}
return INVALID_TYPE
@@ -373,14 +520,13 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> 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:
+1
View File
@@ -83,6 +83,7 @@ Expr_Kind :: enum u8 {
Optional_Some,
Local,
Global,
Function,
Address,
Deref,
Index,
+1
View File
@@ -73,6 +73,7 @@ Opcode :: enum u8 {
None,
Optional_Some,
Load_Global,
Function_Address,
Address_Global,
Address_Of,
Alloca,
+494 -30
View File
@@ -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..<int(item.count) {
if !hfa_walk(item.child, store, scalar, count, depth+1) {
return false
}
}
return true
}
if item.kind != .Struct {
return false
}
for field in types.fields_for(store, value) {
if !hfa_walk(field.type, store, scalar, count, depth+1) {
return false
}
}
return true
}
c_record_abi :: proc(value: types.Type, store: ^types.Store) -> 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")
+168 -4
View File
@@ -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..<len(result.records) {
changed := false
for record, record_index in result.records {
if !record.complete || len(record.reason) > 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)
}
+41 -4
View File
@@ -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)
+48 -6
View File
@@ -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
+196 -16
View File
@@ -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))
+120 -11
View File
@@ -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"
+10
View File
@@ -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
+30
View File
@@ -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
+11
View File
@@ -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);
@@ -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
+46
View File
@@ -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
}
+53
View File
@@ -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
+24
View File
@@ -0,0 +1,24 @@
#include "include/native.h"
#include <stdlib.h>
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;
}