manual c function interop
This commit is contained in:
+4
-3
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
- demand-monomorphized functions
|
- demand-monomorphized functions
|
||||||
- bodyful `c func` definitions using the c calling convention
|
- bodyful `c func` definitions using the c calling convention
|
||||||
|
- bodyless `c func` declarations with exact, globally unique external symbol names
|
||||||
|
- concrete-only foreign signatures
|
||||||
- directory packages with merged declarations
|
- directory packages with merged declarations
|
||||||
- file-local relative imports, aliases, and qualified member access
|
- file-local relative imports, aliases, and qualified member access
|
||||||
|
|
||||||
@@ -28,15 +30,14 @@
|
|||||||
|
|
||||||
- error-tolerant compilation with runtime diagnostic traps
|
- error-tolerant compilation with runtime diagnostic traps
|
||||||
- lazy semantic checking of demanded function specializations
|
- lazy semantic checking of demanded function specializations
|
||||||
|
- demand-driven LLVM declarations for referenced foreign functions
|
||||||
|
- ordered linking of additional c sources, objects, and libraries
|
||||||
- static, eager runtime, and deferred problematic globals
|
- static, eager runtime, and deferred problematic globals
|
||||||
|
|
||||||
## PLANNED
|
## PLANNED
|
||||||
|
|
||||||
### foreign functions and linking
|
### foreign functions and linking
|
||||||
|
|
||||||
- bodyless `c func` declarations with exact external symbol names
|
|
||||||
- concrete-only foreign signatures
|
|
||||||
- linking c sources, objects, and libraries
|
|
||||||
- c variadic calls with default argument promotions
|
- c variadic calls with default argument promotions
|
||||||
- exporting brolang functions to c
|
- exporting brolang functions to c
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,25 @@ odin build . -out:brolang
|
|||||||
/tmp/prototype
|
/tmp/prototype
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Bodyless `c func` declarations bind exact external symbols and require concrete
|
||||||
|
types:
|
||||||
|
|
||||||
|
```bro
|
||||||
|
foreign_add :: c func(a, b i32) i32
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional native inputs and libraries are passed to the final `zig cc`
|
||||||
|
invocation in command-line order:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./brolang examples/interop/manual -o /tmp/manual \
|
||||||
|
--link examples/interop/manual/native.c
|
||||||
|
```
|
||||||
|
|
||||||
|
`--link` accepts C sources, object files, and direct library paths.
|
||||||
|
`--library-path <dir>` becomes `-L<dir>`, and `--library <name>` becomes
|
||||||
|
`-l<name>`.
|
||||||
|
|
||||||
Compilation phases are isolated under `compiler/`:
|
Compilation phases are isolated under `compiler/`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -41,6 +60,8 @@ Current prototype features:
|
|||||||
- Directory packages with merged declarations and file-local relative imports
|
- Directory packages with merged declarations and file-local relative imports
|
||||||
- Qualified imported globals and functions with package-aware symbol mangling
|
- Qualified imported globals and functions with package-aware symbol mangling
|
||||||
- Demand-monomorphized Brolang and C-ABI functions
|
- Demand-monomorphized Brolang and C-ABI functions
|
||||||
|
- Bodyless concrete C function declarations with exact external symbol names
|
||||||
|
- Ordered linking of additional C sources, objects, and libraries
|
||||||
- Checked signed addition
|
- Checked signed addition
|
||||||
- Static, eager runtime, and deferred problematic globals
|
- Static, eager runtime, and deferred problematic globals
|
||||||
- Runtime diagnostics followed by `llvm.trap`
|
- Runtime diagnostics followed by `llvm.trap`
|
||||||
|
|||||||
@@ -4,16 +4,7 @@
|
|||||||
|
|
||||||
# milestones
|
# milestones
|
||||||
|
|
||||||
1. manual c function interop
|
1. interop type foundation
|
||||||
- separate calling convention, implementation, linkage, and link name
|
|
||||||
- allow bodyless `c func` declarations with exact external symbol names
|
|
||||||
- require concrete types in foreign signatures; inferred `int` is invalid
|
|
||||||
- emit LLVM `declare` for referenced foreign functions
|
|
||||||
- compile and link additional c sources, object files, and libraries through CLI options
|
|
||||||
- preserve bodyful, mangled, demand-monomorphized `c func` behavior
|
|
||||||
- verify end-to-end with an integer-only c function
|
|
||||||
|
|
||||||
2. interop type foundation
|
|
||||||
- unsigned integers, floats, and target-dependent c scalar types
|
- unsigned integers, floats, and target-dependent c scalar types
|
||||||
- keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`)
|
- keep binding mutability (`::` / `=`) separate from element or pointee mutability (`mut`)
|
||||||
- arrays and indexing
|
- arrays and indexing
|
||||||
@@ -36,7 +27,7 @@
|
|||||||
- `Some :: c struct`: opaque c-layout struct
|
- `Some :: c struct`: opaque c-layout struct
|
||||||
- defer passing c structs by value until target ABI classification exists
|
- defer passing c structs by value until target ABI classification exists
|
||||||
|
|
||||||
3. restricted c header imports
|
2. restricted c header imports
|
||||||
- treat an imported header as a synthetic, file-local package namespace
|
- treat an imported header as a synthetic, file-local package namespace
|
||||||
- `import "relative/path/to/header.h"`
|
- `import "relative/path/to/header.h"`
|
||||||
- `other :: import "relative/path/to/header.h"`
|
- `other :: import "relative/path/to/header.h"`
|
||||||
@@ -46,13 +37,13 @@
|
|||||||
- diagnose unsupported declarations when referenced
|
- diagnose unsupported declarations when referenced
|
||||||
- research libclang's c API behind a replaceable c importer boundary
|
- research libclang's c API behind a replaceable c importer boundary
|
||||||
|
|
||||||
4. c variadic calls
|
3. c variadic calls
|
||||||
- represent c variadics as a fixed parameter count plus a variadic flag
|
- represent c variadics as a fixed parameter count plus a variadic flag
|
||||||
- apply c default argument promotions at call sites
|
- apply c default argument promotions at call sites
|
||||||
- emit LLVM c-variadic declarations and calls
|
- emit LLVM c-variadic declarations and calls
|
||||||
- keep native brolang variadics and tuple design separate
|
- keep native brolang variadics and tuple design separate
|
||||||
|
|
||||||
5. advanced c interop
|
4. advanced c interop
|
||||||
- by-value records and unions
|
- by-value records and unions
|
||||||
- function pointers and callbacks
|
- function pointers and callbacks
|
||||||
- external variables
|
- external variables
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ Function :: struct {
|
|||||||
pkg: int,
|
pkg: int,
|
||||||
file: int,
|
file: int,
|
||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
|
has_body: bool,
|
||||||
params: []Param,
|
params: []Param,
|
||||||
result: Type_Syntax,
|
result: Type_Syntax,
|
||||||
body: []int,
|
body: []int,
|
||||||
|
|||||||
@@ -1,25 +1,59 @@
|
|||||||
package backend
|
package backend
|
||||||
|
|
||||||
|
import "../linker"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:os/os2"
|
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()
|
pid := os2.get_pid()
|
||||||
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
|
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
|
||||||
defer _ = os.remove(temporary_output)
|
defer _ = os.remove(temporary_output)
|
||||||
|
|
||||||
command := []string{
|
command := build_command(llvm_path, temporary_output, link_arguments)
|
||||||
"/usr/bin/env",
|
defer destroy_command(command)
|
||||||
"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,
|
|
||||||
}
|
|
||||||
state, stdout, stderr, err := os2.process_exec(
|
state, stdout, stderr, err := os2.process_exec(
|
||||||
os2.Process_Desc{command=command},
|
os2.Process_Desc{command=command},
|
||||||
context.allocator,
|
context.allocator,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ Checker :: struct {
|
|||||||
import_index: []Symbol_Index_Entry,
|
import_index: []Symbol_Index_Entry,
|
||||||
global_types: []types.Type,
|
global_types: []types.Type,
|
||||||
constants: []Constant,
|
constants: []Constant,
|
||||||
|
template_diagnostics: []int,
|
||||||
main_symbol: symbol.Id,
|
main_symbol: symbol.Id,
|
||||||
sink_symbol: symbol.Id,
|
sink_symbol: symbol.Id,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
@@ -287,7 +288,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validate_declarations :: proc(checker: ^Checker) {
|
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: [dynamic]symbol.Id
|
||||||
locals.allocator = checker.allocator
|
locals.allocator = checker.allocator
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
@@ -308,6 +309,42 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
append(&locals, param.name)
|
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 {
|
for statement_id in function.body {
|
||||||
statement := checker.ast_module.statements[statement_id]
|
statement := checker.ast_module.statements[statement_id]
|
||||||
switch statement.kind {
|
switch statement.kind {
|
||||||
@@ -318,6 +355,23 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
delete(locals)
|
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 {
|
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 {
|
if template < 0 {
|
||||||
return types.INVALID
|
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)
|
args := make([]types.Type, len(expr.args), checker.allocator)
|
||||||
for arg, index in expr.args {
|
for arg, index in expr.args {
|
||||||
args[index] = infer_expr(checker, arg, locals, pkg, file)
|
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)
|
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
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) {
|
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
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 {
|
if function.pkg == 0 && function.name == checker.main_symbol {
|
||||||
return fmt.aprintf("main", allocator = checker.allocator)
|
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)
|
builder := strings.builder_make(checker.allocator)
|
||||||
defer strings.builder_destroy(&builder)
|
defer strings.builder_destroy(&builder)
|
||||||
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
|
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)
|
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
|
has_return := false
|
||||||
if signature_diagnostic >= 0 {
|
if signature_diagnostic >= 0 {
|
||||||
append(&body, len(checker.module.statements))
|
append(&body, len(checker.module.statements))
|
||||||
@@ -1288,7 +1379,9 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
hir.Function {
|
hir.Function {
|
||||||
name = function.name,
|
name = function.name,
|
||||||
link_name = make_link_name(checker, spec_id),
|
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,
|
is_main = function.pkg == 0 && function.name == checker.main_symbol,
|
||||||
params = params[:],
|
params = params[:],
|
||||||
result = spec.result,
|
result = spec.result,
|
||||||
@@ -1547,7 +1640,9 @@ synthesize_trap_main :: proc(checker: ^Checker) {
|
|||||||
hir.Function {
|
hir.Function {
|
||||||
name = checker.main_symbol,
|
name = checker.main_symbol,
|
||||||
link_name = fmt.aprintf("main", allocator = checker.allocator),
|
link_name = fmt.aprintf("main", allocator = checker.allocator),
|
||||||
c_abi = true,
|
calling_convention = .C,
|
||||||
|
implementation = .Definition,
|
||||||
|
linkage = .External,
|
||||||
is_main = true,
|
is_main = true,
|
||||||
result = types.VOID,
|
result = types.VOID,
|
||||||
body = body,
|
body = body,
|
||||||
@@ -1566,6 +1661,9 @@ replace_main_with_trap :: proc(checker: ^Checker, diagnostic: int) {
|
|||||||
delete(function.body, checker.allocator)
|
delete(function.body, checker.allocator)
|
||||||
function.params = nil
|
function.params = nil
|
||||||
function.result = types.VOID
|
function.result = types.VOID
|
||||||
|
function.calling_convention = .C
|
||||||
|
function.implementation = .Definition
|
||||||
|
function.linkage = .External
|
||||||
function.problematic = true
|
function.problematic = true
|
||||||
function.diagnostic = diagnostic
|
function.diagnostic = diagnostic
|
||||||
statement_id := len(checker.module.statements)
|
statement_id := len(checker.module.statements)
|
||||||
@@ -1605,6 +1703,10 @@ check :: proc(
|
|||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
checker.constants = make([]Constant, len(ast_module.exprs), 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 {
|
defer {
|
||||||
for spec in checker.specs {
|
for spec in checker.specs {
|
||||||
delete(spec.args, allocator)
|
delete(spec.args, allocator)
|
||||||
@@ -1615,6 +1717,7 @@ check :: proc(
|
|||||||
delete(checker.import_index, allocator)
|
delete(checker.import_index, allocator)
|
||||||
delete(checker.global_types, allocator)
|
delete(checker.global_types, allocator)
|
||||||
delete(checker.constants, allocator)
|
delete(checker.constants, allocator)
|
||||||
|
delete(checker.template_diagnostics, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
@@ -1658,13 +1761,17 @@ check :: proc(
|
|||||||
} else {
|
} else {
|
||||||
template := ast_module.functions[main_template]
|
template := ast_module.functions[main_template]
|
||||||
if main_declarations != 1 ||
|
if main_declarations != 1 ||
|
||||||
|
!template.has_body ||
|
||||||
len(template.params) != 0 ||
|
len(template.params) != 0 ||
|
||||||
!(template.result == .Void || template.result == .I32 || template.result == .Int) {
|
!(template.result == .Void || template.result == .I32 || template.result == .Int) {
|
||||||
id := source.add(
|
id := checker.template_diagnostics[main_template]
|
||||||
|
if id < 0 {
|
||||||
|
id = source.add(
|
||||||
diagnostics,
|
diagnostics,
|
||||||
template.span,
|
template.span,
|
||||||
"main must be unique, take no parameters, and return void, i32, or int",
|
"main must be unique, have a body, take no parameters, and return void, i32, or int",
|
||||||
)
|
)
|
||||||
|
}
|
||||||
replace_main_with_trap(&checker, id)
|
replace_main_with_trap(&checker, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package compiler
|
|||||||
import "./backend"
|
import "./backend"
|
||||||
import "./checker"
|
import "./checker"
|
||||||
import "./llvm"
|
import "./llvm"
|
||||||
|
import "./linker"
|
||||||
import "./loader"
|
import "./loader"
|
||||||
import "./lower"
|
import "./lower"
|
||||||
import "./opt"
|
import "./opt"
|
||||||
@@ -13,7 +14,7 @@ import vmem "core:mem/virtual"
|
|||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:os/os2"
|
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()
|
sources := source.init_store()
|
||||||
defer source.destroy_store(&sources)
|
defer source.destroy_store(&sources)
|
||||||
diagnostics := source.init_store_diagnostics(&sources)
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
@@ -76,7 +77,7 @@ compile_package :: proc(input_path, output_path: string) -> int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.print_all(&diagnostics)
|
source.print_all(&diagnostics)
|
||||||
if !backend.compile(llvm_path, output_path) {
|
if !backend.compile(llvm_path, output_path, link_arguments) {
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
if len(diagnostics.items) > 0 {
|
if len(diagnostics.items) > 0 {
|
||||||
|
|||||||
+18
-1
@@ -7,6 +7,21 @@ import "core:mem"
|
|||||||
|
|
||||||
INVALID_ID :: -1
|
INVALID_ID :: -1
|
||||||
|
|
||||||
|
Calling_Convention :: enum {
|
||||||
|
Brolang,
|
||||||
|
C,
|
||||||
|
}
|
||||||
|
|
||||||
|
Implementation :: enum {
|
||||||
|
Definition,
|
||||||
|
Declaration,
|
||||||
|
}
|
||||||
|
|
||||||
|
Linkage :: enum {
|
||||||
|
Internal,
|
||||||
|
External,
|
||||||
|
}
|
||||||
|
|
||||||
Expr_Kind :: enum {
|
Expr_Kind :: enum {
|
||||||
Invalid,
|
Invalid,
|
||||||
Integer,
|
Integer,
|
||||||
@@ -56,7 +71,9 @@ Stmt :: struct {
|
|||||||
Function :: struct {
|
Function :: struct {
|
||||||
name: symbol.Id,
|
name: symbol.Id,
|
||||||
link_name: string,
|
link_name: string,
|
||||||
c_abi: bool,
|
calling_convention: Calling_Convention,
|
||||||
|
implementation: Implementation,
|
||||||
|
linkage: Linkage,
|
||||||
is_main: bool,
|
is_main: bool,
|
||||||
params: []int,
|
params: []int,
|
||||||
result: types.Type,
|
result: types.Type,
|
||||||
|
|||||||
+18
-1
@@ -7,6 +7,21 @@ import "core:mem"
|
|||||||
|
|
||||||
INVALID_ID :: -1
|
INVALID_ID :: -1
|
||||||
|
|
||||||
|
Calling_Convention :: enum {
|
||||||
|
Brolang,
|
||||||
|
C,
|
||||||
|
}
|
||||||
|
|
||||||
|
Implementation :: enum {
|
||||||
|
Definition,
|
||||||
|
Declaration,
|
||||||
|
}
|
||||||
|
|
||||||
|
Linkage :: enum {
|
||||||
|
Internal,
|
||||||
|
External,
|
||||||
|
}
|
||||||
|
|
||||||
Opcode :: enum {
|
Opcode :: enum {
|
||||||
Param,
|
Param,
|
||||||
Const,
|
Const,
|
||||||
@@ -36,7 +51,9 @@ Instruction :: struct {
|
|||||||
|
|
||||||
Function :: struct {
|
Function :: struct {
|
||||||
link_name: string,
|
link_name: string,
|
||||||
c_abi: bool,
|
calling_convention: Calling_Convention,
|
||||||
|
implementation: Implementation,
|
||||||
|
linkage: Linkage,
|
||||||
is_main: bool,
|
is_main: bool,
|
||||||
param_types: []types.Type,
|
param_types: []types.Type,
|
||||||
result: types.Type,
|
result: types.Type,
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package linker
|
||||||
|
|
||||||
|
Kind :: enum {
|
||||||
|
Input,
|
||||||
|
Library_Path,
|
||||||
|
Library,
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument :: struct {
|
||||||
|
kind: Kind,
|
||||||
|
value: string,
|
||||||
|
}
|
||||||
+18
-3
@@ -202,7 +202,7 @@ emit_instruction_stream :: proc(
|
|||||||
strings.write_string(&emitter.builder, " ")
|
strings.write_string(&emitter.builder, " ")
|
||||||
}
|
}
|
||||||
strings.write_string(&emitter.builder, "call ")
|
strings.write_string(&emitter.builder, "call ")
|
||||||
if !target.c_abi {
|
if target.calling_convention == .Brolang {
|
||||||
strings.write_string(&emitter.builder, "fastcc ")
|
strings.write_string(&emitter.builder, "fastcc ")
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
||||||
@@ -321,17 +321,32 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
|||||||
|
|
||||||
emit_functions :: proc(emitter: ^Emitter) {
|
emit_functions :: proc(emitter: ^Emitter) {
|
||||||
for function in emitter.module.functions {
|
for function in emitter.module.functions {
|
||||||
|
if function.implementation == .Declaration {
|
||||||
|
strings.write_string(&emitter.builder, "declare ")
|
||||||
|
} else {
|
||||||
strings.write_string(&emitter.builder, "define ")
|
strings.write_string(&emitter.builder, "define ")
|
||||||
if !function.c_abi {
|
if function.linkage == .Internal {
|
||||||
strings.write_string(&emitter.builder, "internal fastcc ")
|
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)
|
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(function), function.link_name)
|
||||||
for param_type, index in function.param_types {
|
for param_type, index in function.param_types {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
strings.write_string(&emitter.builder, ", ")
|
strings.write_string(&emitter.builder, ", ")
|
||||||
}
|
}
|
||||||
|
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)
|
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")
|
strings.write_string(&emitter.builder, ") {\nentry:\n")
|
||||||
_ = emit_instruction_stream(emitter, function.instructions, function)
|
_ = emit_instruction_stream(emitter, function.instructions, function)
|
||||||
strings.write_string(&emitter.builder, "}\n\n")
|
strings.write_string(&emitter.builder, "}\n\n")
|
||||||
|
|||||||
@@ -343,11 +343,13 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
|
|||||||
}
|
}
|
||||||
append(&module.functions, ir.Function{
|
append(&module.functions, ir.Function{
|
||||||
link_name=fmt.aprintf("%s", function.link_name, allocator=allocator),
|
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,
|
is_main=function.is_main,
|
||||||
param_types=param_types,
|
param_types=param_types,
|
||||||
result=function.result,
|
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,
|
problematic=function.problematic,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -385,10 +385,29 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
}
|
}
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
result := parse_type(parser)
|
result := parse_type(parser)
|
||||||
|
end := previous(parser)
|
||||||
|
ended_by_newline := current(parser).kind == .Newline
|
||||||
|
if current(parser).kind == .Newline {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if _, ok := allow(parser, .Left_Brace); !ok {
|
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '{' before function body")
|
|
||||||
}
|
}
|
||||||
|
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: [dynamic]int
|
||||||
body.allocator = parser.module.allocator
|
body.allocator = parser.module.allocator
|
||||||
@@ -406,7 +425,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
append(&body, statement_id)
|
append(&body, statement_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end := current(parser)
|
end = current(parser)
|
||||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after function body")
|
source.add(parser.diagnostics, current(parser).span, "expected '}' after function body")
|
||||||
end = func_token
|
end = func_token
|
||||||
@@ -417,6 +436,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
pkg=parser.pkg,
|
pkg=parser.pkg,
|
||||||
file=parser.file,
|
file=parser.file,
|
||||||
c_abi=c_abi,
|
c_abi=c_abi,
|
||||||
|
has_body=true,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
result=result,
|
||||||
body=body[:],
|
body=body[:],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import "./compiler/checker"
|
|||||||
import "./compiler/hir"
|
import "./compiler/hir"
|
||||||
import "./compiler/ir"
|
import "./compiler/ir"
|
||||||
import "./compiler/lexer"
|
import "./compiler/lexer"
|
||||||
|
import "./compiler/linker"
|
||||||
import "./compiler/loader"
|
import "./compiler/loader"
|
||||||
import "./compiler/llvm"
|
import "./compiler/llvm"
|
||||||
import "./compiler/lower"
|
import "./compiler/lower"
|
||||||
@@ -143,6 +144,76 @@ main :: func() void { _ = give() }
|
|||||||
testing.expect_value(t, module.statements[module.functions[1].body[0]].kind, ast.Stmt_Kind.Assignment)
|
testing.expect_value(t, module.statements[module.functions[1].body[0]].kind, ast.Stmt_Kind.Assignment)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_distinguishes_bodyless_declarations_and_definitions :: proc(t: ^testing.T) {
|
||||||
|
text := `foreign :: c func(value i32) i32
|
||||||
|
defined :: c func(value i32) i32
|
||||||
|
{
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
native :: func() i32
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, len(module.functions), 4)
|
||||||
|
testing.expect(t, !module.functions[0].has_body)
|
||||||
|
testing.expect(t, module.functions[0].c_abi)
|
||||||
|
testing.expect(t, module.functions[1].has_body)
|
||||||
|
testing.expect(t, module.functions[1].c_abi)
|
||||||
|
testing.expect_value(t, len(module.functions[1].body), 1)
|
||||||
|
testing.expect(t, !module.functions[2].has_body)
|
||||||
|
testing.expect(t, !module.functions[2].c_abi)
|
||||||
|
testing.expect(t, module.functions[3].has_body)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||||
|
options, valid := parse_cli_args([]string{
|
||||||
|
"brolang",
|
||||||
|
"app",
|
||||||
|
"--link",
|
||||||
|
"native.c",
|
||||||
|
"-o",
|
||||||
|
"app.out",
|
||||||
|
"--library-path",
|
||||||
|
"vendor/lib",
|
||||||
|
"--library",
|
||||||
|
"thing",
|
||||||
|
"--link",
|
||||||
|
"helper.o",
|
||||||
|
})
|
||||||
|
defer delete(options.link_arguments)
|
||||||
|
|
||||||
|
testing.expect(t, valid)
|
||||||
|
testing.expect_value(t, options.input_path, "app")
|
||||||
|
testing.expect_value(t, options.output_path, "app.out")
|
||||||
|
testing.expect_value(t, len(options.link_arguments), 4)
|
||||||
|
testing.expect_value(t, options.link_arguments[0].kind, linker.Kind.Input)
|
||||||
|
testing.expect_value(t, options.link_arguments[0].value, "native.c")
|
||||||
|
testing.expect_value(t, options.link_arguments[1].kind, linker.Kind.Library_Path)
|
||||||
|
testing.expect_value(t, options.link_arguments[2].kind, linker.Kind.Library)
|
||||||
|
testing.expect_value(t, options.link_arguments[3].value, "helper.o")
|
||||||
|
|
||||||
|
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
|
||||||
|
_, incomplete_valid := parse_cli_args([]string{"brolang", "app", "-o"})
|
||||||
|
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"})
|
||||||
|
_, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
|
||||||
|
testing.expect(t, !unknown_valid)
|
||||||
|
testing.expect(t, !incomplete_valid)
|
||||||
|
testing.expect(t, !duplicate_output_valid)
|
||||||
|
testing.expect(t, !duplicate_empty_output_valid)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
parser_accepts_bare_and_aliased_imports :: proc(t: ^testing.T) {
|
parser_accepts_bare_and_aliased_imports :: proc(t: ^testing.T) {
|
||||||
text := `import
|
text := `import
|
||||||
@@ -319,6 +390,139 @@ main :: func() void {
|
|||||||
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
|
testing.expect(t, strings.contains(llvm_text, "@llvm.sadd.with.overflow.i8"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
pipeline_emits_only_referenced_foreign_declarations_with_exact_names :: proc(t: ^testing.T) {
|
||||||
|
text := `used :: c func(a, b i32) i32
|
||||||
|
unused :: c func() i32
|
||||||
|
bodyful :: c func(value i32) i32 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
main :: func() void {
|
||||||
|
_ = used(1, 2)
|
||||||
|
_ = bodyful(3)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "declare i32 @used(i32, i32)"))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "call i32 @used(i32 1, i32 2)"))
|
||||||
|
testing.expect(t, !strings.contains(llvm_text, "@unused("))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "define i32 @bro_c__p0__bodyful__i32"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
invalid_foreign_declarations_are_eagerly_diagnosed_and_calls_trap :: proc(t: ^testing.T) {
|
||||||
|
text := `bad :: c func(value int) int
|
||||||
|
native :: func() i32
|
||||||
|
main :: func() void {
|
||||||
|
_ = bad(1)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
|
||||||
|
found_parameter := false
|
||||||
|
found_result := false
|
||||||
|
found_native := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_parameter = found_parameter || strings.contains(diagnostic.message, "requires concrete parameter types")
|
||||||
|
found_result = found_result || strings.contains(diagnostic.message, "requires a concrete or void result type")
|
||||||
|
found_native = found_native || strings.contains(diagnostic.message, "must use 'c func'")
|
||||||
|
}
|
||||||
|
testing.expect(t, found_parameter)
|
||||||
|
testing.expect(t, found_result)
|
||||||
|
testing.expect(t, found_native)
|
||||||
|
testing.expect(t, !strings.contains(llvm_text, "@bad("))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "call void @bro.trap"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
duplicate_foreign_symbols_across_packages_are_poisoned :: proc(t: ^testing.T) {
|
||||||
|
sources := source.init_store()
|
||||||
|
defer source.destroy_store(&sources)
|
||||||
|
diagnostics := source.init_store_diagnostics(&sources)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
ast_module, loaded := loader.load("examples/packages/foreign_duplicate/app", &sources, &diagnostics, &symbols)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
|
||||||
|
duplicate_count := 0
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
if strings.contains(diagnostic.message, "duplicate foreign symbol 'same'") {
|
||||||
|
duplicate_count += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect(t, loaded)
|
||||||
|
testing.expect_value(t, duplicate_count, 2)
|
||||||
|
testing.expect(t, !strings.contains(llvm_text, "@same("))
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "call void @bro.trap"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
bodyless_root_main_recovers_as_a_trap_definition :: proc(t: ^testing.T) {
|
||||||
|
text := "main :: c func() i32\n"
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||||
|
defer delete(llvm_text)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "main must have a body")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
testing.expect_value(t, len(ir_module.functions), 1)
|
||||||
|
testing.expect_value(t, ir_module.functions[0].implementation, ir.Implementation.Definition)
|
||||||
|
testing.expect(t, strings.contains(llvm_text, "define i32 @main()"))
|
||||||
|
testing.expect(t, !strings.contains(llvm_text, "declare i32 @main()"))
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
literal_addition_trees_fold_with_contextual_types :: proc(t: ^testing.T) {
|
literal_addition_trees_fold_with_contextual_types :: proc(t: ^testing.T) {
|
||||||
text := `return_i16 :: func() i16 {
|
text := `return_i16 :: func() i16 {
|
||||||
@@ -604,6 +808,43 @@ main :: func() void {}
|
|||||||
testing.expect(t, found_void)
|
testing.expect(t, found_void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_command_success :: proc(command: []string) -> bool {
|
||||||
|
state, stdout, stderr, err := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=command},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
delete(stdout)
|
||||||
|
delete(stderr)
|
||||||
|
return err == nil && state.exit_code == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_native_object :: proc(output: string) -> bool {
|
||||||
|
local_cache := fmt.aprintf("ZIG_LOCAL_CACHE_DIR=%s-zig-cache", output)
|
||||||
|
defer delete(local_cache)
|
||||||
|
global_cache := fmt.aprintf("ZIG_GLOBAL_CACHE_DIR=%s-zig-global-cache", output)
|
||||||
|
defer delete(global_cache)
|
||||||
|
return run_command_success([]string{
|
||||||
|
"/usr/bin/env",
|
||||||
|
local_cache,
|
||||||
|
global_cache,
|
||||||
|
"zig",
|
||||||
|
"cc",
|
||||||
|
"-c",
|
||||||
|
"examples/interop/manual/native.c",
|
||||||
|
"-o",
|
||||||
|
output,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_native_archive :: proc(object, archive: string) -> bool {
|
||||||
|
return run_command_success([]string{
|
||||||
|
"/usr/bin/ar",
|
||||||
|
"-rcs",
|
||||||
|
archive,
|
||||||
|
object,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
run_executable :: proc(path: string) -> os2.Process_State {
|
run_executable :: proc(path: string) -> os2.Process_State {
|
||||||
state, stdout, stderr, _ := os2.process_exec(
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
os2.Process_Desc{command=[]string{path}},
|
os2.Process_Desc{command=[]string{path}},
|
||||||
@@ -624,6 +865,68 @@ valid_program_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 0)
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-foreign-source"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
arguments := []linker.Argument{{kind=.Input, value="examples/interop/manual/native.c"}}
|
||||||
|
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
foreign_function_links_from_object :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-foreign-object"
|
||||||
|
object := "/tmp/brolang-test-foreign-object.o"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
defer _ = os.remove(object)
|
||||||
|
testing.expect(t, prepare_native_object(object))
|
||||||
|
arguments := []linker.Argument{{kind=.Input, value=object}}
|
||||||
|
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
foreign_function_links_from_direct_library :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-foreign-direct-library"
|
||||||
|
object := "/tmp/brolang-test-foreign-direct-library.o"
|
||||||
|
archive := "/tmp/brolang-test-foreign-direct-library.a"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
defer _ = os.remove(object)
|
||||||
|
defer _ = os.remove(archive)
|
||||||
|
testing.expect(t, prepare_native_object(object))
|
||||||
|
testing.expect(t, prepare_native_archive(object, archive))
|
||||||
|
arguments := []linker.Argument{{kind=.Input, value=archive}}
|
||||||
|
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
foreign_function_links_from_named_library :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-foreign-named-library"
|
||||||
|
object := "/tmp/brolang-test-foreign-named-library.o"
|
||||||
|
archive := "/tmp/libbrolang-test-foreign-named.a"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
defer _ = os.remove(object)
|
||||||
|
defer _ = os.remove(archive)
|
||||||
|
testing.expect(t, prepare_native_object(object))
|
||||||
|
testing.expect(t, prepare_native_archive(object, archive))
|
||||||
|
arguments := []linker.Argument{
|
||||||
|
{kind=.Library_Path, value="/tmp"},
|
||||||
|
{kind=.Library, value="brolang-test-foreign-named"},
|
||||||
|
}
|
||||||
|
status := compiler_core.compile_package("examples/interop/manual", output, arguments)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
one_line_main_compiles_and_runs :: proc(t: ^testing.T) {
|
one_line_main_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-one-line-main"
|
output := "/tmp/brolang-test-one-line-main"
|
||||||
@@ -762,6 +1065,38 @@ backend_failure_preserves_existing_output :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, string(data), previous)
|
testing.expect_value(t, string(data), previous)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T) {
|
||||||
|
arguments := []linker.Argument{
|
||||||
|
{kind=.Input, value="native.c"},
|
||||||
|
{kind=.Library_Path, value="vendor/lib"},
|
||||||
|
{kind=.Library, value="thing"},
|
||||||
|
{kind=.Input, value="helper.o"},
|
||||||
|
}
|
||||||
|
command := backend.build_command("module.ll", "program", arguments)
|
||||||
|
defer backend.destroy_command(command)
|
||||||
|
|
||||||
|
expected := []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",
|
||||||
|
"module.ll",
|
||||||
|
"native.c",
|
||||||
|
"-Lvendor/lib",
|
||||||
|
"-lthing",
|
||||||
|
"helper.o",
|
||||||
|
"-o",
|
||||||
|
"program",
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(command), len(expected))
|
||||||
|
for value, index in expected {
|
||||||
|
testing.expect_value(t, command[index], value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-mutable"
|
output := "/tmp/brolang-test-mutable"
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
foreign_add :: c func(a, b i32) i32
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
return foreign_add(20, 22)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
int foreign_add(int a, int b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import "../left"
|
||||||
|
import "../right"
|
||||||
|
|
||||||
|
main :: func() void {
|
||||||
|
_ = left.same()
|
||||||
|
_ = right.same()
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
same :: c func() i32
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
same :: c func() i32
|
||||||
@@ -1,15 +1,75 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "./compiler"
|
import "./compiler"
|
||||||
|
import "./compiler/linker"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:os/os2"
|
import "core:os/os2"
|
||||||
|
|
||||||
|
Cli_Options :: struct {
|
||||||
|
input_path: string,
|
||||||
|
output_path: string,
|
||||||
|
link_arguments: []linker.Argument,
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_Options, bool) {
|
||||||
|
if len(args) < 4 {
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
options := Cli_Options{input_path=args[1]}
|
||||||
|
link_arguments: [dynamic]linker.Argument
|
||||||
|
link_arguments.allocator = allocator
|
||||||
|
output_set := false
|
||||||
|
cursor := 2
|
||||||
|
for cursor < len(args) {
|
||||||
|
option := args[cursor]
|
||||||
|
cursor += 1
|
||||||
|
if cursor >= len(args) {
|
||||||
|
delete(link_arguments)
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
value := args[cursor]
|
||||||
|
cursor += 1
|
||||||
|
switch option {
|
||||||
|
case "-o":
|
||||||
|
if output_set {
|
||||||
|
delete(link_arguments)
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
output_set = true
|
||||||
|
options.output_path = value
|
||||||
|
case "--link":
|
||||||
|
append(&link_arguments, linker.Argument{kind=.Input, value=value})
|
||||||
|
case "--library-path":
|
||||||
|
append(&link_arguments, linker.Argument{kind=.Library_Path, value=value})
|
||||||
|
case "--library":
|
||||||
|
append(&link_arguments, linker.Argument{kind=.Library, value=value})
|
||||||
|
case:
|
||||||
|
delete(link_arguments)
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !output_set || len(options.output_path) == 0 {
|
||||||
|
delete(link_arguments)
|
||||||
|
return {}, false
|
||||||
|
}
|
||||||
|
options.link_arguments = link_arguments[:]
|
||||||
|
return options, true
|
||||||
|
}
|
||||||
|
|
||||||
|
print_usage :: proc() {
|
||||||
|
fmt.eprintln(
|
||||||
|
"usage: brolang <package-directory> -o <executable> [--link <path> | --library-path <dir> | --library <name>]...",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
if len(os2.args) != 4 || os2.args[2] != "-o" {
|
options, valid := parse_cli_args(os2.args)
|
||||||
fmt.eprintln("usage: brolang <package-directory> -o <executable>")
|
if !valid {
|
||||||
|
print_usage()
|
||||||
os2.exit(2)
|
os2.exit(2)
|
||||||
}
|
}
|
||||||
status := compiler.compile_package(os2.args[1], os2.args[3])
|
defer delete(options.link_arguments)
|
||||||
|
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments)
|
||||||
if status != 0 {
|
if status != 0 {
|
||||||
os2.exit(status)
|
os2.exit(status)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user