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