function pointers and callbacks

This commit is contained in:
2026-06-15 21:09:46 +02:00
parent 3b7c3fcbd0
commit f5605fd3ec
21 changed files with 1927 additions and 122 deletions
+436 -29
View File
@@ -489,6 +489,71 @@ call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
return type_from_syntax(function.params[index].type)
}
callable_arg_expected :: proc(function_type: types.Type, function_item: types.Node, store: ^types.Store, index: int) -> types.Type {
if index < 0 || index >= int(function_item.field_count) {
return types.INVALID
}
params := types.params_for(store, function_type)
if index >= len(params) {
return types.INVALID
}
return params[index].type
}
valid_callable_arity :: proc(function_item: types.Node, count: int) -> bool {
return count >= int(function_item.field_count) if function_item.variadic else count == int(function_item.field_count)
}
function_value_signature :: proc(
checker: ^Checker,
template: ast.Function_Id,
) -> (params: []types.Type, result: types.Type, ok: bool) {
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
return nil, types.INVALID, false
}
function := checker.ast_module.functions[template]
if !function.c_abi {
return nil, types.INVALID, false
}
result = type_from_syntax(function.result)
if !types.is_void(result) && !is_runtime_type(checker, result) {
return nil, types.INVALID, false
}
params = make([]types.Type, len(function.params), checker.allocator)
for param, index in function.params {
param_type := type_from_syntax(param.type)
if !is_runtime_type(checker, param_type) {
delete(params, checker.allocator)
return nil, types.INVALID, false
}
params[index] = param_type
}
return params, result, true
}
function_pointer_type_for_template :: proc(
checker: ^Checker,
template: ast.Function_Id,
demanded: ^[dynamic]Spec_Id = nil,
) -> (types.Type, Spec_Id, bool) {
params, result, ok := function_value_signature(checker, template)
if !ok {
return types.INVALID, INVALID_SPEC, false
}
defer delete(params, checker.allocator)
function := checker.ast_module.functions[template]
function_type := types.function(&checker.module.types, params, result, true, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, true)
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, template, params)
} else {
spec = find_spec(checker, template, params)
mark_spec_demanded(checker, spec, demanded)
}
return pointer_type, spec, spec != INVALID_SPEC
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names {
if existing == name {
@@ -518,6 +583,9 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
switch expr.kind {
case .Call:
append(&stack, ..expr.args)
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Array, .Struct_Literal, .Slice:
append(&stack, ..expr.args)
if expr.left != ast.INVALID_EXPR {
@@ -600,11 +668,15 @@ validate_declarations :: proc(checker: ^Checker) {
}
if !function.has_body && function.c_abi {
for param in function.params {
if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) !=
param_type := type_from_syntax(param.type)
if add_unsupported_type_diagnostic(checker, param.span, param_type) !=
source.INVALID_DIAGNOSTIC {
continue
}
if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) {
if types.contains_c_struct_by_value(param_type, &checker.module.types) {
continue
}
if !types.is_c_signature_type(param_type, &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
@@ -615,6 +687,7 @@ validate_declarations :: proc(checker: ^Checker) {
}
result := type_from_syntax(function.result)
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
!types.contains_c_struct_by_value(result, &checker.module.types) &&
!types.is_c_signature_type(result, &checker.module.types, true) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
@@ -683,17 +756,43 @@ validate_type_nodes :: proc(checker: ^Checker) {
)
}
}
if item.kind == .Struct {
if item.kind == .Struct || item.kind == .Union {
if item.c_layout && !item.opaque && item.field_count == 0 {
source.add(
checker.diagnostics,
source.Span{},
"c_struct definitions require at least one field",
)
}
for field in types.fields_for(&checker.module.types, id) {
if types.contains_c_struct_by_value(field.type, &checker.module.types) {
if !types.is_runtime_value(field.type, &checker.module.types) {
source.add(
checker.diagnostics,
source.Span{},
"C records may only appear behind pointers",
"record fields must have runtime value types",
)
} else if item.c_layout && !types.is_c_record_field_type(field.type, &checker.module.types) {
source.add(
checker.diagnostics,
source.Span{},
"c_struct fields must have C-layout-compatible types",
)
}
}
}
if item.kind == .Function {
if !item.c_abi {
source.add(checker.diagnostics, source.Span{}, "only c_func function pointer types are supported")
}
for param in types.params_for(&checker.module.types, id) {
if types.is_void(param.type) || !types.is_c_signature_type(param.type, &checker.module.types) {
source.add(checker.diagnostics, source.Span{}, "function pointer parameters must be concrete C signature types")
}
}
if !types.is_c_signature_type(item.child, &checker.module.types, true) {
source.add(checker.diagnostics, source.Span{}, "function pointer results must be concrete C signature types or void")
}
}
}
}
@@ -893,7 +992,8 @@ infer_compound_expr :: proc(
_ = infer_nested_expr(checker, checker.ast_module.exprs[keyed].left, locals, pkg, file, demanded)
}
target_pkg, available := expr_package(checker, expr, pkg, file)
return types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
value := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
return types.resolve_alias(value, store)
case .Keyed:
return infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
case:
@@ -994,6 +1094,20 @@ infer_expr :: proc(
}
}
}
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
template := find_template(checker, expr.name, target_pkg)
if template != ast.INVALID_FUNCTION &&
len(checker.ast_module.functions[template].unsupported_reason) == 0 &&
checker.template_diagnostics[template] == source.INVALID_DIAGNOSTIC {
pointer_type, _, ok := function_pointer_type_for_template(checker, template, demanded)
if ok {
last = pointer_type
}
}
}
}
_ = pop(&stack)
case .Negate:
stack[frame_index].stage = 5
@@ -1002,14 +1116,60 @@ infer_expr :: proc(
stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Call:
if expr.left != ast.INVALID_EXPR {
callee_type := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].left = function_type
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 6
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
} else if valid_callable_arity(function_item, 0) {
last = function_item.child
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
continue
}
target_pkg, available := expr_package(checker, expr, pkg, file)
template := ast.INVALID_FUNCTION
if available {
template = find_template(checker, expr.name, target_pkg)
}
if template == ast.INVALID_FUNCTION {
last = types.INVALID
_ = pop(&stack)
callee_type := types.INVALID
if !symbol.is_valid(expr.qualifier) {
callee_type = find_infer_local(locals, expr.name)
}
if !types.is_valid(callee_type) && available {
global := find_global(checker, expr.name, target_pkg)
if global != ast.INVALID_GLOBAL {
callee_type = checker.global_types[global]
}
}
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
last = types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].left = function_type
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 6
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
} else if valid_callable_arity(function_item, 0) {
last = function_item.child
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
continue
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
@@ -1091,6 +1251,25 @@ infer_expr :: proc(
stack[frame_index].args = nil
_ = pop(&stack)
}
if frame.stage == 6 {
if frame.arg_index < len(expr.args) {
stack[frame_index].args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
continue
}
}
function_item, ok := types.node(&checker.module.types, frame.left)
if ok && function_item.kind == .Function && valid_callable_arity(function_item, len(expr.args)) {
last = function_item.child
} else {
last = types.INVALID
}
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
}
return last
}
@@ -1548,6 +1727,65 @@ find_struct_field :: proc(checker: ^Checker, struct_type: types.Type, name: symb
return 0, {}, false
}
build_function_value :: proc(
checker: ^Checker,
template: ast.Function_Id,
span: source.Span,
expected: types.Type,
) -> hir.Expr_Id {
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
return hir.INVALID_EXPR
}
function := checker.ast_module.functions[template]
if len(function.unsupported_reason) > 0 {
id := source.addf(
checker.diagnostics,
span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, function.name),
function.unsupported_reason,
)
return invalid_hir_expr(checker, span, id)
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
return invalid_hir_expr(checker, span, checker.template_diagnostics[template])
}
params, result, ok := function_value_signature(checker, template)
if !ok {
id := source.addf(
checker.diagnostics,
span,
"function '%s' cannot be used as a C callback; expected a concrete c_func",
symbol_text(checker, function.name),
)
return invalid_hir_expr(checker, span, id)
}
defer delete(params, checker.allocator)
function_type := types.function(&checker.module.types, params, result, true, function.variadic)
pointer_type := types.pointer(&checker.module.types, function_type, false, true)
spec := find_spec(checker, template, params)
if spec == INVALID_SPEC {
id := source.addf(
checker.diagnostics,
span,
"could not resolve callback specialization of '%s'",
symbol_text(checker, function.name),
)
return invalid_hir_expr(checker, span, id, pointer_type)
}
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
return add_hir_expr(checker, hir.Expr{
kind=.Function,
span=span,
type=pointer_type,
target=hir.function_ref(function_id),
left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
build_nested_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
@@ -1776,16 +2014,18 @@ build_compound_expr :: proc(
case .Struct_Literal:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
if !types.is_struct(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque struct type '%s'", symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id)
}
if types.is_c_struct(struct_type, store) {
id := source.add(checker.diagnostics, expr.span, "c_struct values cannot be constructed by value")
struct_type = types.resolve_alias(struct_type, store)
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
id := source.addf(checker.diagnostics, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
return invalid_hir_expr(checker, expr.span, id)
}
fields := types.fields_for(store, struct_type)
values := make([]hir.Expr_Id, len(fields), checker.allocator)
union_record := types.is_union(struct_type, store)
if union_record && len(expr.args) != 1 {
id := source.add(checker.diagnostics, expr.span, "union literal requires exactly one field initializer")
return invalid_hir_expr(checker, expr.span, id, struct_type)
}
values := make([]hir.Expr_Id, 1 if union_record else len(fields), checker.allocator)
initialized := make([]bool, len(fields), checker.allocator)
defer delete(initialized, checker.allocator)
for &value in values {
@@ -1803,18 +2043,35 @@ build_compound_expr :: proc(
continue
}
initialized[index] = true
values[index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file)
values[index] = coerce_expr(checker, values[index], field.type, keyed_expr.span)
value_index := 0 if union_record else index
values[value_index] = build_nested_expr(checker, keyed_expr.left, locals, global_reads, calls, field.type, pkg, file)
values[value_index] = coerce_expr(checker, values[value_index], field.type, keyed_expr.span)
}
for field, index in fields {
if values[index] == hir.INVALID_EXPR {
if !union_record {
for field, index in fields {
if values[index] != hir.INVALID_EXPR {
continue
}
id := source.addf(checker.diagnostics, expr.span, "missing initializer for struct field '%s'", symbol_text(checker, symbol.Id(field.name)))
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, id, struct_type)
}
}
active_field: i64
if union_record {
if values[0] == hir.INVALID_EXPR {
delete(values, checker.allocator)
return invalid_hir_expr(checker, expr.span, source.add(checker.diagnostics, expr.span, "union literal requires a known field"), struct_type)
}
for value, index in initialized {
if value {
active_field = i64(index)
break
}
}
}
return add_hir_expr(checker, hir.Expr{
kind=.Struct, span=expr.span, type=struct_type, args=values,
kind=.Struct, span=expr.span, type=struct_type, args=values, integer=active_field,
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -1938,11 +2195,16 @@ build_expr :: proc(
target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
})
} else {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_name_resolution_diagnostic(checker, expr, target_pkg)
template := find_template(checker, expr.name, target_pkg)
if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi {
last = build_function_value(checker, template, expr.span, frame.expected)
} else {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_name_resolution_diagnostic(checker, expr, target_pkg)
}
last = invalid_hir_expr(checker, expr.span, id)
}
last = invalid_hir_expr(checker, expr.span, id)
}
}
_ = pop(&stack)
@@ -1953,6 +2215,11 @@ build_expr :: proc(
stack[frame_index].stage = 1
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Call:
if expr.left != ast.INVALID_EXPR {
stack[frame_index].stage = 6
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
continue
}
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
@@ -1962,12 +2229,63 @@ build_expr :: proc(
}
template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_call_resolution_diagnostic(checker, expr, target_pkg)
callee := hir.INVALID_EXPR
callee_from_global := false
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
callee = add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id),
left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
}
if callee == hir.INVALID_EXPR {
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
hir_global := hir.Global_Id(global)
add_unique_global(global_reads, hir_global)
callee = add_hir_expr(checker, hir.Expr{
kind=.Global, span=expr.span, type=checker.global_types[global],
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR,
right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
callee_from_global = true
}
}
if callee == hir.INVALID_EXPR {
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_call_resolution_diagnostic(checker, expr, target_pkg)
}
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := add_call_resolution_diagnostic(checker, expr, target_pkg) if callee_from_global else
source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].left = callee
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].stage = 7
if len(expr.args) > 0 {
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
@@ -2143,6 +2461,95 @@ build_expr :: proc(
}
_ = pop(&stack)
}
if frame.stage == 6 {
callee := last
_, function_item, function_type, ok := types.function_pointer(checker.module.exprs[callee].type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if !valid_callable_arity(function_item, len(expr.args)) {
message := "function pointer expects at least %d arguments, got %d" if function_item.variadic else
"function pointer expects %d arguments, got %d"
id := source.addf(checker.diagnostics, expr.span, message, function_item.field_count, len(expr.args))
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].left = callee
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].stage = 7
if len(expr.args) > 0 {
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, 0)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
continue
}
if frame.stage == 7 {
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, _ := types.function_pointer(callee_type, &checker.module.types)
arg_expected := callable_arg_expected(function_type, function_item, &checker.module.types, next)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
continue
}
}
callee_type := checker.module.exprs[frame.left].type
_, function_item, function_type, ok := types.function_pointer(callee_type, &checker.module.types)
if !ok {
id := source.add(checker.diagnostics, expr.span, "call target is not a function pointer")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
fixed_count := int(function_item.field_count)
for index in 0..<min(fixed_count, len(stack[frame_index].built_args)) {
expected_arg := callable_arg_expected(function_type, function_item, &checker.module.types, index)
stack[frame_index].built_args[index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
expected_arg,
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
for index in fixed_count..<len(stack[frame_index].built_args) {
arg := stack[frame_index].built_args[index]
stack[frame_index].built_args[index] = promote_c_vararg_expr(
checker,
arg,
checker.module.exprs[arg].span,
)
}
result := function_item.child
if !types.is_valid(result) {
id := source.add(checker.diagnostics, expr.span, "could not resolve function pointer result type")
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=hir.INVALID_REF,
left=frame.left, right=hir.INVALID_EXPR, args=stack[frame_index].built_args,
diagnostic=source.INVALID_DIAGNOSTIC,
})
stack[frame_index].built_args = nil
}
_ = pop(&stack)
}
}
return last
}
+30
View File
@@ -24,19 +24,41 @@ Type_Kind :: enum u8 {
C_Double,
C_Longdouble,
Pointer,
Array,
Function,
Record,
}
Type :: struct {
kind: Type_Kind,
child: Type_Id,
params: []Type_Id,
record: u32,
count: u64,
mutable: bool,
variadic: bool,
}
Record_Kind :: enum u8 {
Struct,
Union,
}
Field :: struct {
name: string,
type: Type_Id,
offset: u64,
}
Record :: struct {
name: string,
identity: string,
fields: [dynamic]Field,
size: u64,
alignment: u32,
kind: Record_Kind,
complete: bool,
reason: string,
}
Alias :: struct {
@@ -82,9 +104,17 @@ init_result :: proc(allocator := context.allocator) -> Result {
}
destroy_result :: proc(result: ^Result) {
for type_item in result.types {
delete(type_item.params, result.allocator)
}
for record in result.records {
delete(record.name, result.allocator)
delete(record.identity, result.allocator)
for field in record.fields {
delete(field.name, result.allocator)
}
delete(record.fields)
delete(record.reason, result.allocator)
}
for alias in result.aliases {
delete(alias.name, result.allocator)
+155 -9
View File
@@ -43,16 +43,25 @@ Api :: struct {
get_cursor_spelling: proc "c"(CXCursor) -> CXString,
get_cursor_usr: proc "c"(CXCursor) -> CXString,
get_cursor_linkage: proc "c"(CXCursor) -> i32,
get_cursor_definition: proc "c"(CXCursor) -> CXCursor,
is_cursor_definition: proc "c"(CXCursor) -> u32,
get_cursor_type: proc "c"(CXCursor) -> CXType,
get_typedef_underlying_type: proc "c"(CXCursor) -> CXType,
get_type_declaration: proc "c"(CXType) -> CXCursor,
get_canonical_type: proc "c"(CXType) -> CXType,
get_pointee_type: proc "c"(CXType) -> CXType,
get_array_element_type: proc "c"(CXType) -> CXType,
get_array_size: proc "c"(CXType) -> i64,
get_result_type: proc "c"(CXType) -> CXType,
get_num_arg_types: proc "c"(CXType) -> i32,
get_arg_type: proc "c"(CXType, u32) -> CXType,
is_function_type_variadic: proc "c"(CXType) -> u32,
is_const_qualified_type: proc "c"(CXType) -> u32,
is_volatile_qualified_type: proc "c"(CXType) -> u32,
cursor_is_bitfield: proc "c"(CXCursor) -> u32,
cursor_get_offset_of_field: proc "c"(CXCursor) -> i64,
type_get_size_of: proc "c"(CXType) -> i64,
type_get_align_of: proc "c"(CXType) -> i64,
cursor_is_variadic: proc "c"(CXCursor) -> u32,
get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32,
get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic,
@@ -70,6 +79,7 @@ CXCursor_FunctionDecl :: i32(8)
CXCursor_VarDecl :: i32(9)
CXCursor_TypedefDecl :: i32(20)
CXCursor_MacroDefinition :: i32(501)
CXCursor_FieldDecl :: i32(6)
CXLinkage_External :: i32(4)
@@ -97,6 +107,10 @@ CXType_Enum :: i32(106)
CXType_Typedef :: i32(107)
CXType_FunctionNoProto :: i32(110)
CXType_FunctionProto :: i32(111)
CXType_ConstantArray :: i32(112)
CXType_IncompleteArray :: i32(114)
CXType_VariableArray :: i32(115)
CXType_DependentSizedArray :: i32(116)
CXType_Elaborated :: i32(119)
CXType_Attributed :: i32(163)
@@ -135,16 +149,25 @@ load_api_from :: proc(path: string) -> (Api, bool) {
load_proc(&api, "clang_getCursorSpelling", &api.get_cursor_spelling) &&
load_proc(&api, "clang_getCursorUSR", &api.get_cursor_usr) &&
load_proc(&api, "clang_getCursorLinkage", &api.get_cursor_linkage) &&
load_proc(&api, "clang_getCursorDefinition", &api.get_cursor_definition) &&
load_proc(&api, "clang_isCursorDefinition", &api.is_cursor_definition) &&
load_proc(&api, "clang_getCursorType", &api.get_cursor_type) &&
load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) &&
load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) &&
load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) &&
load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) &&
load_proc(&api, "clang_getArrayElementType", &api.get_array_element_type) &&
load_proc(&api, "clang_getArraySize", &api.get_array_size) &&
load_proc(&api, "clang_getResultType", &api.get_result_type) &&
load_proc(&api, "clang_getNumArgTypes", &api.get_num_arg_types) &&
load_proc(&api, "clang_getArgType", &api.get_arg_type) &&
load_proc(&api, "clang_isFunctionTypeVariadic", &api.is_function_type_variadic) &&
load_proc(&api, "clang_isConstQualifiedType", &api.is_const_qualified_type) &&
load_proc(&api, "clang_isVolatileQualifiedType", &api.is_volatile_qualified_type) &&
load_proc(&api, "clang_Cursor_isBitField", &api.cursor_is_bitfield) &&
load_proc(&api, "clang_Cursor_getOffsetOfField", &api.cursor_get_offset_of_field) &&
load_proc(&api, "clang_Type_getSizeOf", &api.type_get_size_of) &&
load_proc(&api, "clang_Type_getAlignOf", &api.type_get_align_of) &&
load_proc(&api, "clang_Cursor_isVariadic", &api.cursor_is_variadic) &&
load_proc(&api, "clang_getNumDiagnostics", &api.get_num_diagnostics) &&
load_proc(&api, "clang_getDiagnostic", &api.get_diagnostic) &&
@@ -228,10 +251,99 @@ add_record :: proc(ctx: ^Context, declaration: CXCursor, preferred_name: string)
name = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
}
index := u32(len(ctx.result.records))
append(&ctx.result.records, Record{name=name, identity=identity})
append(&ctx.result.records, Record{
name=name,
identity=identity,
kind=.Union if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl else .Struct,
reason=fmt.aprintf("", allocator=ctx.allocator),
})
ctx.result.records[index].fields.allocator = ctx.allocator
return index
}
Record_Field_Context :: struct {
ctx: ^Context,
record: u32,
}
visit_record_field :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
context = runtime.default_context()
field_ctx := (^Record_Field_Context)(client_data)
ctx := field_ctx.ctx
if ctx.api.get_cursor_kind(cursor) != CXCursor_FieldDecl {
return CXChildVisit_Continue
}
if len(ctx.result.records[field_ctx.record].reason) > 0 {
return CXChildVisit_Continue
}
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator)
if len(name) == 0 {
delete(name, ctx.allocator)
delete(ctx.result.records[field_ctx.record].reason, ctx.allocator)
ctx.result.records[field_ctx.record].reason = fmt.aprintf("anonymous C record fields are not supported", allocator=ctx.allocator)
return CXChildVisit_Continue
}
field_type := ctx.api.get_cursor_type(cursor)
if ctx.api.cursor_is_bitfield(cursor) != 0 {
delete(name, ctx.allocator)
delete(ctx.result.records[field_ctx.record].reason, ctx.allocator)
ctx.result.records[field_ctx.record].reason = fmt.aprintf("C bitfields are not supported", allocator=ctx.allocator)
return CXChildVisit_Continue
}
if ctx.api.is_const_qualified_type(field_type) != 0 ||
ctx.api.is_volatile_qualified_type(field_type) != 0 {
delete(name, ctx.allocator)
delete(ctx.result.records[field_ctx.record].reason, ctx.allocator)
ctx.result.records[field_ctx.record].reason = fmt.aprintf("qualified C record fields are not supported", allocator=ctx.allocator)
return CXChildVisit_Continue
}
if field_type.kind == CXType_IncompleteArray ||
field_type.kind == CXType_VariableArray ||
field_type.kind == CXType_DependentSizedArray {
delete(name, ctx.allocator)
delete(ctx.result.records[field_ctx.record].reason, ctx.allocator)
ctx.result.records[field_ctx.record].reason = fmt.aprintf("flexible or variable C array fields are not supported", allocator=ctx.allocator)
return CXChildVisit_Continue
}
translated := translate_type(ctx, field_type)
offset_bits := ctx.api.cursor_get_offset_of_field(cursor)
if translated == INVALID_TYPE || offset_bits < 0 || offset_bits%8 != 0 {
delete(name, ctx.allocator)
delete(ctx.result.records[field_ctx.record].reason, ctx.allocator)
ctx.result.records[field_ctx.record].reason = fmt.aprintf("C record field type or layout is not supported", allocator=ctx.allocator)
return CXChildVisit_Continue
}
append(&ctx.result.records[field_ctx.record].fields, Field{name=name, type=translated, offset=u64(offset_bits/8)})
return CXChildVisit_Continue
}
populate_record :: proc(ctx: ^Context, index: u32, declaration: CXCursor) {
if ctx.result.records[index].complete || len(ctx.result.records[index].reason) > 0 {
return
}
definition := declaration
if ctx.api.is_cursor_definition(definition) == 0 {
definition = ctx.api.get_cursor_definition(declaration)
}
if ctx.api.is_cursor_definition(definition) == 0 {
return
}
record_type := ctx.api.get_cursor_type(definition)
size := ctx.api.type_get_size_of(record_type)
alignment := ctx.api.type_get_align_of(record_type)
if size < 0 || alignment <= 0 {
delete(ctx.result.records[index].reason, ctx.allocator)
ctx.result.records[index].reason = fmt.aprintf("C record size or alignment is not supported", allocator=ctx.allocator)
return
}
ctx.result.records[index].kind = .Union if ctx.api.get_cursor_kind(definition) == CXCursor_UnionDecl else .Struct
ctx.result.records[index].size = u64(size)
ctx.result.records[index].alignment = u32(alignment)
field_ctx := Record_Field_Context{ctx=ctx, record=index}
_ = ctx.api.visit_children(definition, visit_record_field, &field_ctx)
ctx.result.records[index].complete = len(ctx.result.records[index].reason) == 0
}
translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := "", depth := 0) -> Type_Id {
if depth > 64 || value.kind == CXType_Invalid || ctx.api.is_volatile_qualified_type(value) != 0 {
return INVALID_TYPE
@@ -259,18 +371,52 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
if child == INVALID_TYPE {
return INVALID_TYPE
}
mutable := ctx.api.is_const_qualified_type(pointee) == 0
if pointee.kind == CXType_FunctionProto {
mutable = false
}
return add_type(ctx, Type{
kind=.Pointer,
child=child,
mutable=ctx.api.is_const_qualified_type(pointee) == 0,
mutable=mutable,
})
case CXType_Record:
declaration := ctx.api.get_type_declaration(value)
if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl {
case CXType_ConstantArray:
count := ctx.api.get_array_size(value)
child := translate_type(ctx, ctx.api.get_array_element_type(value), "", depth+1)
if count <= 0 || child == INVALID_TYPE {
return INVALID_TYPE
}
return add_type(ctx, Type{kind=.Array, child=child, count=u64(count), mutable=true})
case CXType_Record:
declaration := ctx.api.get_type_declaration(value)
record := add_record(ctx, declaration, preferred_record_name)
populate_record(ctx, record, declaration)
return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
case CXType_FunctionProto:
result := translate_type(ctx, ctx.api.get_result_type(value), "", depth+1)
if result == INVALID_TYPE {
return INVALID_TYPE
}
count := ctx.api.get_num_arg_types(value)
if count < 0 {
return INVALID_TYPE
}
params: [dynamic]Type_Id
params.allocator = ctx.allocator
for index in 0..<count {
param := translate_type(ctx, ctx.api.get_arg_type(value, u32(index)), "", depth+1)
append(&params, param)
if param == INVALID_TYPE {
delete(params)
return INVALID_TYPE
}
}
return add_type(ctx, Type{
kind=.Function,
child=result,
params=params[:],
variadic=ctx.api.is_function_type_variadic(value) != 0,
})
case CXType_Typedef:
declaration := ctx.api.get_type_declaration(value)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
@@ -282,7 +428,8 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
return INVALID_TYPE
}
return translate_type(ctx, canonical, preferred_record_name, depth+1)
case CXType_Enum, CXType_FunctionNoProto, CXType_FunctionProto:
case CXType_Enum, CXType_FunctionNoProto,
CXType_IncompleteArray, CXType_VariableArray, CXType_DependentSizedArray:
return INVALID_TYPE
}
return INVALID_TYPE
@@ -373,14 +520,13 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
reason = "typedef underlying type is not supported"
}
add_alias(ctx, name, value, reason)
case CXCursor_StructDecl:
case CXCursor_StructDecl, CXCursor_UnionDecl:
if len(name) > 0 {
record := add_record(ctx, cursor, name)
populate_record(ctx, record, cursor)
value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
add_alias(ctx, name, value)
}
case CXCursor_UnionDecl:
add_unsupported(ctx, name, "C unions are not supported")
case CXCursor_EnumDecl:
add_unsupported(ctx, name, "C enums are not supported")
case CXCursor_VarDecl:
+1
View File
@@ -83,6 +83,7 @@ Expr_Kind :: enum u8 {
Optional_Some,
Local,
Global,
Function,
Address,
Deref,
Index,
+1
View File
@@ -73,6 +73,7 @@ Opcode :: enum u8 {
None,
Optional_Some,
Load_Global,
Function_Address,
Address_Global,
Address_Of,
Alloca,
+494 -30
View File
@@ -22,6 +22,118 @@ Emitter :: struct {
allocator: mem.Allocator,
}
C_Record_ABI_Kind :: enum u8 {
None,
Small_Integer,
Integer_Pair,
Homogeneous_Float,
Indirect,
}
C_Record_ABI :: struct {
kind: C_Record_ABI_Kind,
size: u64,
alignment: int,
float_type: types.Type,
float_count: int,
}
hfa_walk :: proc(value: types.Type, store: ^types.Store, scalar: ^types.Type, count: ^int, depth := 0) -> bool {
if depth > 64 || count^ > 4 {
return false
}
if types.is_float(value, store.selected) {
repr := types.representation(value, store.selected)
if scalar^ == types.INVALID {
scalar^ = repr
}
if scalar^ != repr {
return false
}
count^ += 1
return count^ <= 4
}
item, ok := types.node(store, value)
if !ok || item.kind == .Union {
return false
}
if item.kind == .Array {
for _ in 0..<int(item.count) {
if !hfa_walk(item.child, store, scalar, count, depth+1) {
return false
}
}
return true
}
if item.kind != .Struct {
return false
}
for field in types.fields_for(store, value) {
if !hfa_walk(field.type, store, scalar, count, depth+1) {
return false
}
}
return true
}
c_record_abi :: proc(value: types.Type, store: ^types.Store) -> C_Record_ABI {
if !types.is_record(value, store) {
return {}
}
result := C_Record_ABI{
size=types.size(value, store, store.selected),
alignment=types.alignment_of(value, store, store.selected),
}
scalar := types.INVALID
count := 0
if !types.is_union(value, store) && hfa_walk(value, store, &scalar, &count) && count > 0 {
result.kind = .Homogeneous_Float
result.float_type = scalar
result.float_count = count
return result
}
if result.size <= 8 {
result.kind = .Small_Integer
} else if result.size <= 16 {
result.kind = .Integer_Pair
} else {
result.kind = .Indirect
}
return result
}
c_abi_param_type :: proc(value: types.Type, store: ^types.Store) -> string {
abi := c_record_abi(value, store)
switch abi.kind {
case .None:
return llvm_type(value, store)
case .Small_Integer:
return "i64"
case .Integer_Pair:
return "[2 x i64]"
case .Homogeneous_Float:
return fmt.tprintf("[%d x %s]", abi.float_count, llvm_type(abi.float_type, store))
case .Indirect:
return "ptr"
}
return llvm_type(value, store)
}
c_abi_result_type :: proc(value: types.Type, store: ^types.Store) -> string {
abi := c_record_abi(value, store)
switch abi.kind {
case .None, .Homogeneous_Float:
return llvm_type(value, store)
case .Small_Integer:
return fmt.tprintf("i%d", abi.size*8)
case .Integer_Pair:
return "[2 x i64]"
case .Indirect:
return "void"
}
return llvm_type(value, store)
}
llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
if types.is_void(value) {
return "void"
@@ -40,7 +152,7 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
return "ptr"
}
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
case .Struct:
case .Struct, .Union:
return fmt.tprintf("%%bro.type.%d", value)
}
selected := store.selected if store != nil else target.DEFAULT
@@ -60,6 +172,9 @@ function_result_type :: proc(function: ir.Function, store: ^types.Store) -> stri
if function.is_main {
return "i32"
}
if function.calling_convention == .C {
return c_abi_result_type(function.result, store)
}
return llvm_type(function.result, store)
}
@@ -111,7 +226,7 @@ valid_value :: proc(
}
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
@@ -289,11 +404,70 @@ emit_call_args :: proc(
}
}
emit_pack_c_record_arg :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
value: ir.Instruction_Id,
value_type: types.Type,
call_index, arg_index: int,
) -> string {
abi := c_record_abi(value_type, &emitter.module.types)
if abi.kind == .None {
return fmt.tprintf("%%v%d", value)
}
if abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " %%abi_arg_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, llvm_type(value_type, &emitter.module.types), abi.alignment)
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(value_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, value, value_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %%abi_arg_slot%d_%d\n", call_index, arg_index)
return fmt.tprintf("%%abi_arg_slot%d_%d", call_index, arg_index)
}
abi_type := c_abi_param_type(value_type, &emitter.module.types)
temp_alignment := max(abi.alignment, 8)
fmt.sbprintf(&emitter.builder, " %%abi_arg_value_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, llvm_type(value_type, &emitter.module.types), abi.alignment)
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(value_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, value, value_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %%abi_arg_value_slot%d_%d\n", call_index, arg_index)
fmt.sbprintf(&emitter.builder, " %%abi_arg_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, abi_type, temp_alignment)
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%abi_arg_slot%d_%d\n", abi_type, call_index, arg_index)
fmt.sbprintf(
&emitter.builder,
" call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_arg_slot%d_%d, ptr align %d %%abi_arg_value_slot%d_%d, i64 %d, i1 false)\n",
temp_alignment, call_index, arg_index, abi.alignment, call_index, arg_index, abi.size,
)
fmt.sbprintf(&emitter.builder, " %%abi_arg%d_%d = load %s, ptr %%abi_arg_slot%d_%d\n", call_index, arg_index, abi_type, call_index, arg_index)
return fmt.tprintf("%%abi_arg%d_%d", call_index, arg_index)
}
emit_unpack_c_record :: proc(
emitter: ^Emitter,
value_type: types.Type,
abi_type, abi_name, result_name: string,
tag: int,
) {
abi := c_record_abi(value_type, &emitter.module.types)
if abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %s\n", result_name, llvm_type(value_type, &emitter.module.types), abi_name)
return
}
temp_alignment := max(abi.alignment, 8)
fmt.sbprintf(&emitter.builder, " %%abi_unpack_source_slot%d = alloca %s, align %d\n", tag, abi_type, temp_alignment)
fmt.sbprintf(&emitter.builder, " store %s %s, ptr %%abi_unpack_source_slot%d\n", abi_type, abi_name, tag)
fmt.sbprintf(&emitter.builder, " %%abi_unpack_slot%d = alloca %s, align %d\n", tag, llvm_type(value_type, &emitter.module.types), abi.alignment)
fmt.sbprintf(
&emitter.builder,
" call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_unpack_slot%d, ptr align %d %%abi_unpack_source_slot%d, i64 %d, i1 false)\n",
abi.alignment, tag, temp_alignment, tag, abi.size,
)
fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %%abi_unpack_slot%d\n", result_name, llvm_type(value_type, &emitter.module.types), tag)
}
emit_instruction_stream :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
function: ir.Function,
global_initializer := false,
sret_name := "",
) -> ir.Instruction_Id {
return_value := ir.INVALID_INSTRUCTION
after_return := false
@@ -326,6 +500,8 @@ emit_instruction_stream :: proc(
expected_count = int(item.count)
} else if ok && item.kind == .Struct {
expected_count = int(item.field_count)
} else if ok && item.kind == .Union {
expected_count = 1
} else {
emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type")
continue
@@ -335,6 +511,22 @@ emit_instruction_stream :: proc(
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
if item.kind == .Union {
fields := types.fields_for(&emitter.module.types, instruction.type)
field_index := int(instruction.integer)
if field_index < 0 || field_index >= len(fields) ||
!valid_value(instructions, instruction.args[0], fields[field_index].type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid union aggregate operands")
continue
}
fmt.sbprintf(&emitter.builder, " %%union_slot%d = alloca %s, align %d\n", instruction_index, type_name, types.alignment_of(instruction.type, &emitter.module.types, emitter.module.target))
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%union_slot%d\n", type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(fields[field_index].type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.args[0], fields[field_index].type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %%union_slot%d\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%union_slot%d\n", instruction_index, type_name, instruction_index)
continue
}
total := len(instruction.args) + (1 if item.kind == .Array && item.has_sentinel else 0)
if total == 0 {
fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name)
@@ -425,6 +617,14 @@ emit_instruction_stream :: proc(
global_id,
)
}
case .Function_Address:
function_id := ir.as_function(instruction.target)
if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid function reference")
continue
}
target := emitter.module.functions[function_id]
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr @%s, ptr null\n", instruction_index, target.link_name)
case .Address_Global:
global_id := ir.as_global(instruction.target)
if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) ||
@@ -529,11 +729,15 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference")
continue
}
fmt.sbprintf(
&emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n",
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index,
)
if types.is_union(base_type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 0\n", instruction_index, instruction.a)
} else {
fmt.sbprintf(
&emitter.builder,
" %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n",
instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index,
)
}
case .Load:
if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot")
@@ -868,7 +1072,138 @@ emit_instruction_stream :: proc(
strings.write_string(&emitter.builder, "\n")
case .Call:
function_id := ir.as_function(instruction.target)
if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) {
if function_id == ir.INVALID_FUNCTION {
if !valid_instruction(instructions, instruction.a) ||
!valid_value(instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call target")
continue
}
callee := instructions[instruction.a]
_, function_item, function_type, ok := types.function_pointer(callee.type, &emitter.module.types)
if !ok {
emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call target")
continue
}
param_fields := types.params_for(&emitter.module.types, function_type)
valid_args := (len(instruction.args) >= len(param_fields) if function_item.variadic else
len(instruction.args) == len(param_fields)) &&
(!function_item.variadic || function_item.c_abi)
if valid_args {
for arg, index in instruction.args {
expected := param_fields[index].type if index < len(param_fields) && valid_instruction(instructions, arg) else
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
if index >= len(param_fields) &&
(!types.is_c_vararg_type(expected, &emitter.module.types) ||
!types.equal(types.c_vararg_promotion(expected, emitter.module.target), expected)) {
valid_args = false
break
}
if !valid_value(instructions, arg, expected, &emitter.module.types) {
valid_args = false
break
}
}
}
if !valid_args || !types.equal(instruction.type, function_item.child) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call operands")
continue
}
arg_names := make([]string, len(instruction.args), context.temp_allocator)
for arg, index in instruction.args {
if function_item.c_abi && index < len(param_fields) &&
types.is_record(param_fields[index].type, &emitter.module.types) {
arg_names[index] = emit_pack_c_record_arg(
emitter, instructions, arg, param_fields[index].type, instruction_index, index,
)
}
}
result_abi := C_Record_ABI{}
if function_item.c_abi {
result_abi = c_record_abi(function_item.child, &emitter.module.types)
}
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " %%abi_result_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), result_abi.alignment)
strings.write_string(&emitter.builder, " ")
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
} else if !types.is_void(instruction.type) {
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
} else {
strings.write_string(&emitter.builder, " ")
}
strings.write_string(&emitter.builder, "call ")
if !function_item.c_abi {
strings.write_string(&emitter.builder, "fastcc ")
}
if function_item.c_abi {
extension := c_abi_extension(function_item.child, emitter.module.target)
if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, "%s ", extension)
}
strings.write_string(&emitter.builder, c_abi_result_type(function_item.child, &emitter.module.types))
} else {
strings.write_string(&emitter.builder, llvm_type(function_item.child, &emitter.module.types))
}
if function_item.variadic {
strings.write_string(&emitter.builder, " (")
wrote_type := false
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, "ptr")
wrote_type = true
}
for param in param_fields {
if wrote_type {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, c_abi_param_type(param.type, &emitter.module.types))
wrote_type = true
}
if wrote_type {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, "...)")
}
strings.write_string(&emitter.builder, " ")
write_operand(&emitter.builder, instructions, instruction.a, callee.type, &emitter.module.types)
strings.write_string(&emitter.builder, "(")
wrote_arg := false
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, "ptr sret(%s) align %d %%abi_result_slot%d", llvm_type(function_item.child, &emitter.module.types), result_abi.alignment, instruction_index)
wrote_arg = true
}
for arg, index in instruction.args {
if wrote_arg {
strings.write_string(&emitter.builder, ", ")
}
arg_type := param_fields[index].type if index < len(param_fields) else instructions[arg].type
fixed := index < len(param_fields)
if function_item.c_abi && fixed && types.is_record(arg_type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, "%s %s", c_abi_param_type(arg_type, &emitter.module.types), arg_names[index])
} else {
fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types))
if function_item.c_abi && fixed {
extension := c_abi_extension(arg_type, emitter.module.target)
if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, "%s ", extension)
}
}
write_operand(&emitter.builder, instructions, arg, arg_type, &emitter.module.types)
}
wrote_arg = true
}
strings.write_string(&emitter.builder, ")\n")
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), instruction_index)
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
emit_unpack_c_record(
emitter, function_item.child, c_abi_result_type(function_item.child, &emitter.module.types),
fmt.tprintf("%%abi_result%d", instruction_index), fmt.tprintf("%%v%d", instruction_index),
100000+instruction_index,
)
}
continue
}
if int(function_id) >= len(emitter.module.functions) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid function specialization")
continue
}
@@ -900,7 +1235,25 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands")
continue
}
if !types.is_void(instruction.type) {
arg_names := make([]string, len(instruction.args), context.temp_allocator)
for arg, index in instruction.args {
if target.calling_convention == .C && index < len(target.param_types) &&
types.is_record(target.param_types[index], &emitter.module.types) {
arg_names[index] = emit_pack_c_record_arg(
emitter, instructions, arg, target.param_types[index], instruction_index, index,
)
}
}
result_abi := C_Record_ABI{}
if target.calling_convention == .C {
result_abi = c_record_abi(target.result, &emitter.module.types)
}
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " %%abi_result_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(target.result, &emitter.module.types), result_abi.alignment)
strings.write_string(&emitter.builder, " ")
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index)
} else if !types.is_void(instruction.type) {
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
} else {
strings.write_string(&emitter.builder, " ")
@@ -912,23 +1265,59 @@ emit_instruction_stream :: proc(
emit_function_result(&emitter.builder, target, &emitter.module.types)
if target.variadic {
strings.write_string(&emitter.builder, " (")
wrote_type := false
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, "ptr")
wrote_type = true
}
for param_type, index in target.param_types {
if index > 0 {
if wrote_type || index > 0 {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, llvm_type(param_type, &emitter.module.types))
strings.write_string(&emitter.builder, c_abi_param_type(param_type, &emitter.module.types))
wrote_type = true
}
if len(target.param_types) > 0 {
if wrote_type {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, "...)")
}
fmt.sbprintf(&emitter.builder, " @%s(", target.link_name)
emit_call_args(
&emitter.builder, instructions, instruction.args, target.param_types,
&emitter.module.types, target.calling_convention == .C,
)
wrote_arg := false
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, "ptr sret(%s) align %d %%abi_result_slot%d", llvm_type(target.result, &emitter.module.types), result_abi.alignment, instruction_index)
wrote_arg = true
}
for arg, index in instruction.args {
if wrote_arg {
strings.write_string(&emitter.builder, ", ")
}
arg_type := target.param_types[index] if index < len(target.param_types) else instructions[arg].type
fixed := index < len(target.param_types)
if target.calling_convention == .C && fixed && types.is_record(arg_type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, "%s %s", c_abi_param_type(arg_type, &emitter.module.types), arg_names[index])
} else {
fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types))
if target.calling_convention == .C && fixed {
extension := c_abi_extension(arg_type, emitter.module.target)
if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, "%s ", extension)
}
}
write_operand(&emitter.builder, instructions, arg, arg_type, &emitter.module.types)
}
wrote_arg = true
}
strings.write_string(&emitter.builder, ")\n")
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(target.result, &emitter.module.types), instruction_index)
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
emit_unpack_c_record(
emitter, target.result, c_abi_result_type(target.result, &emitter.module.types),
fmt.tprintf("%%abi_result%d", instruction_index), fmt.tprintf("%%v%d", instruction_index),
100000+instruction_index,
)
}
case .Trap:
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
emit_trap_call(emitter, message)
@@ -937,9 +1326,31 @@ emit_instruction_stream :: proc(
return_value = instruction.a
continue
}
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{}
if result_abi.kind == .Indirect {
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(function.result, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %s\n ret void\n", sret_name)
} else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float {
abi_type := c_abi_result_type(function.result, &emitter.module.types)
temp_alignment := max(result_abi.alignment, 8)
fmt.sbprintf(&emitter.builder, " %%abi_return_value_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(function.result, &emitter.module.types), result_abi.alignment)
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(function.result, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %%abi_return_value_slot%d\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%abi_return_slot%d = alloca %s, align %d\n", instruction_index, abi_type, temp_alignment)
fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%abi_return_slot%d\n", abi_type, instruction_index)
fmt.sbprintf(
&emitter.builder,
" call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_return_slot%d, ptr align %d %%abi_return_value_slot%d, i64 %d, i1 false)\n",
temp_alignment, instruction_index, result_abi.alignment, instruction_index, result_abi.size,
)
fmt.sbprintf(&emitter.builder, " %%abi_return%d = load %s, ptr %%abi_return_slot%d\n ret %s %%abi_return%d\n", instruction_index, abi_type, instruction_index, abi_type, instruction_index)
} else {
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
}
after_return = true
case .Return_Void:
if global_initializer {
@@ -982,7 +1393,7 @@ emit_globals :: proc(emitter: ^Emitter) {
emit_types :: proc(emitter: ^Emitter) {
for item, index in emitter.module.types.nodes {
if item.kind != .Struct {
if item.kind != .Struct && item.kind != .Union {
continue
}
id := types.DYNAMIC_START+types.Type(index)
@@ -991,6 +1402,34 @@ emit_types :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "opaque\n")
continue
}
if item.kind == .Union {
fields := types.fields_for(&emitter.module.types, id)
carrier := types.INVALID
carrier_size: u64
carrier_alignment := 0
for field in fields {
field_alignment := types.alignment_of(field.type, &emitter.module.types, emitter.module.target)
field_size := types.size(field.type, &emitter.module.types, emitter.module.target)
if field_alignment > carrier_alignment ||
(field_alignment == carrier_alignment && field_size > carrier_size) {
carrier = field.type
carrier_size = field_size
carrier_alignment = field_alignment
}
}
total_size := types.size(id, &emitter.module.types, emitter.module.target)
if !types.is_valid(carrier) {
fmt.sbprintf(&emitter.builder, "[%d x i8]\n", total_size)
continue
}
strings.write_string(&emitter.builder, "{ ")
strings.write_string(&emitter.builder, llvm_type(carrier, &emitter.module.types))
if carrier_size < total_size {
fmt.sbprintf(&emitter.builder, ", [%d x i8]", total_size-carrier_size)
}
strings.write_string(&emitter.builder, " }\n")
continue
}
strings.write_string(&emitter.builder, "{ ")
for field, field_index in types.fields_for(&emitter.module.types, id) {
if field_index > 0 {
@@ -1108,24 +1547,38 @@ emit_functions :: proc(emitter: ^Emitter) {
}
emit_function_result(&emitter.builder, function, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " @%s(", function.link_name)
result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{}
wrote_param := false
if result_abi.kind == .Indirect {
fmt.sbprintf(
&emitter.builder, "ptr sret(%s) align %d",
llvm_type(function.result, &emitter.module.types), result_abi.alignment,
)
if function.implementation != .Declaration {
strings.write_string(&emitter.builder, " %abi_sret")
}
wrote_param = true
}
for param_type, index in function.param_types {
if index > 0 {
if wrote_param || index > 0 {
strings.write_string(&emitter.builder, ", ")
}
if function.implementation == .Declaration {
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
} else {
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
}
if function.calling_convention == .C {
type_name := c_abi_param_type(param_type, &emitter.module.types) if function.calling_convention == .C else llvm_type(param_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "%s", type_name)
if function.calling_convention == .C && !types.is_record(param_type, &emitter.module.types) {
extension := c_abi_extension(param_type, emitter.module.target)
if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, " %s", extension)
}
}
if function.implementation != .Declaration {
fmt.sbprintf(&emitter.builder, " %%v%d", index)
if function.calling_convention == .C && types.is_record(param_type, &emitter.module.types) {
fmt.sbprintf(&emitter.builder, " %%abi_p%d", index)
} else {
fmt.sbprintf(&emitter.builder, " %%v%d", index)
}
}
wrote_param = true
}
if function.variadic {
if len(function.param_types) > 0 {
@@ -1138,7 +1591,18 @@ emit_functions :: proc(emitter: ^Emitter) {
continue
}
strings.write_string(&emitter.builder, ") {\nentry:\n")
_ = emit_instruction_stream(emitter, function.instructions, function)
if function.calling_convention == .C {
for param_type, index in function.param_types {
if !types.is_record(param_type, &emitter.module.types) {
continue
}
emit_unpack_c_record(
emitter, param_type, c_abi_param_type(param_type, &emitter.module.types),
fmt.tprintf("%%abi_p%d", index), fmt.tprintf("%%v%d", index), 200000+index,
)
}
}
_ = emit_instruction_stream(emitter, function.instructions, function, sret_name="%abi_sret")
strings.write_string(&emitter.builder, "}\n\n")
}
}
@@ -1163,7 +1627,7 @@ emit_messages :: proc(emitter: ^Emitter) {
}
emit_declarations :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\n")
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\n")
widths := [?]int{8, 16, 32, 64}
for bits in widths {
strings.write_string(&emitter.builder, "declare { i")
+168 -4
View File
@@ -168,6 +168,26 @@ translate_c_type :: proc(
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
translated = types.optional(&state.module.type_store, pointer)
}
case .Array:
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if types.is_valid(child) {
translated = types.array(&state.module.type_store, child, item.count, item.mutable)
}
case .Function:
params := make([]types.Type, len(item.params), state.allocator)
for param, index in item.params {
params[index] = translate_c_type(state, result, param, pkg, record_mapping, type_mapping)
if !types.is_valid(params[index]) {
delete(params, state.allocator)
type_mapping[value] = types.INVALID
return types.INVALID
}
}
result_type := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if types.is_valid(result_type) {
translated = types.function(&state.module.type_store, params, result_type, true, item.variadic)
}
delete(params, state.allocator)
case .Record:
if int(item.record) < len(record_mapping) {
translated = record_mapping[item.record]
@@ -189,6 +209,93 @@ function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, resul
return true
}
RECORD_DEPENDENCY_PENDING :: "C record contains an incomplete or unsupported record field"
record_layout_reason :: proc(
store: ^types.Store,
record: cimport.Record,
fields: []types.Field,
selected: target.Target,
) -> string {
if len(fields) == 0 {
if record.size > 0 {
return "anonymous C record fields are not supported"
}
return "empty C records are not supported"
}
if len(fields) != len(record.fields) || record.alignment == 0 {
return "C record metadata is incomplete"
}
size: u64
alignment: u64 = 1
if record.kind == .Union {
for field in fields {
if !types.is_runtime_value(field.type, store) {
return RECORD_DEPENDENCY_PENDING
}
if field.offset != 0 {
return "non-natural C record layouts are not supported"
}
size = max(size, types.size(field.type, store, selected))
alignment = max(alignment, u64(types.alignment_of(field.type, store, selected)))
}
} else {
offset: u64
for field in fields {
if !types.is_runtime_value(field.type, store) {
return RECORD_DEPENDENCY_PENDING
}
field_alignment := u64(types.alignment_of(field.type, store, selected))
offset = (offset+field_alignment-1)/field_alignment*field_alignment
if field.offset != offset {
if field.offset < offset {
return "packed C records are not supported"
}
return "non-natural C record layouts are not supported"
}
offset += types.size(field.type, store, selected)
alignment = max(alignment, field_alignment)
}
size = offset
}
size = (size+alignment-1)/alignment*alignment
if u64(record.alignment) > alignment {
return "over-aligned C records are not supported"
}
if u64(record.alignment) < alignment || record.size < size {
return "packed C records are not supported"
}
if record.size != size {
return "non-natural C record layouts are not supported"
}
return ""
}
c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id, depth := 0) -> string {
if depth > 64 || value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
return ""
}
item := result.types[value]
#partial switch item.kind {
case .Pointer:
return ""
case .Array:
return c_record_by_value_reason(result, item.child, depth+1)
case .Record:
if int(item.record) >= len(result.records) {
return "C record metadata is incomplete"
}
record := result.records[item.record]
if len(record.reason) > 0 {
return record.reason
}
if !record.complete {
return "incomplete C records are pointer-only"
}
}
return ""
}
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator)
if !ok {
@@ -229,7 +336,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
name = fmt.tprintf("__c_record_%d", len(state.record_types))
}
record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name)))
_ = types.define_struct(&state.module.type_store, record_type, nil, true, true)
_ = types.define_record(&state.module.type_store, record_type, nil, true, true, record.kind == .Union)
append(&state.record_identities, strings.clone(record.identity, state.allocator))
append(&state.record_types, record_type)
}
@@ -238,6 +345,42 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
type_mapping := make([]types.Type, len(result.types), state.allocator)
defer delete(type_mapping, state.allocator)
for _ in 0..<len(result.records) {
changed := false
for record, record_index in result.records {
if !record.complete || len(record.reason) > 0 {
continue
}
record_type := record_mapping[record_index]
item, ok := types.node(&state.module.type_store, record_type)
if !ok || (item.declared && !item.opaque) {
continue
}
fields := make([]types.Field, len(record.fields), state.allocator)
for field, field_index in record.fields {
fields[field_index] = types.Field{
name=u32(symbol.intern(state.symbols, field.name)),
type=translate_c_type(state, &result, field.type, pkg_id, record_mapping, type_mapping),
offset=field.offset,
}
}
layout_reason := record_layout_reason(&state.module.type_store, record, fields, state.selected)
if len(layout_reason) == 0 {
changed = types.define_record(
&state.module.type_store, record_type, fields, true, false,
record.kind == .Union, record.size, record.alignment,
) || changed
} else if layout_reason != RECORD_DEPENDENCY_PENDING && len(record.reason) == 0 {
delete(result.records[record_index].reason, result.allocator)
result.records[record_index].reason = fmt.aprintf("%s", layout_reason, allocator=result.allocator)
}
delete(fields, state.allocator)
}
if !changed {
break
}
}
for alias in result.aliases {
name := symbol.intern(state.symbols, alias.name)
id := types.named(&state.module.type_store, u32(pkg_id), u32(name))
@@ -263,17 +406,28 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
}
function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping)
unsupported_reason := function.reason
if len(unsupported_reason) == 0 {
for param_type in function.params {
if reason := c_record_by_value_reason(&result, param_type); len(reason) > 0 {
unsupported_reason = reason
break
}
}
}
if len(unsupported_reason) == 0 {
unsupported_reason = c_record_by_value_reason(&result, function.result)
}
if len(unsupported_reason) == 0 {
for param in params {
if types.contains_c_struct_by_value(param.type, &state.module.type_store) {
unsupported_reason = "C records passed by value are not supported"
unsupported_reason = "C record parameter is incomplete or has an unsupported layout"
break
}
}
}
if len(unsupported_reason) == 0 &&
types.contains_c_struct_by_value(function_result, &state.module.type_store) {
unsupported_reason = "C records returned by value are not supported"
unsupported_reason = "C record result is incomplete or has an unsupported layout"
}
name := symbol.intern(state.symbols, function.name)
duplicate := false
@@ -522,7 +676,7 @@ canonical_type :: proc(
mapping[index] = value if !types.is_valid(resolved) else resolved
return mapping[index]
}
if item.kind == .Struct {
if item.kind == .Struct || item.kind == .Union {
mapping[index] = value
fields := types.fields_for(&module.type_store, value)
for &field in fields {
@@ -530,6 +684,16 @@ canonical_type :: proc(
}
return value
}
if item.kind == .Function {
params := make([]types.Type, int(item.field_count), context.temp_allocator)
for param, param_index in types.params_for(&module.type_store, value) {
params[param_index] = canonical_type(module, param.type, mapping, visiting)
}
result := canonical_type(module, item.child, mapping, visiting)
resolved := types.function(&module.type_store, params, result, item.c_abi, item.variadic)
mapping[index] = resolved
return resolved
}
if types.is_valid(item.child) {
item.child = canonical_type(module, item.child, mapping, visiting)
}
+41 -4
View File
@@ -156,7 +156,7 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
args[index] = lower_nested_expr(state, arg)
}
return append_instruction(state, ir.Instruction{
op=.Aggregate, span=expr.span, type=expr.type, args=args,
op=.Aggregate, span=expr.span, type=expr.type, args=args, integer=expr.integer,
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -332,6 +332,19 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Function:
function := hir.as_function(expr.target)
if function == hir.INVALID_FUNCTION || int(function) >= len(state.hir_module.functions) {
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
} else {
last = append_instruction(state, ir.Instruction{
op=.Function_Address, span=expr.span, type=expr.type,
target=ir.function_ref(ir.Function_Id(function)),
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
_ = pop(&stack)
case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
@@ -343,7 +356,17 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call:
function := hir.as_function(expr.target)
if function == hir.INVALID_FUNCTION || int(function) >= len(state.hir_module.functions) {
if function == hir.INVALID_FUNCTION {
if expr.left == hir.INVALID_EXPR {
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
_ = pop(&stack)
continue
}
stack[frame_index].stage = 6
append(&stack, Lower_Expr_Frame{expr=expr.left})
continue
}
if int(function) >= len(state.hir_module.functions) {
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
_ = pop(&stack)
continue
@@ -356,6 +379,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}
continue
}
if frame.stage == 6 {
stack[frame_index].left = last
stack[frame_index].args = make([]ir.Instruction_Id, len(expr.args), state.allocator)
stack[frame_index].stage = 4
if len(expr.args) > 0 {
append(&stack, Lower_Expr_Frame{expr=expr.args[0]})
}
continue
}
if frame.stage == 5 {
last = append_instruction(state, ir.Instruction{
op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF,
@@ -405,9 +437,14 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
continue
}
}
function := hir.as_function(expr.target)
callee := frame.left
if function != hir.INVALID_FUNCTION {
callee = ir.INVALID_INSTRUCTION
}
last = append_instruction(state, ir.Instruction{
op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(hir.as_function(expr.target))),
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC,
op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(function)),
a=callee, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC,
})
stack[frame_index].args = nil
_ = pop(&stack)
+48 -6
View File
@@ -90,7 +90,7 @@ is_type_token :: proc(kind: token.Kind) -> bool {
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
.Keyword_Void, .Identifier, .Question, .At, .Star, .Left_Bracket:
.Keyword_Void, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket:
return true
}
return false
@@ -291,6 +291,25 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
case .Keyword_Void:
advance(parser)
return types.VOID
case .Keyword_C_Func:
advance(parser)
if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(' after c_func type")
return types.INVALID
}
params, variadic := parse_params(parser)
if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after function type parameters")
}
result := parse_type(parser)
param_types := make([]types.Type, len(params), parser.module.allocator)
for param, index in params {
param_types[index] = param.type
}
function_type := types.function(&parser.module.type_store, param_types, result, true, variadic)
delete(param_types, parser.module.allocator)
delete(params, parser.module.allocator)
return function_type
case .Identifier:
first := advance(parser)
name := first
@@ -334,11 +353,7 @@ skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
return span_from(start.span, end.span)
}
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id {
if nesting >= MAX_EXPRESSION_NESTING {
span := skip_parenthesized(parser)
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
}
parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.Token) {
left_paren := advance(parser)
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
@@ -359,6 +374,15 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
source.add(parser.diagnostics, current(parser).span, "expected ')' after call arguments")
right_paren = left_paren
}
return args[:], right_paren
}
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id {
if nesting >= MAX_EXPRESSION_NESTING {
span := skip_parenthesized(parser)
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
}
args, right_paren := parse_call_args(parser, nesting)
return add_expr(parser, ast.Expr{
kind=.Call,
span=source.Span{file=name.span.file, start=first.span.start, end=right_paren.span.end},
@@ -735,6 +759,24 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
}
continue
}
if current(parser).kind == .Left_Paren {
if nesting >= MAX_EXPRESSION_NESTING {
span := skip_parenthesized(parser)
left = invalid_expr(parser, span, "expression nesting exceeds 256 levels")
continue
}
left_expr := parser.module.exprs[left]
args, right_paren := parse_call_args(parser, nesting)
left = add_expr(parser, ast.Expr{
kind=.Call,
span=span_from(left_expr.span, right_paren.span),
args=args,
left=left,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
continue
}
left_power, right_power, ok := infix_binding_power(current(parser).kind)
if !ok || left_power < minimum_binding_power {
break
+196 -16
View File
@@ -58,9 +58,11 @@ Kind :: enum u8 {
Pointer,
Slice,
Optional,
Function,
Named,
Alias,
Struct,
Union,
}
Node :: struct {
@@ -68,8 +70,10 @@ Node :: struct {
child: Type,
count: u64,
sentinel: u64,
explicit_size: u64,
field_start: u32,
field_count: u32,
explicit_alignment: u32,
pkg: u32,
name: u32,
qualifier: u32,
@@ -78,14 +82,17 @@ Node :: struct {
many: bool,
has_sentinel: bool,
inferred_count: bool,
c_abi: bool,
variadic: bool,
c_layout: bool,
opaque: bool,
declared: bool,
}
Field :: struct {
name: u32,
type: Type,
name: u32,
type: Type,
offset: u64,
}
Store :: struct {
@@ -118,7 +125,7 @@ clone_store :: proc(source: ^Store, allocator := context.allocator) -> Store {
}
intern :: proc(store: ^Store, candidate: Node) -> Type {
if candidate.kind != .Struct && candidate.kind != .Named {
if candidate.kind != .Struct && candidate.kind != .Union && candidate.kind != .Named {
for existing, index in store.nodes {
if existing == candidate {
return DYNAMIC_START+Type(index)
@@ -133,7 +140,7 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
normalized_file := file if qualifier != 0 else u32(0)
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) &&
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file {
return DYNAMIC_START+Type(index)
@@ -144,7 +151,7 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) &&
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct || existing.kind == .Union) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index)
}
@@ -164,25 +171,53 @@ define_alias :: proc(store: ^Store, id, child: Type) -> bool {
return true
}
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
define_record :: proc(
store: ^Store,
id: Type,
fields: []Field,
c_layout, opaque: bool,
is_union := false,
explicit_size: u64 = 0,
explicit_alignment: u32 = 0,
) -> bool {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared {
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
(existing.declared && !existing.opaque) {
return false
}
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Struct
store.nodes[index].kind = .Union if is_union else .Struct
store.nodes[index].c_layout = c_layout
store.nodes[index].opaque = opaque
store.nodes[index].declared = true
store.nodes[index].explicit_size = explicit_size
store.nodes[index].explicit_alignment = explicit_alignment
store.nodes[index].field_start = u32(len(store.fields))
store.nodes[index].field_count = u32(len(fields))
append(&store.fields, ..fields)
return true
}
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
return define_record(store, id, fields, c_layout, opaque)
}
fields_for :: proc(store: ^Store, value: Type) -> []Field {
item, ok := node(store, value)
if !ok || item.kind != .Struct {
if !ok || (item.kind != .Struct && item.kind != .Union) {
return nil
}
start := int(item.field_start)
end := start+int(item.field_count)
if start < 0 || end > len(store.fields) {
return nil
}
return store.fields[start:end]
}
params_for :: proc(store: ^Store, value: Type) -> []Field {
item, ok := node(store, value)
if !ok || item.kind != .Function {
return nil
}
start := int(item.field_start)
@@ -350,7 +385,7 @@ is_concrete :: proc(value: Type, store: ^Store = nil) -> bool {
value_kind == .Slice || value_kind == .Optional {
return true
}
if value_kind == .Struct {
if value_kind == .Struct || value_kind == .Union {
item, ok := node(store, value)
return ok && item.declared
}
@@ -373,10 +408,64 @@ is_optional :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Optional
}
is_function :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Function
}
is_struct :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Struct
}
is_record :: proc(value: Type, store: ^Store) -> bool {
value_kind := kind(value, store)
return value_kind == .Struct || value_kind == .Union
}
is_union :: proc(value: Type, store: ^Store) -> bool {
return kind(value, store) == .Union
}
resolve_alias :: proc(value: Type, store: ^Store, depth := 0) -> Type {
if depth > 64 {
return INVALID
}
item, ok := node(store, value)
if !ok || item.kind != .Alias {
return value
}
return resolve_alias(item.child, store, depth+1)
}
is_c_record_field_type :: proc(value: Type, store: ^Store, depth := 0) -> bool {
if depth > 256 {
return false
}
resolved := resolve_alias(value, store)
if is_concrete_scalar(resolved) || is_pointer(resolved, store) || is_optional_pointer(resolved, store) {
return true
}
item, ok := node(store, resolved)
if !ok {
return false
}
if item.kind == .Array {
return item.count > 0 && !item.has_sentinel && !item.inferred_count &&
is_c_record_field_type(item.child, store, depth+1)
}
if item.kind != .Struct && item.kind != .Union {
return false
}
if !item.c_layout || !item.declared || item.opaque || item.field_count == 0 {
return false
}
for field in fields_for(store, resolved) {
if !is_c_record_field_type(field.type, store, depth+1) {
return false
}
}
return true
}
is_optional_pointer :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && item.kind == .Optional && is_pointer(item.child, store)
@@ -390,9 +479,9 @@ is_runtime_value :: proc(value: Type, store: ^Store) -> bool {
if value_kind == .Slice || value_kind == .Array || value_kind == .Optional {
return !contains_c_struct_by_value(value, store)
}
if value_kind == .Struct {
if value_kind == .Struct || value_kind == .Union {
item, ok := node(store, value)
return ok && item.declared && !item.opaque && !contains_c_struct_by_value(value, store)
return ok && item.declared && !item.opaque && (!item.c_layout || item.field_count > 0)
}
return false
}
@@ -409,7 +498,7 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo
return false
}
if item.kind == .Struct {
if item.c_layout {
if item.opaque || (item.c_layout && item.field_count == 0) {
return true
}
for field in fields_for(store, value) {
@@ -419,6 +508,9 @@ contains_c_struct_by_value :: proc(value: Type, store: ^Store, depth := 0) -> bo
}
return false
}
if item.kind == .Union {
return item.opaque || (item.c_layout && item.field_count == 0)
}
if item.kind == .Array || item.kind == .Slice || item.kind == .Optional {
return contains_c_struct_by_value(item.child, store, depth+1)
}
@@ -429,7 +521,8 @@ is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) ->
if allow_void && is_void(value) {
return true
}
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store)
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store) ||
(is_c_struct(value, store) && is_runtime_value(value, store))
}
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
@@ -518,9 +611,21 @@ container :: proc(value: Type, store: ^Store) -> (Node, bool) {
return array_node, true
}
function_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, function_item: Node, function_type: Type, ok: bool) {
pointer_node, pointer_ok := node(store, value)
if !pointer_ok || pointer_node.kind != .Pointer {
return {}, {}, INVALID, false
}
function_node, function_ok := node(store, pointer_node.child)
if !function_ok || function_node.kind != .Function {
return {}, {}, INVALID, false
}
return pointer_node, function_node, pointer_node.child, true
}
is_c_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && item.kind == .Struct && item.c_layout
return ok && (item.kind == .Struct || item.kind == .Union) && item.c_layout
}
pointer :: proc(
@@ -572,6 +677,47 @@ optional :: proc(store: ^Store, child: Type) -> Type {
return intern(store, Node{kind=.Optional, child=child})
}
function_params_equal :: proc(store: ^Store, item: Node, params: []Type) -> bool {
if item.field_count != u32(len(params)) {
return false
}
start := int(item.field_start)
end := start+int(item.field_count)
if start < 0 || end > len(store.fields) {
return false
}
for param, index in params {
if store.fields[start+index].type != param {
return false
}
}
return true
}
function :: proc(store: ^Store, params: []Type, result: Type, c_abi, variadic: bool) -> Type {
for existing, index in store.nodes {
if existing.kind == .Function &&
existing.child == result &&
existing.c_abi == c_abi &&
existing.variadic == variadic &&
function_params_equal(store, existing, params) {
return DYNAMIC_START+Type(index)
}
}
start := len(store.fields)
for param in params {
append(&store.fields, Field{type=param})
}
return intern(store, Node{
kind=.Function,
child=result,
field_start=u32(start),
field_count=u32(len(params)),
c_abi=c_abi,
variadic=variadic,
})
}
with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type {
item, ok := node(store, value)
if !ok || item.kind != .Array {
@@ -638,7 +784,7 @@ can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool {
is_opaque_struct :: proc(value: Type, store: ^Store) -> bool {
item, ok := node(store, value)
return ok && item.kind == .Struct && item.opaque
return ok && (item.kind == .Struct || item.kind == .Union) && item.opaque
}
size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
@@ -660,7 +806,13 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
child_size := size(item.child, store, selected)
child_align := u64(alignment_of(item.child, store, selected))
return ((child_size+1+child_align-1)/child_align)*child_align
case .Function:
return 0
case .Struct:
item, _ := node(store, value)
if item.explicit_size > 0 {
return item.explicit_size
}
offset: u64
max_align: u64 = 1
for field in fields_for(store, value) {
@@ -670,6 +822,18 @@ size :: proc(value: Type, store: ^Store, selected := target.DEFAULT) -> u64 {
max_align = max(max_align, field_align)
}
return (offset+max_align-1)/max_align*max_align
case .Union:
item, _ := node(store, value)
if item.explicit_size > 0 {
return item.explicit_size
}
result: u64
max_align: u64 = 1
for field in fields_for(store, value) {
result = max(result, size(field.type, store, selected))
max_align = max(max_align, u64(alignment_of(field.type, store, selected)))
}
return (result+max_align-1)/max_align*max_align
case:
return 0
}
@@ -683,7 +847,23 @@ alignment_of :: proc(value: Type, store: ^Store, selected := target.DEFAULT) ->
return target.pointer_bits(selected)/8
case .Array, .Optional:
return alignment_of(child_type(value, store), store, selected)
case .Function:
return 1
case .Struct:
item, _ := node(store, value)
if item.explicit_alignment > 0 {
return int(item.explicit_alignment)
}
result := 1
for field in fields_for(store, value) {
result = max(result, alignment_of(field.type, store, selected))
}
return result
case .Union:
item, _ := node(store, value)
if item.explicit_alignment > 0 {
return int(item.explicit_alignment)
}
result := 1
for field in fields_for(store, value) {
result = max(result, alignment_of(field.type, store, selected))