manual c function interop
This commit is contained in:
@@ -66,6 +66,7 @@ Function :: struct {
|
||||
pkg: int,
|
||||
file: int,
|
||||
c_abi: bool,
|
||||
has_body: bool,
|
||||
params: []Param,
|
||||
result: Type_Syntax,
|
||||
body: []int,
|
||||
|
||||
@@ -1,25 +1,59 @@
|
||||
package backend
|
||||
|
||||
import "../linker"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:os/os2"
|
||||
import "core:strings"
|
||||
|
||||
compile :: proc(llvm_path, output_path: string) -> bool {
|
||||
append_owned :: proc(command: ^[dynamic]string, value: string, allocator: mem.Allocator) {
|
||||
append(command, strings.clone(value, allocator))
|
||||
}
|
||||
|
||||
build_command :: proc(
|
||||
llvm_path, output_path: string,
|
||||
link_arguments: []linker.Argument,
|
||||
allocator := context.allocator,
|
||||
) -> []string {
|
||||
command: [dynamic]string
|
||||
command.allocator = allocator
|
||||
append_owned(&command, "/usr/bin/env", allocator)
|
||||
append_owned(&command, "ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache", allocator)
|
||||
append_owned(&command, "ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache", allocator)
|
||||
append_owned(&command, "zig", allocator)
|
||||
append_owned(&command, "cc", allocator)
|
||||
append_owned(&command, "-Wno-override-module", allocator)
|
||||
append_owned(&command, llvm_path, allocator)
|
||||
for argument in link_arguments {
|
||||
switch argument.kind {
|
||||
case .Input:
|
||||
append_owned(&command, argument.value, allocator)
|
||||
case .Library_Path:
|
||||
append(&command, fmt.aprintf("-L%s", argument.value, allocator=allocator))
|
||||
case .Library:
|
||||
append(&command, fmt.aprintf("-l%s", argument.value, allocator=allocator))
|
||||
}
|
||||
}
|
||||
append_owned(&command, "-o", allocator)
|
||||
append_owned(&command, output_path, allocator)
|
||||
return command[:]
|
||||
}
|
||||
|
||||
destroy_command :: proc(command: []string, allocator := context.allocator) {
|
||||
for argument in command {
|
||||
delete(argument, allocator)
|
||||
}
|
||||
delete(command, allocator)
|
||||
}
|
||||
|
||||
compile :: proc(llvm_path, output_path: string, link_arguments: []linker.Argument = nil) -> bool {
|
||||
pid := os2.get_pid()
|
||||
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
|
||||
defer _ = os.remove(temporary_output)
|
||||
|
||||
command := []string{
|
||||
"/usr/bin/env",
|
||||
"ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache",
|
||||
"ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache",
|
||||
"zig",
|
||||
"cc",
|
||||
"-Wno-override-module",
|
||||
llvm_path,
|
||||
"-o",
|
||||
temporary_output,
|
||||
}
|
||||
command := build_command(llvm_path, temporary_output, link_arguments)
|
||||
defer destroy_command(command)
|
||||
state, stdout, stderr, err := os2.process_exec(
|
||||
os2.Process_Desc{command=command},
|
||||
context.allocator,
|
||||
|
||||
+129
-22
@@ -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(¶ms, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package compiler
|
||||
import "./backend"
|
||||
import "./checker"
|
||||
import "./llvm"
|
||||
import "./linker"
|
||||
import "./loader"
|
||||
import "./lower"
|
||||
import "./opt"
|
||||
@@ -13,7 +14,7 @@ import vmem "core:mem/virtual"
|
||||
import "core:os"
|
||||
import "core:os/os2"
|
||||
|
||||
compile_package :: proc(input_path, output_path: string) -> int {
|
||||
compile_package :: proc(input_path, output_path: string, link_arguments: []linker.Argument = nil) -> int {
|
||||
sources := source.init_store()
|
||||
defer source.destroy_store(&sources)
|
||||
diagnostics := source.init_store_diagnostics(&sources)
|
||||
@@ -76,7 +77,7 @@ compile_package :: proc(input_path, output_path: string) -> int {
|
||||
}
|
||||
|
||||
source.print_all(&diagnostics)
|
||||
if !backend.compile(llvm_path, output_path) {
|
||||
if !backend.compile(llvm_path, output_path, link_arguments) {
|
||||
return 2
|
||||
}
|
||||
if len(diagnostics.items) > 0 {
|
||||
|
||||
+18
-1
@@ -7,6 +7,21 @@ import "core:mem"
|
||||
|
||||
INVALID_ID :: -1
|
||||
|
||||
Calling_Convention :: enum {
|
||||
Brolang,
|
||||
C,
|
||||
}
|
||||
|
||||
Implementation :: enum {
|
||||
Definition,
|
||||
Declaration,
|
||||
}
|
||||
|
||||
Linkage :: enum {
|
||||
Internal,
|
||||
External,
|
||||
}
|
||||
|
||||
Expr_Kind :: enum {
|
||||
Invalid,
|
||||
Integer,
|
||||
@@ -56,7 +71,9 @@ Stmt :: struct {
|
||||
Function :: struct {
|
||||
name: symbol.Id,
|
||||
link_name: string,
|
||||
c_abi: bool,
|
||||
calling_convention: Calling_Convention,
|
||||
implementation: Implementation,
|
||||
linkage: Linkage,
|
||||
is_main: bool,
|
||||
params: []int,
|
||||
result: types.Type,
|
||||
|
||||
+24
-7
@@ -7,6 +7,21 @@ import "core:mem"
|
||||
|
||||
INVALID_ID :: -1
|
||||
|
||||
Calling_Convention :: enum {
|
||||
Brolang,
|
||||
C,
|
||||
}
|
||||
|
||||
Implementation :: enum {
|
||||
Definition,
|
||||
Declaration,
|
||||
}
|
||||
|
||||
Linkage :: enum {
|
||||
Internal,
|
||||
External,
|
||||
}
|
||||
|
||||
Opcode :: enum {
|
||||
Param,
|
||||
Const,
|
||||
@@ -35,13 +50,15 @@ Instruction :: struct {
|
||||
}
|
||||
|
||||
Function :: struct {
|
||||
link_name: string,
|
||||
c_abi: bool,
|
||||
is_main: bool,
|
||||
param_types: []types.Type,
|
||||
result: types.Type,
|
||||
instructions: []Instruction,
|
||||
problematic: bool,
|
||||
link_name: string,
|
||||
calling_convention: Calling_Convention,
|
||||
implementation: Implementation,
|
||||
linkage: Linkage,
|
||||
is_main: bool,
|
||||
param_types: []types.Type,
|
||||
result: types.Type,
|
||||
instructions: []Instruction,
|
||||
problematic: bool,
|
||||
}
|
||||
|
||||
Global :: struct {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package linker
|
||||
|
||||
Kind :: enum {
|
||||
Input,
|
||||
Library_Path,
|
||||
Library,
|
||||
}
|
||||
|
||||
Argument :: struct {
|
||||
kind: Kind,
|
||||
value: string,
|
||||
}
|
||||
+20
-5
@@ -202,7 +202,7 @@ emit_instruction_stream :: proc(
|
||||
strings.write_string(&emitter.builder, " ")
|
||||
}
|
||||
strings.write_string(&emitter.builder, "call ")
|
||||
if !target.c_abi {
|
||||
if target.calling_convention == .Brolang {
|
||||
strings.write_string(&emitter.builder, "fastcc ")
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
||||
@@ -321,16 +321,31 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
||||
|
||||
emit_functions :: proc(emitter: ^Emitter) {
|
||||
for function in emitter.module.functions {
|
||||
strings.write_string(&emitter.builder, "define ")
|
||||
if !function.c_abi {
|
||||
strings.write_string(&emitter.builder, "internal fastcc ")
|
||||
if function.implementation == .Declaration {
|
||||
strings.write_string(&emitter.builder, "declare ")
|
||||
} else {
|
||||
strings.write_string(&emitter.builder, "define ")
|
||||
if function.linkage == .Internal {
|
||||
strings.write_string(&emitter.builder, "internal ")
|
||||
}
|
||||
}
|
||||
if function.calling_convention == .Brolang {
|
||||
strings.write_string(&emitter.builder, "fastcc ")
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(function), function.link_name)
|
||||
for param_type, index in function.param_types {
|
||||
if index > 0 {
|
||||
strings.write_string(&emitter.builder, ", ")
|
||||
}
|
||||
fmt.sbprintf(&emitter.builder, "%s %%v%d", llvm_type(param_type), index)
|
||||
if function.implementation == .Declaration {
|
||||
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type))
|
||||
} else {
|
||||
fmt.sbprintf(&emitter.builder, "%s %%v%d", llvm_type(param_type), index)
|
||||
}
|
||||
}
|
||||
if function.implementation == .Declaration {
|
||||
strings.write_string(&emitter.builder, ")\n\n")
|
||||
continue
|
||||
}
|
||||
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
||||
_ = emit_instruction_stream(emitter, function.instructions, function)
|
||||
|
||||
@@ -343,11 +343,13 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
|
||||
}
|
||||
append(&module.functions, ir.Function{
|
||||
link_name=fmt.aprintf("%s", function.link_name, allocator=allocator),
|
||||
c_abi=function.c_abi,
|
||||
calling_convention=.C if function.calling_convention == .C else .Brolang,
|
||||
implementation=.Declaration if function.implementation == .Declaration else .Definition,
|
||||
linkage=.External if function.linkage == .External else .Internal,
|
||||
is_main=function.is_main,
|
||||
param_types=param_types,
|
||||
result=function.result,
|
||||
instructions=lower_body(hir_module, function, allocator),
|
||||
instructions=nil if function.implementation == .Declaration else lower_body(hir_module, function, allocator),
|
||||
problematic=function.problematic,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -385,10 +385,29 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||
}
|
||||
skip_newlines(parser)
|
||||
result := parse_type(parser)
|
||||
skip_newlines(parser)
|
||||
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '{' before function body")
|
||||
end := previous(parser)
|
||||
ended_by_newline := current(parser).kind == .Newline
|
||||
if current(parser).kind == .Newline {
|
||||
skip_newlines(parser)
|
||||
}
|
||||
if current(parser).kind != .Left_Brace {
|
||||
if !ended_by_newline && current(parser).kind != .Eof {
|
||||
_ = finish_statement(parser)
|
||||
}
|
||||
append(&parser.module.functions, ast.Function{
|
||||
span=span_from(name.span, end.span),
|
||||
name=name.symbol,
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=c_abi,
|
||||
has_body=false,
|
||||
params=params,
|
||||
result=result,
|
||||
diagnostic=-1,
|
||||
})
|
||||
return
|
||||
}
|
||||
advance(parser)
|
||||
|
||||
body: [dynamic]int
|
||||
body.allocator = parser.module.allocator
|
||||
@@ -406,7 +425,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||
append(&body, statement_id)
|
||||
}
|
||||
}
|
||||
end := current(parser)
|
||||
end = current(parser)
|
||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after function body")
|
||||
end = func_token
|
||||
@@ -417,6 +436,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||
pkg=parser.pkg,
|
||||
file=parser.file,
|
||||
c_abi=c_abi,
|
||||
has_body=true,
|
||||
params=params,
|
||||
result=result,
|
||||
body=body[:],
|
||||
|
||||
Reference in New Issue
Block a user