manual c function interop

This commit is contained in:
2026-06-11 22:46:12 +02:00
parent 2a79010a57
commit a5ceb727c1
20 changed files with 725 additions and 74 deletions
+129 -22
View File
@@ -49,19 +49,20 @@ Symbol_Index_Entry :: struct {
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
module: hir.Module,
specs: [dynamic]Spec,
function_index: []Symbol_Index_Entry,
global_index: []Symbol_Index_Entry,
import_index: []Symbol_Index_Entry,
global_types: []types.Type,
constants: []Constant,
main_symbol: symbol.Id,
sink_symbol: symbol.Id,
allocator: mem.Allocator,
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
module: hir.Module,
specs: [dynamic]Spec,
function_index: []Symbol_Index_Entry,
global_index: []Symbol_Index_Entry,
import_index: []Symbol_Index_Entry,
global_types: []types.Type,
constants: []Constant,
template_diagnostics: []int,
main_symbol: symbol.Id,
sink_symbol: symbol.Id,
allocator: mem.Allocator,
}
symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
@@ -287,7 +288,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
}
validate_declarations :: proc(checker: ^Checker) {
for function in checker.ast_module.functions {
for function, function_id in checker.ast_module.functions {
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
for param in function.params {
@@ -308,6 +309,42 @@ validate_declarations :: proc(checker: ^Checker) {
}
append(&locals, param.name)
}
if !function.has_body && !function.c_abi {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"bodyless function '%s' must use 'c func'",
symbol_text(checker, function.name),
)
}
if !function.has_body && function.c_abi {
for param in function.params {
if type_from_syntax(param.type).kind != .Concrete {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"foreign function '%s' requires concrete parameter types",
symbol_text(checker, function.name),
)
}
}
result := type_from_syntax(function.result)
if result.kind != .Concrete && result.kind != .Void {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"foreign function '%s' requires a concrete or void result type",
symbol_text(checker, function.name),
)
}
if function.pkg == 0 && function.name == checker.main_symbol {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"main must have a body",
)
}
}
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
@@ -318,6 +355,23 @@ validate_declarations :: proc(checker: ^Checker) {
}
delete(locals)
}
for function, function_id in checker.ast_module.functions {
if function.has_body || !function.c_abi {
continue
}
for other, other_id in checker.ast_module.functions {
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
continue
}
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"duplicate foreign symbol '%s'",
symbol_text(checker, function.name),
)
break
}
}
}
find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
@@ -437,6 +491,13 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg :
if template < 0 {
return types.INVALID
}
if checker.template_diagnostics[template] >= 0 {
declared := type_from_syntax(checker.ast_module.functions[template].result)
if declared.kind == .Concrete || declared.kind == .Void {
return declared
}
return types.INVALID
}
args := make([]types.Type, len(expr.args), checker.allocator)
for arg, index in expr.args {
args[index] = infer_expr(checker, arg, locals, pkg, file)
@@ -794,6 +855,9 @@ build_expr :: proc(
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
return invalid_hir_expr(checker, expr.span, id)
}
if checker.template_diagnostics[template] >= 0 {
return invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
}
if len(expr.args) != len(checker.ast_module.functions[template].params) {
id := source.addf(
checker.diagnostics,
@@ -873,6 +937,9 @@ make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
if function.pkg == 0 && function.name == checker.main_symbol {
return fmt.aprintf("main", allocator = checker.allocator)
}
if !function.has_body && function.c_abi {
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
}
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
@@ -940,7 +1007,31 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
append(&params, local_id)
}
problematic := signature_diagnostic >= 0
problematic := signature_diagnostic >= 0 || checker.template_diagnostics[spec.template] >= 0
if !function.has_body {
append(
&checker.module.functions,
hir.Function {
name = function.name,
link_name = make_link_name(checker, spec_id),
calling_convention = .C if function.c_abi else .Brolang,
implementation = .Declaration,
linkage = .External if function.c_abi else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
params = params[:],
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads[:],
calls = calls[:],
problematic = problematic,
diagnostic = checker.template_diagnostics[spec.template],
},
)
delete(locals)
return
}
has_return := false
if signature_diagnostic >= 0 {
append(&body, len(checker.module.statements))
@@ -1288,7 +1379,9 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
hir.Function {
name = function.name,
link_name = make_link_name(checker, spec_id),
c_abi = function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol),
calling_convention = .C if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang,
implementation = .Definition,
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
params = params[:],
result = spec.result,
@@ -1547,7 +1640,9 @@ synthesize_trap_main :: proc(checker: ^Checker) {
hir.Function {
name = checker.main_symbol,
link_name = fmt.aprintf("main", allocator = checker.allocator),
c_abi = true,
calling_convention = .C,
implementation = .Definition,
linkage = .External,
is_main = true,
result = types.VOID,
body = body,
@@ -1566,6 +1661,9 @@ replace_main_with_trap :: proc(checker: ^Checker, diagnostic: int) {
delete(function.body, checker.allocator)
function.params = nil
function.result = types.VOID
function.calling_convention = .C
function.implementation = .Definition
function.linkage = .External
function.problematic = true
function.diagnostic = diagnostic
statement_id := len(checker.module.statements)
@@ -1605,6 +1703,10 @@ check :: proc(
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
checker.template_diagnostics = make([]int, len(ast_module.functions), allocator)
for &diagnostic in checker.template_diagnostics {
diagnostic = -1
}
defer {
for spec in checker.specs {
delete(spec.args, allocator)
@@ -1615,6 +1717,7 @@ check :: proc(
delete(checker.import_index, allocator)
delete(checker.global_types, allocator)
delete(checker.constants, allocator)
delete(checker.template_diagnostics, allocator)
}
for function, index in ast_module.functions {
@@ -1658,13 +1761,17 @@ check :: proc(
} else {
template := ast_module.functions[main_template]
if main_declarations != 1 ||
!template.has_body ||
len(template.params) != 0 ||
!(template.result == .Void || template.result == .I32 || template.result == .Int) {
id := source.add(
diagnostics,
template.span,
"main must be unique, take no parameters, and return void, i32, or int",
)
id := checker.template_diagnostics[main_template]
if id < 0 {
id = source.add(
diagnostics,
template.span,
"main must be unique, have a body, take no parameters, and return void, i32, or int",
)
}
replace_main_with_trap(&checker, id)
}
}