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 - 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 - 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 - 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 - postfix pointer dereference, general writable locations, function calls, assignments, and returns
### functions and packages ### functions and packages
@@ -33,12 +35,13 @@
- bodyful `c_func` definitions using the c calling convention - bodyful `c_func` definitions using the c calling convention
- bodyless `c_func` declarations with exact, globally unique external symbol names - bodyless `c_func` declarations with exact, globally unique external symbol names
- concrete-only foreign signatures - 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 - directory packages with merged declarations
- file-local relative imports, aliases, and qualified member access - file-local relative imports, aliases, and qualified member access
- relative `.h` imports as synthetic package namespaces - 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 - 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 - reference-time diagnostics for unsupported imported C declarations
### compiler behavior ### compiler behavior
@@ -60,9 +63,9 @@
### scalar and compound types ### scalar and compound types
- tuples and native variadic functions - 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 ### advanced c imports
- C enums, external variables, function pointers, callbacks, macros, and static inline functions - C enums, external variables, macros, and static inline functions
- target-specific by-value C record and union ABI lowering - 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 Header imports expose supported external functions, typedefs, C scalars, fixed
pointers to opaque records. They never add linker inputs; implementations must arrays, complete plain structs and unions, function pointer typedefs, and
still be supplied explicitly with the C-prefixed linking options. Set 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. `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: Bodyless manual and imported C functions may be variadic:
```bro ```bro
@@ -97,13 +124,14 @@ Current prototype features:
- `#` comments - `#` comments
- Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks - Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks
- Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` - 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 - 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 - 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 - 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 - Contextual integer constants and compile-time folding of addition and unary negation trees
- Directory packages with merged declarations and file-local relative imports - Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces - 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 - Qualified imported globals and functions with package-aware symbol mangling
- Demand-monomorphized Brolang and C-ABI functions - Demand-monomorphized Brolang and C-ABI functions
- Bodyless concrete C function declarations with exact external symbol names - 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 - pointer-only `c_struct` support with target c layout
- `Some :: c_struct { ... }`: defined c-layout struct - `Some :: c_struct { ... }`: defined c-layout struct
- `Some :: c_struct`: opaque 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) 2. restricted c header imports (implemented)
- treat an imported header as a synthetic, file-local package namespace - 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 - reject general `u8`/`c_char` interchange, slice-to-pointer coercion, and conversion to mutable c character pointers
4. advanced c interop 4. advanced c interop
- by-value records and unions - by-value records and unions (implemented)
- function pointers and callbacks - 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 - external variables
- macros and static inline functions - macros and static inline functions
- exporting brolang functions to c - 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) 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 { contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names { for existing in names {
if existing == name { if existing == name {
@@ -518,6 +583,9 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
switch expr.kind { switch expr.kind {
case .Call: case .Call:
append(&stack, ..expr.args) append(&stack, ..expr.args)
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Array, .Struct_Literal, .Slice: case .Array, .Struct_Literal, .Slice:
append(&stack, ..expr.args) append(&stack, ..expr.args)
if expr.left != ast.INVALID_EXPR { if expr.left != ast.INVALID_EXPR {
@@ -600,11 +668,15 @@ validate_declarations :: proc(checker: ^Checker) {
} }
if !function.has_body && function.c_abi { if !function.has_body && function.c_abi {
for param in function.params { 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 { source.INVALID_DIAGNOSTIC {
continue 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.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
param.span, param.span,
@@ -615,6 +687,7 @@ validate_declarations :: proc(checker: ^Checker) {
} }
result := type_from_syntax(function.result) result := type_from_syntax(function.result)
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC && 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) { !types.is_c_signature_type(result, &checker.module.types, true) {
checker.template_diagnostics[function_id] = source.addf( checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics, 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) { 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( source.add(
checker.diagnostics, checker.diagnostics,
source.Span{}, 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) _ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded)
} }
target_pkg, available := expr_package(checker, expr, pkg, file) 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: case .Keyed:
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
case: 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) _ = pop(&stack)
case .Negate: case .Negate:
stack[frame_index].stage = 5 stack[frame_index].stage = 5
@@ -1002,14 +1116,60 @@ infer_expr :: proc(
stack[frame_index].stage = 1 stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION}) append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Call: 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) target_pkg, available := expr_package(checker, expr, pkg, file)
template := ast.INVALID_FUNCTION template := ast.INVALID_FUNCTION
if available { if available {
template = find_template(checker, expr.name, target_pkg) template = find_template(checker, expr.name, target_pkg)
} }
if template == ast.INVALID_FUNCTION { if template == ast.INVALID_FUNCTION {
last = types.INVALID callee_type := types.INVALID
_ = pop(&stack) 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 continue
} }
if len(checker.ast_module.functions[template].unsupported_reason) > 0 { if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
@@ -1091,6 +1251,25 @@ infer_expr :: proc(
stack[frame_index].args = nil stack[frame_index].args = nil
_ = pop(&stack) _ = 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 return last
} }
@@ -1548,6 +1727,65 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb
return 0, {}, false 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( build_nested_expr :: proc(
checker: ^Checker, checker: ^Checker,
expr_id: ast.Expr_Id, expr_id: ast.Expr_Id,
@@ -1776,16 +2014,18 @@ build_compound_expr :: proc(
case .Struct_Literal: case .Struct_Literal:
target_pkg, available := expr_package(checker, expr, pkg, file, true) 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 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) { struct_type = types.resolve_alias(struct_type, store)
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque struct type '%s'", symbol_text(checker, expr.name)) if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
return invalid_hir_expr(checker, expr.span, id) id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
}
if types.is_c_struct(struct_type, store) {
id := source.add(checker.diagnostics, expr.span, "c_struct values cannot be constructed by value")
return invalid_hir_expr(checker, expr.span, id) return invalid_hir_expr(checker, expr.span, id)
} }
fields := types.fields_for(store, struct_type) 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) initialized := make([]bool, len(fields), checker.allocator)
defer delete(initialized, checker.allocator) defer delete(initialized, checker.allocator)
for &value in values { for &value in values {
@@ -1803,18 +2043,35 @@ build_compound_expr :: proc(
continue continue
} }
initialized[index] = true initialized[index] = true
values[index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file) value_index := 0 if union_record else index
values[index] = coerce_expr(checker, values[index], field.type, keyed_expr.span) 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 !union_record {
if values[index] == hir.INVALID_EXPR { 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))) id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
delete(values, checker.allocator) delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, struct_type) 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{ 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, target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC, 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, target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
}) })
} else { } else {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) template := find_template(checker, expr.name, target_pkg)
if id == source.INVALID_DIAGNOSTIC { if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi {
id = add_name_resolution_diagnostic(checker, expr, target_pkg) 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) _ = pop(&stack)
@@ -1953,6 +2215,11 @@ build_expr :: proc(
stack[frame_index].stage = 1 stack[frame_index].stage = 1
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION}) append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Call: 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) target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available { if !available {
id := add_package_resolution_diagnostic(checker, expr, file) id := add_package_resolution_diagnostic(checker, expr, file)
@@ -1962,12 +2229,63 @@ build_expr :: proc(
} }
template := find_template(checker, expr.name, target_pkg) template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION { if template == ast.INVALID_FUNCTION {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name) callee := hir.INVALID_EXPR
if id == source.INVALID_DIAGNOSTIC { callee_from_global := false
id = add_call_resolution_diagnostic(checker, expr, target_pkg) 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 continue
} }
if len(checker.ast_module.functions[template].unsupported_reason) > 0 { if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
@@ -2143,6 +2461,95 @@ build_expr :: proc(
} }
_ = pop(&stack) _ = 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 return last
} }
+30
View File
@@ -24,19 +24,41 @@ Type_Kind :: enum u8 {
C_Double, C_Double,
C_Longdouble, C_Longdouble,
Pointer, Pointer,
Array,
Function,
Record, Record,
} }
Type :: struct { Type :: struct {
kind: Type_Kind, kind: Type_Kind,
child: Type_Id, child: Type_Id,
params: []Type_Id,
record: u32, record: u32,
count: u64,
mutable: bool, mutable: bool,
variadic: bool,
}
Record_Kind :: enum u8 {
Struct,
Union,
}
Field :: struct {
name: string,
type: Type_Id,
offset: u64,
} }
Record :: struct { Record :: struct {
name: string, name: string,
identity: string, identity: string,
fields: [dynamic]Field,
size: u64,
alignment: u32,
kind: Record_Kind,
complete: bool,
reason: string,
} }
Alias :: struct { Alias :: struct {
@@ -82,9 +104,17 @@ init_result :: proc(allocator := context.allocator) -> Result {
} }
destroy_result :: proc(result: ^Result) { destroy_result :: proc(result: ^Result) {
for type_item in result.types {
delete(type_item.params, result.allocator)
}
for record in result.records { for record in result.records {
delete(record.name, result.allocator) delete(record.name, result.allocator)
delete(record.identity, 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 { for alias in result.aliases {
delete(alias.name, result.allocator) delete(alias.name, result.allocator)
+155 -9
View File
@@ -43,16 +43,25 @@ Api :: struct {
get_cursor_spelling: proc "c"(CXCursor) -> CXString, get_cursor_spelling: proc "c"(CXCursor) -> CXString,
get_cursor_usr: proc "c"(CXCursor) -> CXString, get_cursor_usr: proc "c"(CXCursor) -> CXString,
get_cursor_linkage: proc "c"(CXCursor) -> i32, 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_cursor_type: proc "c"(CXCursor) -> CXType,
get_typedef_underlying_type: proc "c"(CXCursor) -> CXType, get_typedef_underlying_type: proc "c"(CXCursor) -> CXType,
get_type_declaration: proc "c"(CXType) -> CXCursor, get_type_declaration: proc "c"(CXType) -> CXCursor,
get_canonical_type: proc "c"(CXType) -> CXType, get_canonical_type: proc "c"(CXType) -> CXType,
get_pointee_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_result_type: proc "c"(CXType) -> CXType,
get_num_arg_types: proc "c"(CXType) -> i32, get_num_arg_types: proc "c"(CXType) -> i32,
get_arg_type: proc "c"(CXType, u32) -> CXType, 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_const_qualified_type: proc "c"(CXType) -> u32,
is_volatile_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, cursor_is_variadic: proc "c"(CXCursor) -> u32,
get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32, get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32,
get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic, get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic,
@@ -70,6 +79,7 @@ CXCursor_FunctionDecl :: i32(8)
CXCursor_VarDecl :: i32(9) CXCursor_VarDecl :: i32(9)
CXCursor_TypedefDecl :: i32(20) CXCursor_TypedefDecl :: i32(20)
CXCursor_MacroDefinition :: i32(501) CXCursor_MacroDefinition :: i32(501)
CXCursor_FieldDecl :: i32(6)
CXLinkage_External :: i32(4) CXLinkage_External :: i32(4)
@@ -97,6 +107,10 @@ CXType_Enum :: i32(106)
CXType_Typedef :: i32(107) CXType_Typedef :: i32(107)
CXType_FunctionNoProto :: i32(110) CXType_FunctionNoProto :: i32(110)
CXType_FunctionProto :: i32(111) 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_Elaborated :: i32(119)
CXType_Attributed :: i32(163) 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_getCursorSpelling", &api.get_cursor_spelling) &&
load_proc(&api, "clang_getCursorUSR", &api.get_cursor_usr) && load_proc(&api, "clang_getCursorUSR", &api.get_cursor_usr) &&
load_proc(&api, "clang_getCursorLinkage", &api.get_cursor_linkage) && 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_getCursorType", &api.get_cursor_type) &&
load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) && load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) &&
load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) && load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) &&
load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) && load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) &&
load_proc(&api, "clang_getPointeeType", &api.get_pointee_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_getResultType", &api.get_result_type) &&
load_proc(&api, "clang_getNumArgTypes", &api.get_num_arg_types) && load_proc(&api, "clang_getNumArgTypes", &api.get_num_arg_types) &&
load_proc(&api, "clang_getArgType", &api.get_arg_type) && 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_isConstQualifiedType", &api.is_const_qualified_type) &&
load_proc(&api, "clang_isVolatileQualifiedType", &api.is_volatile_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_Cursor_isVariadic", &api.cursor_is_variadic) &&
load_proc(&api, "clang_getNumDiagnostics", &api.get_num_diagnostics) && load_proc(&api, "clang_getNumDiagnostics", &api.get_num_diagnostics) &&
load_proc(&api, "clang_getDiagnostic", &api.get_diagnostic) && 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) name = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
} }
index := u32(len(ctx.result.records)) 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 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 { 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 { if depth > 64 || value.kind == CXType_Invalid || ctx.api.is_volatile_qualified_type(value) != 0 {
return INVALID_TYPE return INVALID_TYPE
@@ -259,18 +371,52 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
if child == INVALID_TYPE { if child == INVALID_TYPE {
return 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{ return add_type(ctx, Type{
kind=.Pointer, kind=.Pointer,
child=child, child=child,
mutable=ctx.api.is_const_qualified_type(pointee) == 0, mutable=mutable,
}) })
case CXType_Record: case CXType_ConstantArray:
declaration := ctx.api.get_type_declaration(value) count := ctx.api.get_array_size(value)
if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl { child := translate_type(ctx, ctx.api.get_array_element_type(value), "", depth+1)
if count <= 0 || child == INVALID_TYPE {
return 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) 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}) 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: case CXType_Typedef:
declaration := ctx.api.get_type_declaration(value) declaration := ctx.api.get_type_declaration(value)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator) 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 INVALID_TYPE
} }
return translate_type(ctx, canonical, preferred_record_name, depth+1) 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
} }
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" reason = "typedef underlying type is not supported"
} }
add_alias(ctx, name, value, reason) add_alias(ctx, name, value, reason)
case CXCursor_StructDecl: case CXCursor_StructDecl, CXCursor_UnionDecl:
if len(name) > 0 { if len(name) > 0 {
record := add_record(ctx, cursor, name) record := add_record(ctx, cursor, name)
populate_record(ctx, record, cursor)
value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record}) value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
add_alias(ctx, name, value) add_alias(ctx, name, value)
} }
case CXCursor_UnionDecl:
add_unsupported(ctx, name, "C unions are not supported")
case CXCursor_EnumDecl: case CXCursor_EnumDecl:
add_unsupported(ctx, name, "C enums are not supported") add_unsupported(ctx, name, "C enums are not supported")
case CXCursor_VarDecl: case CXCursor_VarDecl:
+1
View File
@@ -83,6 +83,7 @@ Expr_Kind :: enum u8 {
Optional_Some, Optional_Some,
Local, Local,
Global, Global,
Function,
Address, Address,
Deref, Deref,
Index, Index,
+1
View File
@@ -73,6 +73,7 @@ Opcode :: enum u8 {
None, None,
Optional_Some, Optional_Some,
Load_Global, Load_Global,
Function_Address,
Address_Global, Address_Global,
Address_Of, Address_Of,
Alloca, Alloca,
+494 -30
View File
@@ -22,6 +22,118 @@ Emitter :: struct {
allocator: mem.Allocator, 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 { llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
if types.is_void(value) { if types.is_void(value) {
return "void" return "void"
@@ -40,7 +152,7 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
return "ptr" return "ptr"
} }
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store)) return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
case .Struct: case .Struct, .Union:
return fmt.tprintf("%%bro.type.%d", value) return fmt.tprintf("%%bro.type.%d", value)
} }
selected := store.selected if store != nil else target.DEFAULT 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 { if function.is_main {
return "i32" return "i32"
} }
if function.calling_convention == .C {
return c_abi_result_type(function.result, store)
}
return llvm_type(function.result, store) return llvm_type(function.result, store)
} }
@@ -111,7 +226,7 @@ valid_value :: proc(
} }
switch instructions[value_id].op { switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some, 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, .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Pointer_Add, .Call: .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true 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( emit_instruction_stream :: proc(
emitter: ^Emitter, emitter: ^Emitter,
instructions: []ir.Instruction, instructions: []ir.Instruction,
function: ir.Function, function: ir.Function,
global_initializer := false, global_initializer := false,
sret_name := "",
) -> ir.Instruction_Id { ) -> ir.Instruction_Id {
return_value := ir.INVALID_INSTRUCTION return_value := ir.INVALID_INSTRUCTION
after_return := false after_return := false
@@ -326,6 +500,8 @@ emit_instruction_stream :: proc(
expected_count = int(item.count) expected_count = int(item.count)
} else if ok && item.kind == .Struct { } else if ok && item.kind == .Struct {
expected_count = int(item.field_count) expected_count = int(item.field_count)
} else if ok && item.kind == .Union {
expected_count = 1
} else { } else {
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type") emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type")
continue continue
@@ -335,6 +511,22 @@ emit_instruction_stream :: proc(
continue continue
} }
type_name := llvm_type(instruction.type, &emitter.module.types) 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) total := len(instruction.args) + (1 if item.kind == .Array && item.has_sentinel else 0)
if total == 0 { if total == 0 {
fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name) fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name)
@@ -425,6 +617,14 @@ emit_instruction_stream :: proc(
global_id, 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: case .Address_Global:
global_id := ir.as_global(instruction.target) global_id := ir.as_global(instruction.target)
if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) || 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") emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference")
continue continue
} }
fmt.sbprintf( if types.is_union(base_type, &emitter.module.types) {
&emitter.builder, fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 0\n", instruction_index, instruction.a)
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", } else {
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index, 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: case .Load:
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) { if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot") emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot")
@@ -868,7 +1072,138 @@ emit_instruction_stream :: proc(
strings.write_string(&emitter.builder, "\n") strings.write_string(&emitter.builder, "\n")
case .Call: case .Call:
function_id := ir.as_function(instruction.target) 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") emit_recovery_value(emitter, instruction_index, instruction, "invalid function specialization")
continue continue
} }
@@ -900,7 +1235,25 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands") emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands")
continue 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) fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
} else { } else {
strings.write_string(&emitter.builder, " ") strings.write_string(&emitter.builder, " ")
@@ -912,23 +1265,59 @@ emit_instruction_stream :: proc(
emit_function_result(&emitter.builder, target, &emitter.module.types) emit_function_result(&emitter.builder, target, &emitter.module.types)
if target.variadic { if target.variadic {
strings.write_string(&emitter.builder, " (") 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 { 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, ", ")
} }
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, ", ")
} }
strings.write_string(&emitter.builder, "...)") strings.write_string(&emitter.builder, "...)")
} }
fmt.sbprintf(&emitter.builder, " @%s(", target.link_name) fmt.sbprintf(&emitter.builder, " @%s(", target.link_name)
emit_call_args( wrote_arg := false
&emitter.builder, instructions, instruction.args, target.param_types, if result_abi.kind == .Indirect {
&emitter.module.types, target.calling_convention == .C, 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") 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: case .Trap:
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source") message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
emit_trap_call(emitter, message) emit_trap_call(emitter, message)
@@ -937,9 +1326,31 @@ emit_instruction_stream :: proc(
return_value = instruction.a return_value = instruction.a
continue continue
} }
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types)) result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{}
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) if result_abi.kind == .Indirect {
strings.write_string(&emitter.builder, "\n") 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 after_return = true
case .Return_Void: case .Return_Void:
if global_initializer { if global_initializer {
@@ -982,7 +1393,7 @@ emit_globals :: proc(emitter: ^Emitter) {
emit_types :: proc(emitter: ^Emitter) { emit_types :: proc(emitter: ^Emitter) {
for item, index in emitter.module.types.nodes { for item, index in emitter.module.types.nodes {
if item.kind != .Struct { if item.kind != .Struct && item.kind != .Union {
continue continue
} }
id := types.DYNAMIC_START+types.Type(index) id := types.DYNAMIC_START+types.Type(index)
@@ -991,6 +1402,34 @@ emit_types :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "opaque\n") strings.write_string(&emitter.builder, "opaque\n")
continue 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, "{ ") strings.write_string(&emitter.builder, "{ ")
for field, field_index in types.fields_for(&emitter.module.types, id) { for field, field_index in types.fields_for(&emitter.module.types, id) {
if field_index > 0 { if field_index > 0 {
@@ -1108,24 +1547,38 @@ emit_functions :: proc(emitter: ^Emitter) {
} }
emit_function_result(&emitter.builder, function, &emitter.module.types) emit_function_result(&emitter.builder, function, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " @%s(", function.link_name) 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 { for param_type, index in function.param_types {
if index > 0 { if wrote_param || index > 0 {
strings.write_string(&emitter.builder, ", ") strings.write_string(&emitter.builder, ", ")
} }
if function.implementation == .Declaration { 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", llvm_type(param_type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, "%s", type_name)
} else { if function.calling_convention == .C && !types.is_record(param_type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
}
if function.calling_convention == .C {
extension := c_abi_extension(param_type, emitter.module.target) extension := c_abi_extension(param_type, emitter.module.target)
if len(extension) > 0 { if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, " %s", extension) fmt.sbprintf(&emitter.builder, " %s", extension)
} }
} }
if function.implementation != .Declaration { 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 function.variadic {
if len(function.param_types) > 0 { if len(function.param_types) > 0 {
@@ -1138,7 +1591,18 @@ emit_functions :: proc(emitter: ^Emitter) {
continue continue
} }
strings.write_string(&emitter.builder, ") {\nentry:\n") 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") strings.write_string(&emitter.builder, "}\n\n")
} }
} }
@@ -1163,7 +1627,7 @@ emit_messages :: proc(emitter: ^Emitter) {
} }
emit_declarations :: 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} widths := [?]int{8, 16, 32, 64}
for bits in widths { for bits in widths {
strings.write_string(&emitter.builder, "declare { i") 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) pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
translated = types.optional(&state.module.type_store, pointer) 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: case .Record:
if int(item.record) < len(record_mapping) { if int(item.record) < len(record_mapping) {
translated = record_mapping[item.record] translated = record_mapping[item.record]
@@ -189,6 +209,93 @@ function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, resul
return true 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 { load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator) canonical, ok := filepath.abs(path, state.allocator)
if !ok { 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)) 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))) 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_identities, strings.clone(record.identity, state.allocator))
append(&state.record_types, record_type) 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) type_mapping := make([]types.Type, len(result.types), state.allocator)
defer delete(type_mapping, 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 { for alias in result.aliases {
name := symbol.intern(state.symbols, alias.name) name := symbol.intern(state.symbols, alias.name)
id := types.named(&state.module.type_store, u32(pkg_id), u32(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) function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping)
unsupported_reason := function.reason 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 { if len(unsupported_reason) == 0 {
for param in params { for param in params {
if types.contains_c_struct_by_value(param.type, &state.module.type_store) { 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 break
} }
} }
} }
if len(unsupported_reason) == 0 && if len(unsupported_reason) == 0 &&
types.contains_c_struct_by_value(function_result, &state.module.type_store) { 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) name := symbol.intern(state.symbols, function.name)
duplicate := false duplicate := false
@@ -522,7 +676,7 @@ canonical_type :: proc(
mapping[index] = value if !types.is_valid(resolved) else resolved mapping[index] = value if !types.is_valid(resolved) else resolved
return mapping[index] return mapping[index]
} }
if item.kind == .Struct { if item.kind == .Struct || item.kind == .Union {
mapping[index] = value mapping[index] = value
fields := types.fields_for(&module.type_store, value) fields := types.fields_for(&module.type_store, value)
for &field in fields { for &field in fields {
@@ -530,6 +684,16 @@ canonical_type :: proc(
} }
return value 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) { if types.is_valid(item.child) {
item.child = canonical_type(module, item.child, mapping, visiting) 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) args[index] = lower_nested_expr(state, arg)
} }
return append_instruction(state, ir.Instruction{ 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, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
@@ -332,6 +332,19 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}) })
} }
_ = pop(&stack) _ = 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: case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1 stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left}) 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}) append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call: case .Call:
function := hir.as_function(expr.target) 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) last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
_ = pop(&stack) _ = pop(&stack)
continue continue
@@ -356,6 +379,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
} }
continue 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 { if frame.stage == 5 {
last = append_instruction(state, ir.Instruction{ last = append_instruction(state, ir.Instruction{
op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF, 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 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{ 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))), op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(function)),
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC, a=callee, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
stack[frame_index].args = nil stack[frame_index].args = nil
_ = pop(&stack) _ = 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_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong, .Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble, .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 true
} }
return false return false
@@ -291,6 +291,25 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
case .Keyword_Void: case .Keyword_Void:
advance(parser) advance(parser)
return types.VOID 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: case .Identifier:
first := advance(parser) first := advance(parser)
name := first name := first
@@ -334,11 +353,7 @@ skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
return span_from(start.span, end.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 { parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.Token) {
if nesting >= MAX_EXPRESSION_NESTING {
span := skip_parenthesized(parser)
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
}
left_paren := advance(parser) left_paren := advance(parser)
parser.delimiter_depth += 1 parser.delimiter_depth += 1
defer 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") source.add(parser.diagnostics, current(parser).span, "expected ')' after call arguments")
right_paren = left_paren 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{ return add_expr(parser, ast.Expr{
kind=.Call, kind=.Call,
span=source.Span{file=name.span.file, start=first.span.start, end=right_paren.span.end}, 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 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) left_power, right_power, ok := infix_binding_power(current(parser).kind)
if !ok || left_power < minimum_binding_power { if !ok || left_power < minimum_binding_power {
break break
+196 -16
View File
@@ -58,9 +58,11 @@ Kind :: enum u8 {
Pointer, Pointer,
Slice, Slice,
Optional, Optional,
Function,
Named, Named,
Alias, Alias,
Struct, Struct,
Union,
} }
Node :: struct { Node :: struct {
@@ -68,8 +70,10 @@ Node :: struct {
child: Type, child: Type,
count: u64, count: u64,
sentinel: u64, sentinel: u64,
explicit_size: u64,
field_start: u32, field_start: u32,
field_count: u32, field_count: u32,
explicit_alignment: u32,
pkg: u32, pkg: u32,
name: u32, name: u32,
qualifier: u32, qualifier: u32,
@@ -78,14 +82,17 @@ Node :: struct {
many: bool, many: bool,
has_sentinel: bool, has_sentinel: bool,
inferred_count: bool, inferred_count: bool,
c_abi: bool,
variadic: bool,
c_layout: bool, c_layout: bool,
opaque: bool, opaque: bool,
declared: bool, declared: bool,
} }
Field :: struct { Field :: struct {
name: u32, name: u32,
type: Type, type: Type,
offset: u64,
} }
Store :: struct { Store :: struct {
@@ -118,7 +125,7 @@ clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
} }
intern :: proc(store: ^Store, candidate: Node) -> Type { 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 { for existing, index in store.nodes {
if existing == candidate { if existing == candidate {
return DYNAMIC_START+Type(index) 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 { named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
normalized_file := file if qualifier != 0 else u32(0) normalized_file := file if qualifier != 0 else u32(0)
for existing, index in store.nodes { 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.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file { existing.file == normalized_file {
return DYNAMIC_START+Type(index) 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 { find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
for existing, index in store.nodes { 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.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index) return DYNAMIC_START+Type(index)
} }
@@ -164,25 +171,53 @@ define_alias :: proc(store: ^Store, id, child: Type) -> bool {
return true 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) 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 return false
} }
index := int(id-DYNAMIC_START) 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].c_layout = c_layout
store.nodes[index].opaque = opaque store.nodes[index].opaque = opaque
store.nodes[index].declared = true 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_start = u32(len(store.fields))
store.nodes[index].field_count = u32(len(fields)) store.nodes[index].field_count = u32(len(fields))
append(&store.fields, ..fields) append(&store.fields, ..fields)
return true 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 { fields_for :: proc(store: ^Store, value: Type) -> []Field {
item, ok := node(store, value) 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 return nil
} }
start := int(item.field_start) start := int(item.field_start)
@@ -350,7 +385,7 @@ is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
value_kind == .Slice || value_kind == .Optional { value_kind == .Slice || value_kind == .Optional {
return true return true
} }
if value_kind == .Struct { if value_kind == .Struct || value_kind == .Union {
item, ok := node(store, value) item, ok := node(store, value)
return ok && item.declared return ok && item.declared
} }
@@ -373,10 +408,64 @@ is_optional :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Optional 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 { is_struct :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Struct 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 { is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value) item, ok := node(store, value)
return ok && item.kind == .Optional && is_pointer(item.child, store) 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 { if value_kind == .Slice || value_kind == .Array || value_kind == .Optional {
return !contains_c_struct_by_value(value, store) return !contains_c_struct_by_value(value, store)
} }
if value_kind == .Struct { if value_kind == .Struct || value_kind == .Union {
item, ok := node(store, value) 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 return false
} }
@@ -409,7 +498,7 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo
return false return false
} }
if item.kind == .Struct { if item.kind == .Struct {
if item.c_layout { if item.opaque || (item.c_layout && item.field_count == 0) {
return true return true
} }
for field in fields_for(store, value) { 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 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 { if item.kind == .Array || item.kind == .Slice || item.kind == .Optional {
return contains_c_struct_by_value(item.child, store, depth+1) 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) { if allow_void && is_void(value) {
return true 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 { 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 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 { is_c_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value) 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( pointer :: proc(
@@ -572,6 +677,47 @@ optional :: proc(store: ^Store, child: Type) -> Type {
return intern(store, Node{kind=.Optional, child=child}) 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 { with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
item, ok := node(store, value) item, ok := node(store, value)
if !ok || item.kind != .Array { 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 { is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value) 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 { 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_size := size(item.child, store, selected)
child_align := u64(alignment_of(item.child, store, selected)) child_align := u64(alignment_of(item.child, store, selected))
return ((child_size+1+child_align-1)/child_align)*child_align return ((child_size+1+child_align-1)/child_align)*child_align
case .Function:
return 0
case .Struct: case .Struct:
item, _ := node(store, value)
if item.explicit_size > 0 {
return item.explicit_size
}
offset: u64 offset: u64
max_align: u64 = 1 max_align: u64 = 1
for field in fields_for(store, value) { 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) max_align = max(max_align, field_align)
} }
return (offset+max_align-1)/max_align*max_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: case:
return 0 return 0
} }
@@ -683,7 +847,23 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
return target.pointer_bits(selected)/8 return target.pointer_bits(selected)/8
case .Array, .Optional: case .Array, .Optional:
return alignment_of(child_type(value, store), store, selected) return alignment_of(child_type(value, store), store, selected)
case .Function:
return 1
case .Struct: 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 result := 1
for field in fields_for(store, value) { for field in fields_for(store, value) {
result = max(result, alignment_of(field.type, store, selected)) 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) 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) @(test)
parser_diagnoses_malformed_sentinel_pointer_types :: proc(t: ^testing.T) { parser_diagnoses_malformed_sentinel_pointer_types :: proc(t: ^testing.T) {
text := `bad :: func(value [*0]u8) void {} text := `bad :: func(value [*0]u8) void {}
@@ -1026,13 +1054,18 @@ main :: func() void {
@(test) @(test)
c_variadic_restrictions_and_extra_argument_types_are_diagnosed :: proc(t: ^testing.T) { 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 requires :: c_func(value c_int, ...) void
native :: func(...) void native :: func(...) void
bodyful :: c_func(...) void {} bodyful :: c_func(...) void {}
main :: func() void { main :: func() void {
values [1]u8 :: [1] values [1]u8 :: [1]
record Record :: Record { value = 1 }
foreign(values) foreign(values)
foreign(record)
requires() requires()
} }
` `
@@ -1072,15 +1105,21 @@ variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T
} }
@(test) @(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 { text := `Defined :: c_struct {
value c_int value c_int
} }
Opaque :: c_struct Opaque :: c_struct
Empty :: c_struct {}
Bad :: c_struct {
values []i32
}
read :: c_func(value @Defined) c_int read :: c_func(value @Defined) c_int
bad_param :: c_func(value Defined) void pass :: c_func(value Defined) Defined
bad_result :: c_func() Defined bad_opaque :: c_func(value Opaque) void
main :: func() void {} main :: func() void {
_ = pass(Defined { value = 1 })
}
` `
source_file := source.Source{path="test.bro", text=text} source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file) diagnostics := source.init_diagnostics(&source_file)
@@ -1094,14 +1133,72 @@ main :: func() void {}
hir_module := checker.check(&ast_module, &diagnostics, &symbols) hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module) defer hir.destroy_module(&hir_module)
found_param := false found_opaque := false
found_result := false found_bad_layout := false
found_empty := false
for diagnostic in diagnostics.items { for diagnostic in diagnostics.items {
found_param = found_param || strings.contains(diagnostic.message, "cannot be passed by value") found_opaque = found_opaque || strings.contains(diagnostic.message, "cannot be passed by value")
found_result = found_result || strings.contains(diagnostic.message, "cannot be returned 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_opaque)
testing.expect(t, found_result) 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) @(test)
@@ -1697,6 +1794,18 @@ restricted_c_header_imports_compile_and_link :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 0) 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) @(test)
unsupported_c_header_members_diagnose_only_when_referenced :: proc(t: ^testing.T) { unsupported_c_header_members_diagnose_only_when_referenced :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-header-unsupported" 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 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 { main :: func() void {
_ = native.imported_add(pass_alias(20), 22) _ = native.imported_add(pass_alias(20), 22)
_ = native.imported_scalar(3, 4) _ = native.imported_scalar(3, 4)
_ = native.child_value(7) _ = native.child_value(7)
_ = native.imported_read(native.imported_handle()?) _ = native.imported_read(native.imported_handle()?)
_ = native.imported_string("hello") _ = native.imported_string("hello")
_ = native.imported_apply(double_value, 21)
_ = call_mapper(double_value)
_ = native.configured_value(9) _ = native.configured_value(9)
signed i8 :: -2 signed i8 :: -2
unsigned u16 :: 3 unsigned u16 :: 3
+30
View File
@@ -7,12 +7,35 @@ typedef int imported_int;
typedef imported_int imported_int_alias; typedef imported_int imported_int_alias;
typedef struct Imported_Handle Imported_Handle; typedef struct Imported_Handle Imported_Handle;
typedef void (*Imported_Callback)(int value); typedef void (*Imported_Callback)(int value);
typedef int (*Imported_Mapper)(int value);
typedef struct Imported_Value { typedef struct Imported_Value {
int value; int value;
} Imported_Value; } Imported_Value;
typedef union Imported_Union { typedef union Imported_Union {
int value; int value;
} Imported_Union; } 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 { typedef enum Imported_Enum {
IMPORTED_ENUM_VALUE, IMPORTED_ENUM_VALUE,
} Imported_Enum; } Imported_Enum;
@@ -22,6 +45,7 @@ unsigned long imported_scalar(unsigned char value, unsigned long extra);
Imported_Handle *imported_handle(void); Imported_Handle *imported_handle(void);
int imported_read(const Imported_Handle *handle); int imported_read(const Imported_Handle *handle);
int imported_string(const char *value); int imported_string(const char *value);
int imported_apply(Imported_Mapper mapper, int value);
Imported_Value imported_by_value(Imported_Value value); Imported_Value imported_by_value(Imported_Value value);
int imported_volatile(volatile int *value); int imported_volatile(volatile int *value);
_Bool imported_bool(_Bool value); _Bool imported_bool(_Bool value);
@@ -37,5 +61,11 @@ int configured_value(int value);
#define IMPORTED_MACRO 42 #define IMPORTED_MACRO 42
int imported_variadic(int marker, ...); 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 #endif
+11
View File
@@ -36,6 +36,17 @@ int imported_string(const char *value) {
return 0; 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, ...) { int imported_variadic(int marker, ...) {
va_list args; va_list args;
va_start(args, marker); va_start(args, marker);
@@ -3,10 +3,17 @@ native :: import "../header/include/native.h"
use_callback :: c_func(value native.Imported_Callback) void use_callback :: c_func(value native.Imported_Callback) void
use_union :: c_func(value native.Imported_Union) void use_union :: c_func(value native.Imported_Union) void
use_enum :: c_func(value native.Imported_Enum) void use_enum :: c_func(value native.Imported_Enum) void
use_opaque :: c_func(value native.Imported_Handle) void
main :: func() void { main :: func() void {
_ = native.IMPORTED_MACRO _ = native.IMPORTED_MACRO
_ = native.imported_by_value() _ = 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_volatile()
_ = native.imported_bool() _ = native.imported_bool()
_ = native.imported_global _ = 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;
}