restricted c header imports

This commit is contained in:
2026-06-14 14:42:38 +02:00
parent 4e860b033e
commit 638ca57f5c
24 changed files with 1377 additions and 45 deletions
+7 -4
View File
@@ -32,6 +32,9 @@
- Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes - Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes
- 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
- relative `.h` imports as synthetic package namespaces
- transitive external C function prototypes, typedef chains, C scalars, and pointers to opaque C records
- reference-time diagnostics for unsupported imported C declarations
### compiler behavior ### compiler behavior
@@ -39,6 +42,8 @@
- lazy semantic checking of demanded function specializations - lazy semantic checking of demanded function specializations
- demand-driven LLVM declarations for referenced foreign functions - demand-driven LLVM declarations for referenced foreign functions
- ordered linking of additional c sources, objects, and libraries - ordered linking of additional c sources, objects, and libraries
- replaceable dynamically loaded libclang C-import backend
- per-compilation C-header import caching by canonical path, target, ordered include paths, and ordered defines
- static, eager runtime, and deferred problematic globals - static, eager runtime, and deferred problematic globals
## PLANNED ## PLANNED
@@ -53,9 +58,7 @@
- tuples and native variadic functions - tuples and native variadic functions
- C unions, C enums, and by-value C record ABI lowering - C unions, C enums, and by-value C record ABI lowering
### c imports ### advanced c imports
- c headers imported as synthetic package namespaces - C enums, external variables, function pointers, callbacks, macros, and static inline functions
- typedefs, enums, opaque records, and external variables
- function pointers, callbacks, macros, and static inline functions
- target-specific by-value C record and union ABI lowering - target-specific by-value C record and union ABI lowering
+22 -5
View File
@@ -22,12 +22,28 @@ invocation in command-line order:
```sh ```sh
./build/brolang examples/interop/manual -o build/manual \ ./build/brolang examples/interop/manual -o build/manual \
--link examples/interop/manual/native.c --c-link examples/interop/manual/native.c
``` ```
`--link` accepts C sources, object files, and direct library paths. `--c-link` accepts C sources, object files, archives, and direct library paths.
`--library-path <dir>` becomes `-L<dir>`, and `--library <name>` becomes `--c-library-path <dir>` becomes `-L<dir>`, and `--c-library <name>` becomes
`-l<name>`. `-l<name>`. `--c-include-path <dir>` and `--c-define <name[=value]>` configure
C preprocessing.
Relative `.h` imports create synthetic package namespaces backed by libclang:
```bro
native :: import "../include/native.h"
main :: func() void {
_ = native.imported_add(20, 22)
}
```
Header imports expose supported external functions, typedefs, C scalars, and
pointers to opaque records. They never add linker inputs; implementations must
still be supplied explicitly with the C-prefixed linking options. Set
`BROLANG_LIBCLANG_PATH` when libclang is not installed in a standard location.
Compilation phases are isolated under `compiler/`: Compilation phases are isolated under `compiler/`:
@@ -63,10 +79,11 @@ Current prototype features:
- Explicit `.ptr`/`.len`, slicing, postfix pointer dereference and optional unwrap, and keyed struct literals - Explicit `.ptr`/`.len`, slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
- Contextual integer constants and compile-time folding of addition and unary negation trees - Contextual integer constants and compile-time folding of addition and unary negation trees
- Directory packages with merged declarations and file-local relative imports - Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces
- 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 - Bodyless concrete C function declarations with exact external symbol names
- Ordered linking of additional C sources, objects, and libraries - Ordered linking of additional C sources, objects, archives, and libraries
- Checked signed addition and unary negation - Checked signed addition and unary negation
- 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`
+3 -4
View File
@@ -29,15 +29,14 @@
- `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
2. restricted c header imports 2. restricted c header imports (implemented)
- 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"` - `native :: import "relative/path/to/header.h"`
- `other :: import "relative/path/to/header.h"`
- import functions, typedefs, scalar types, and pointers to opaque records - import functions, typedefs, scalar types, and pointers to opaque records
- keep implementation linking separate from header imports - keep implementation linking separate from header imports
- cache imports by canonical header path and target/include/define configuration - cache imports by canonical header path and target/include/define configuration
- diagnose unsupported declarations when referenced - diagnose unsupported declarations when referenced
- research libclang's c API behind a replaceable c importer boundary - dynamically load libclang behind a replaceable c importer boundary
3. 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
+21
View File
@@ -128,10 +128,12 @@ Function :: struct {
pkg: Package_Id, pkg: Package_Id,
file: File_Id, file: File_Id,
c_abi: bool, c_abi: bool,
imported: bool,
has_body: bool, has_body: bool,
params: []Param, params: []Param,
result: Type_Syntax, result: Type_Syntax,
body: []Stmt_Id, body: []Stmt_Id,
unsupported_reason: string,
diagnostic: source.Diagnostic_Id, diagnostic: source.Diagnostic_Id,
} }
@@ -167,6 +169,18 @@ Package :: struct {
path: string, path: string,
name: symbol.Id, name: symbol.Id,
available: bool, available: bool,
kind: Package_Kind,
}
Package_Kind :: enum u8 {
Native,
C_Header,
}
Unsupported :: struct {
pkg: Package_Id,
name: symbol.Id,
reason: string,
} }
Module :: struct { Module :: struct {
@@ -177,6 +191,7 @@ Module :: struct {
imports: [dynamic]Import, imports: [dynamic]Import,
files: [dynamic]File, files: [dynamic]File,
packages: [dynamic]Package, packages: [dynamic]Package,
unsupported: [dynamic]Unsupported,
strings: [dynamic]string, strings: [dynamic]string,
type_store: types.Store, type_store: types.Store,
allocator: mem.Allocator, allocator: mem.Allocator,
@@ -193,6 +208,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.imports.allocator = allocator module.imports.allocator = allocator
module.files.allocator = allocator module.files.allocator = allocator
module.packages.allocator = allocator module.packages.allocator = allocator
module.unsupported.allocator = allocator
module.strings.allocator = allocator module.strings.allocator = allocator
return module return module
} }
@@ -204,6 +220,7 @@ destroy_module :: proc(module: ^Module) {
for function in module.functions { for function in module.functions {
delete(function.params, module.allocator) delete(function.params, module.allocator)
delete(function.body, module.allocator) delete(function.body, module.allocator)
delete(function.unsupported_reason, module.allocator)
} }
for import_item in module.imports { for import_item in module.imports {
delete(import_item.path, module.allocator) delete(import_item.path, module.allocator)
@@ -211,6 +228,9 @@ destroy_module :: proc(module: ^Module) {
for pkg in module.packages { for pkg in module.packages {
delete(pkg.path, module.allocator) delete(pkg.path, module.allocator)
} }
for item in module.unsupported {
delete(item.reason, module.allocator)
}
for value in module.strings { for value in module.strings {
delete(value, module.allocator) delete(value, module.allocator)
} }
@@ -221,6 +241,7 @@ destroy_module :: proc(module: ^Module) {
delete(module.imports) delete(module.imports)
delete(module.files) delete(module.files)
delete(module.packages) delete(module.packages)
delete(module.unsupported)
delete(module.strings) delete(module.strings)
types.destroy_store(&module.type_store) types.destroy_store(&module.type_store)
} }
+11 -1
View File
@@ -2,6 +2,7 @@ package backend
import "../linker" import "../linker"
import "../target" import "../target"
import "../cimport"
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
@@ -16,6 +17,7 @@ build_command :: proc(
llvm_path, output_path: string, llvm_path, output_path: string,
link_arguments: []linker.Argument, link_arguments: []linker.Argument,
selected := target.DEFAULT, selected := target.DEFAULT,
c_options := cimport.Options{},
allocator := context.allocator, allocator := context.allocator,
) -> []string { ) -> []string {
command: [dynamic]string command: [dynamic]string
@@ -26,7 +28,14 @@ build_command :: proc(
append_owned(&command, "-target", allocator) append_owned(&command, "-target", allocator)
append_owned(&command, target.name(selected), allocator) append_owned(&command, target.name(selected), allocator)
append_owned(&command, "-Wno-override-module", allocator) append_owned(&command, "-Wno-override-module", allocator)
append_owned(&command, "-Wno-unused-command-line-argument", allocator)
append_owned(&command, llvm_path, allocator) append_owned(&command, llvm_path, allocator)
for path in c_options.include_paths {
append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
}
for define in c_options.defines {
append(&command, fmt.aprintf("-D%s", define, allocator=allocator))
}
for argument in link_arguments { for argument in link_arguments {
switch argument.kind { switch argument.kind {
case .Input: case .Input:
@@ -53,12 +62,13 @@ compile :: proc(
llvm_path, output_path: string, llvm_path, output_path: string,
link_arguments: []linker.Argument = nil, link_arguments: []linker.Argument = nil,
selected := target.DEFAULT, selected := target.DEFAULT,
c_options := cimport.Options{},
) -> bool { ) -> 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 := build_command(llvm_path, temporary_output, link_arguments, selected) command := build_command(llvm_path, temporary_output, link_arguments, selected, c_options)
defer destroy_command(command) defer destroy_command(command)
state, stdout, stderr, err := os2.process_exec( state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=command}, os2.Process_Desc{command=command},
+111 -5
View File
@@ -409,6 +409,62 @@ add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name)) return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
} }
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
for item in checker.ast_module.unsupported {
if item.pkg == pkg && item.name == name {
return item, true
}
}
return {}, false
}
add_unsupported_diagnostic :: proc(checker: ^Checker, span: source.Span, pkg: ast.Package_Id, name: symbol.Id) -> source.Diagnostic_Id {
if item, ok := find_unsupported(checker, pkg, name); ok {
return source.addf(
checker.diagnostics,
span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, name),
item.reason,
)
}
return source.INVALID_DIAGNOSTIC
}
add_unsupported_type_diagnostic :: proc(
checker: ^Checker,
span: source.Span,
value: types.Type,
depth := 0,
) -> source.Diagnostic_Id {
if depth > 64 {
return source.INVALID_DIAGNOSTIC
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return source.INVALID_DIAGNOSTIC
}
if item.kind == .Alias {
return add_unsupported_diagnostic(checker, span, ast.Package_Id(item.pkg), symbol.Id(item.name))
}
if types.is_valid(item.child) {
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
}
return source.INVALID_DIAGNOSTIC
}
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
if left.result != right.result || len(left.params) != len(right.params) {
return false
}
for param, index in left.params {
if param.type != right.params[index].type {
return false
}
}
return true
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool { contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names { for existing in names {
if existing == name { if existing == name {
@@ -454,9 +510,17 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
validate_declarations :: proc(checker: ^Checker) { validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions { for function, function_id in checker.ast_module.functions {
if len(function.unsupported_reason) > 0 {
continue
}
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 {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
continue
}
if param.type == types.VOID { if param.type == types.VOID {
source.add( source.add(
checker.diagnostics, checker.diagnostics,
@@ -482,6 +546,10 @@ validate_declarations :: proc(checker: ^Checker) {
) )
} }
} }
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(function.result));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
}
if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) { if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf( checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
@@ -500,6 +568,10 @@ validate_declarations :: proc(checker: ^Checker) {
} }
if !function.has_body && function.c_abi { if !function.has_body && function.c_abi {
for param in function.params { for param in function.params {
if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) !=
source.INVALID_DIAGNOSTIC {
continue
}
if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) { if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf( checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
@@ -510,7 +582,8 @@ validate_declarations :: proc(checker: ^Checker) {
} }
} }
result := type_from_syntax(function.result) result := type_from_syntax(function.result)
if !types.is_c_signature_type(result, &checker.module.types, true) { if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
!types.is_c_signature_type(result, &checker.module.types, true) {
checker.template_diagnostics[function_id] = source.addf( checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
function.span, function.span,
@@ -547,6 +620,9 @@ validate_declarations :: proc(checker: ^Checker) {
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name { if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
continue continue
} }
if function.imported && other.imported && function_signatures_equal(function, other) {
continue
}
checker.template_diagnostics[function_id] = source.addf( checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics, checker.diagnostics,
function.span, function.span,
@@ -901,6 +977,11 @@ infer_expr :: proc(
_ = pop(&stack) _ = pop(&stack)
continue continue
} }
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
last = types.INVALID
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC { if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
declared := type_from_syntax(checker.ast_module.functions[template].result) declared := type_from_syntax(checker.ast_module.functions[template].result)
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
@@ -1185,8 +1266,15 @@ coerce_expr :: proc(
return expr_id return expr_id
} }
if types.can_weaken_pointer(actual, expected, &checker.module.types) { if types.can_weaken_pointer(actual, expected, &checker.module.types) {
checker.module.exprs[expr_id].type = expected return add_hir_expr(checker, hir.Expr{
return expr_id kind=.Weaken_Pointer,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
} }
if types.is_optional(expected, &checker.module.types) { if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types) child := types.child_type(expected, &checker.module.types)
@@ -1754,7 +1842,10 @@ build_expr :: proc(
target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC, target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
}) })
} else { } else {
id := add_name_resolution_diagnostic(checker, expr, target_pkg) 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)
} }
} }
@@ -1775,7 +1866,22 @@ build_expr :: proc(
} }
template := find_template(checker, expr.name, target_pkg) template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION { if template == ast.INVALID_FUNCTION {
id := add_call_resolution_diagnostic(checker, expr, target_pkg) 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
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
id := source.addf(
checker.diagnostics,
expr.span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, expr.name),
checker.ast_module.functions[template].unsupported_reason,
)
last = invalid_hir_expr(checker, expr.span, id) last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack) _ = pop(&stack)
continue continue
+140
View File
@@ -0,0 +1,140 @@
package cimport
import "../target"
import "core:mem"
Type_Id :: distinct u32
INVALID_TYPE :: Type_Id(0xffff_ffff)
Type_Kind :: enum u8 {
Invalid,
Void,
C_Char,
C_Schar,
C_Uchar,
C_Short,
C_Ushort,
C_Int,
C_Uint,
C_Long,
C_Ulong,
C_Longlong,
C_Ulonglong,
C_Float,
C_Double,
C_Longdouble,
Pointer,
Record,
}
Type :: struct {
kind: Type_Kind,
child: Type_Id,
record: u32,
mutable: bool,
}
Record :: struct {
name: string,
identity: string,
}
Alias :: struct {
name: string,
type: Type_Id,
reason: string,
}
Function :: struct {
name: string,
params: []Type_Id,
result: Type_Id,
reason: string,
}
Unsupported :: struct {
name: string,
reason: string,
}
Result :: struct {
types: [dynamic]Type,
records: [dynamic]Record,
aliases: [dynamic]Alias,
functions: [dynamic]Function,
unsupported: [dynamic]Unsupported,
error_message: string,
infrastructure: bool,
available: bool,
allocator: mem.Allocator,
}
init_result :: proc(allocator := context.allocator) -> Result {
result: Result
result.allocator = allocator
result.types.allocator = allocator
result.records.allocator = allocator
result.aliases.allocator = allocator
result.functions.allocator = allocator
result.unsupported.allocator = allocator
return result
}
destroy_result :: proc(result: ^Result) {
for record in result.records {
delete(record.name, result.allocator)
delete(record.identity, result.allocator)
}
for alias in result.aliases {
delete(alias.name, result.allocator)
delete(alias.reason, result.allocator)
}
for function in result.functions {
delete(function.name, result.allocator)
delete(function.params, result.allocator)
delete(function.reason, result.allocator)
}
for item in result.unsupported {
delete(item.name, result.allocator)
delete(item.reason, result.allocator)
}
delete(result.error_message, result.allocator)
delete(result.types)
delete(result.records)
delete(result.aliases)
delete(result.functions)
delete(result.unsupported)
}
Request :: struct {
path: string,
include_paths: []string,
defines: []string,
target: target.Target,
}
Backend_Proc :: proc(user_data: rawptr, request: Request, allocator: mem.Allocator) -> Result
Backend :: struct {
import_header: Backend_Proc,
user_data: rawptr,
}
Options :: struct {
include_paths: []string,
defines: []string,
backend: Backend,
}
import_header :: proc(options: Options, path: string, selected := target.DEFAULT, allocator := context.allocator) -> Result {
request := Request{
path=path,
include_paths=options.include_paths,
defines=options.defines,
target=selected,
}
if options.backend.import_header != nil {
return options.backend.import_header(options.backend.user_data, request, allocator)
}
return import_with_libclang(nil, request, allocator)
}
+464
View File
@@ -0,0 +1,464 @@
package cimport
import "../target"
import "base:runtime"
import "core:dynlib"
import "core:fmt"
import "core:mem"
import "core:os/os2"
import "core:strings"
CXCursor :: struct {
kind: i32,
xdata: i32,
data: [3]rawptr,
}
CXType :: struct {
kind: i32,
data: [2]rawptr,
}
CXString :: struct {
data: rawptr,
private_flags: u32,
}
CXIndex :: distinct rawptr
CXTranslationUnit :: distinct rawptr
CXDiagnostic :: distinct rawptr
Cursor_Visitor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32
Api :: struct {
library: dynlib.Library,
create_index: proc "c"(i32, i32) -> CXIndex,
dispose_index: proc "c"(CXIndex),
parse_translation_unit: proc "c"(CXIndex, cstring, [^]cstring, i32, rawptr, u32, u32, ^CXTranslationUnit) -> i32,
dispose_translation_unit: proc "c"(CXTranslationUnit),
get_translation_unit_cursor: proc "c"(CXTranslationUnit) -> CXCursor,
visit_children: proc "c"(CXCursor, Cursor_Visitor, rawptr) -> u32,
get_cursor_kind: proc "c"(CXCursor) -> i32,
get_cursor_spelling: proc "c"(CXCursor) -> CXString,
get_cursor_usr: proc "c"(CXCursor) -> CXString,
get_cursor_linkage: proc "c"(CXCursor) -> i32,
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_result_type: proc "c"(CXType) -> CXType,
get_num_arg_types: proc "c"(CXType) -> i32,
get_arg_type: proc "c"(CXType, u32) -> CXType,
is_const_qualified_type: proc "c"(CXType) -> u32,
is_volatile_qualified_type: proc "c"(CXType) -> u32,
cursor_is_variadic: proc "c"(CXCursor) -> u32,
get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32,
get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic,
get_diagnostic_severity: proc "c"(CXDiagnostic) -> i32,
get_diagnostic_spelling: proc "c"(CXDiagnostic) -> CXString,
dispose_diagnostic: proc "c"(CXDiagnostic),
get_cstring: proc "c"(CXString) -> cstring,
dispose_string: proc "c"(CXString),
}
CXCursor_StructDecl :: i32(2)
CXCursor_UnionDecl :: i32(3)
CXCursor_EnumDecl :: i32(5)
CXCursor_FunctionDecl :: i32(8)
CXCursor_VarDecl :: i32(9)
CXCursor_TypedefDecl :: i32(20)
CXCursor_MacroDefinition :: i32(501)
CXLinkage_External :: i32(4)
CXType_Invalid :: i32(0)
CXType_Unexposed :: i32(1)
CXType_Void :: i32(2)
CXType_Char_U :: i32(4)
CXType_UChar :: i32(5)
CXType_UShort :: i32(8)
CXType_UInt :: i32(9)
CXType_ULong :: i32(10)
CXType_ULongLong :: i32(11)
CXType_Char_S :: i32(13)
CXType_SChar :: i32(14)
CXType_Short :: i32(16)
CXType_Int :: i32(17)
CXType_Long :: i32(18)
CXType_LongLong :: i32(19)
CXType_Float :: i32(21)
CXType_Double :: i32(22)
CXType_LongDouble :: i32(23)
CXType_Pointer :: i32(101)
CXType_Record :: i32(105)
CXType_Enum :: i32(106)
CXType_Typedef :: i32(107)
CXType_FunctionNoProto :: i32(110)
CXType_FunctionProto :: i32(111)
CXType_Elaborated :: i32(119)
CXType_Attributed :: i32(163)
CXChildVisit_Continue :: i32(1)
CXTranslationUnit_DetailedPreprocessingRecord :: u32(0x01)
CXTranslationUnit_SkipFunctionBodies :: u32(0x40)
CXTranslationUnit_KeepGoing :: u32(0x200)
CXDiagnostic_Error :: i32(3)
load_proc :: proc(api: ^Api, name: string, destination: ^$T) -> bool {
address, found := dynlib.symbol_address(api.library, name)
if !found {
return false
}
destination^ = transmute(T)address
return true
}
load_api_from :: proc(path: string) -> (Api, bool) {
api: Api
library, loaded := dynlib.load_library(path)
if !loaded {
return {}, false
}
api.library = library
ok :=
load_proc(&api, "clang_createIndex", &api.create_index) &&
load_proc(&api, "clang_disposeIndex", &api.dispose_index) &&
load_proc(&api, "clang_parseTranslationUnit2", &api.parse_translation_unit) &&
load_proc(&api, "clang_disposeTranslationUnit", &api.dispose_translation_unit) &&
load_proc(&api, "clang_getTranslationUnitCursor", &api.get_translation_unit_cursor) &&
load_proc(&api, "clang_visitChildren", &api.visit_children) &&
load_proc(&api, "clang_getCursorKind", &api.get_cursor_kind) &&
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_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_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_isConstQualifiedType", &api.is_const_qualified_type) &&
load_proc(&api, "clang_isVolatileQualifiedType", &api.is_volatile_qualified_type) &&
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) &&
load_proc(&api, "clang_getDiagnosticSeverity", &api.get_diagnostic_severity) &&
load_proc(&api, "clang_getDiagnosticSpelling", &api.get_diagnostic_spelling) &&
load_proc(&api, "clang_disposeDiagnostic", &api.dispose_diagnostic) &&
load_proc(&api, "clang_getCString", &api.get_cstring) &&
load_proc(&api, "clang_disposeString", &api.dispose_string)
if !ok {
_ = dynlib.unload_library(api.library)
return {}, false
}
return api, true
}
load_api :: proc() -> (Api, bool) {
if override, found := os2.lookup_env_alloc("BROLANG_LIBCLANG_PATH", context.temp_allocator); found {
if api, ok := load_api_from(override); ok {
return api, true
}
}
candidates := [?]string{
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib",
"/Library/Developer/CommandLineTools/usr/lib/libclang.dylib",
"/opt/homebrew/opt/llvm/lib/libclang.dylib",
"/opt/homebrew/opt/llvm@21/lib/libclang.dylib",
"libclang.dylib",
"libclang.so",
}
for candidate in candidates {
if api, ok := load_api_from(candidate); ok {
return api, true
}
}
return {}, false
}
clone_cx_string :: proc(api: ^Api, value: CXString, allocator: mem.Allocator) -> string {
defer api.dispose_string(value)
text := api.get_cstring(value)
if text == nil {
return fmt.aprintf("", allocator=allocator)
}
return fmt.aprintf("%s", string(text), allocator=allocator)
}
Context :: struct {
api: ^Api,
result: ^Result,
allocator: mem.Allocator,
}
add_type :: proc(ctx: ^Context, value: Type) -> Type_Id {
id := Type_Id(len(ctx.result.types))
append(&ctx.result.types, value)
return id
}
find_record :: proc(ctx: ^Context, identity: string) -> (u32, bool) {
for record, index in ctx.result.records {
if record.identity == identity {
return u32(index), true
}
}
return 0, false
}
add_record :: proc(ctx: ^Context, declaration: CXCursor, preferred_name: string) -> u32 {
identity := clone_cx_string(ctx.api, ctx.api.get_cursor_usr(declaration), ctx.allocator)
if len(identity) == 0 {
delete(identity, ctx.allocator)
identity = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
}
if index, ok := find_record(ctx, identity); ok {
delete(identity, ctx.allocator)
return index
}
name := fmt.aprintf("%s", preferred_name, allocator=ctx.allocator)
if len(name) == 0 {
delete(name, ctx.allocator)
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})
return index
}
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
}
switch value.kind {
case CXType_Void: return add_type(ctx, Type{kind=.Void, child=INVALID_TYPE})
case CXType_Char_U: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE})
case CXType_Char_S: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE})
case CXType_SChar: return add_type(ctx, Type{kind=.C_Schar, child=INVALID_TYPE})
case CXType_UChar: return add_type(ctx, Type{kind=.C_Uchar, child=INVALID_TYPE})
case CXType_Short: return add_type(ctx, Type{kind=.C_Short, child=INVALID_TYPE})
case CXType_UShort: return add_type(ctx, Type{kind=.C_Ushort, child=INVALID_TYPE})
case CXType_Int: return add_type(ctx, Type{kind=.C_Int, child=INVALID_TYPE})
case CXType_UInt: return add_type(ctx, Type{kind=.C_Uint, child=INVALID_TYPE})
case CXType_Long: return add_type(ctx, Type{kind=.C_Long, child=INVALID_TYPE})
case CXType_ULong: return add_type(ctx, Type{kind=.C_Ulong, child=INVALID_TYPE})
case CXType_LongLong: return add_type(ctx, Type{kind=.C_Longlong, child=INVALID_TYPE})
case CXType_ULongLong: return add_type(ctx, Type{kind=.C_Ulonglong, child=INVALID_TYPE})
case CXType_Float: return add_type(ctx, Type{kind=.C_Float, child=INVALID_TYPE})
case CXType_Double: return add_type(ctx, Type{kind=.C_Double, child=INVALID_TYPE})
case CXType_LongDouble: return add_type(ctx, Type{kind=.C_Longdouble, child=INVALID_TYPE})
case CXType_Pointer:
pointee := ctx.api.get_pointee_type(value)
child := translate_type(ctx, pointee, "", depth+1)
if child == INVALID_TYPE {
return INVALID_TYPE
}
return add_type(ctx, Type{
kind=.Pointer,
child=child,
mutable=ctx.api.is_const_qualified_type(pointee) == 0,
})
case CXType_Record:
declaration := ctx.api.get_type_declaration(value)
if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl {
return INVALID_TYPE
}
record := add_record(ctx, declaration, preferred_record_name)
return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
case CXType_Typedef:
declaration := ctx.api.get_type_declaration(value)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
defer delete(name, ctx.allocator)
return translate_type(ctx, ctx.api.get_typedef_underlying_type(declaration), name, depth+1)
case CXType_Elaborated, CXType_Attributed, CXType_Unexposed:
canonical := ctx.api.get_canonical_type(value)
if canonical.kind == value.kind {
return INVALID_TYPE
}
return translate_type(ctx, canonical, preferred_record_name, depth+1)
case CXType_Enum, CXType_FunctionNoProto, CXType_FunctionProto:
return INVALID_TYPE
}
return INVALID_TYPE
}
has_named :: proc(items: []Unsupported, name: string) -> bool {
for item in items {
if item.name == name {
return true
}
}
return false
}
add_unsupported :: proc(ctx: ^Context, name, reason: string) {
if len(name) == 0 || strings.has_prefix(name, "__") || has_named(ctx.result.unsupported[:], name) {
return
}
append(&ctx.result.unsupported, Unsupported{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
}
add_alias :: proc(ctx: ^Context, name: string, value: Type_Id, reason := "") {
if len(name) == 0 {
return
}
for alias in ctx.result.aliases {
if alias.name == name {
return
}
}
append(&ctx.result.aliases, Alias{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
type=value,
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
}
visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
context = runtime.default_context()
ctx := (^Context)(client_data)
kind := ctx.api.get_cursor_kind(cursor)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator)
defer delete(name, ctx.allocator)
switch kind {
case CXCursor_FunctionDecl:
if len(name) == 0 {
return CXChildVisit_Continue
}
reason := ""
linkage := ctx.api.get_cursor_linkage(cursor)
if linkage != CXLinkage_External {
reason = "static and non-external C functions are not supported"
} else if ctx.api.cursor_is_variadic(cursor) != 0 {
reason = "C variadic functions are not supported"
}
function_type := ctx.api.get_cursor_type(cursor)
result_type := translate_type(ctx, ctx.api.get_result_type(function_type))
if result_type == INVALID_TYPE && len(reason) == 0 {
reason = "function result type is not supported"
}
params: [dynamic]Type_Id
params.allocator = ctx.allocator
count := ctx.api.get_num_arg_types(function_type)
if count < 0 {
reason = "function declaration has no prototype"
} else {
for index in 0..<count {
param := translate_type(ctx, ctx.api.get_arg_type(function_type, u32(index)))
append(&params, param)
if param == INVALID_TYPE && len(reason) == 0 {
reason = "function parameter type is not supported"
}
}
}
append(&ctx.result.functions, Function{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
params=params[:],
result=result_type,
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
case CXCursor_TypedefDecl:
value := translate_type(ctx, ctx.api.get_typedef_underlying_type(cursor), name)
reason := ""
if value == INVALID_TYPE {
reason = "typedef underlying type is not supported"
}
add_alias(ctx, name, value, reason)
case CXCursor_StructDecl:
if len(name) > 0 {
record := add_record(ctx, cursor, name)
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:
add_unsupported(ctx, name, "external C variables are not supported")
case CXCursor_MacroDefinition:
add_unsupported(ctx, name, "C macros are not supported")
case:
}
return CXChildVisit_Continue
}
import_with_libclang :: proc(_: rawptr, request: Request, allocator: mem.Allocator) -> Result {
result := init_result(allocator)
api, loaded := load_api()
if !loaded {
result.infrastructure = true
result.error_message = fmt.aprintf(
"could not load libclang; set BROLANG_LIBCLANG_PATH to a compatible library",
allocator=allocator,
)
return result
}
defer _ = dynlib.unload_library(api.library)
index := api.create_index(1, 0)
if index == nil {
result.infrastructure = true
result.error_message = fmt.aprintf("could not create libclang index", allocator=allocator)
return result
}
defer api.dispose_index(index)
arguments: [dynamic]string
arguments.allocator = context.temp_allocator
append(&arguments, "-x", "c", "-target", target.llvm_triple(request.target))
for path in request.include_paths {
append(&arguments, fmt.tprintf("-I%s", path))
}
for define in request.defines {
append(&arguments, fmt.tprintf("-D%s", define))
}
c_arguments := make([]cstring, len(arguments), context.temp_allocator)
for argument, argument_index in arguments {
c_arguments[argument_index] = strings.clone_to_cstring(argument, context.temp_allocator)
}
c_path := strings.clone_to_cstring(request.path, context.temp_allocator)
translation_unit: CXTranslationUnit
error_code := api.parse_translation_unit(
index,
c_path,
raw_data(c_arguments),
i32(len(c_arguments)),
nil,
0,
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing,
&translation_unit,
)
if error_code != 0 || translation_unit == nil {
result.error_message = fmt.aprintf("libclang could not parse header '%s'", request.path, allocator=allocator)
return result
}
defer api.dispose_translation_unit(translation_unit)
for diagnostic_index in 0..<api.get_num_diagnostics(translation_unit) {
diagnostic := api.get_diagnostic(translation_unit, diagnostic_index)
severity := api.get_diagnostic_severity(diagnostic)
if severity >= CXDiagnostic_Error && len(result.error_message) == 0 {
result.error_message = clone_cx_string(&api, api.get_diagnostic_spelling(diagnostic), allocator)
}
api.dispose_diagnostic(diagnostic)
}
if len(result.error_message) > 0 {
return result
}
ctx := Context{api=&api, result=&result, allocator=allocator}
root := api.get_translation_unit_cursor(translation_unit)
_ = api.visit_children(root, visit_cursor, &ctx)
result.available = true
return result
}
+5 -1
View File
@@ -1,6 +1,7 @@
package compiler package compiler
import "./backend" import "./backend"
import "./cimport"
import "./checker" import "./checker"
import "./llvm" import "./llvm"
import "./linker" import "./linker"
@@ -19,6 +20,7 @@ compile_package :: proc(
input_path, output_path: string, input_path, output_path: string,
link_arguments: []linker.Argument = nil, link_arguments: []linker.Argument = nil,
selected := target.DEFAULT, selected := target.DEFAULT,
c_options := cimport.Options{},
) -> int { ) -> int {
sources := source.init_store() sources := source.init_store()
defer source.destroy_store(&sources) defer source.destroy_store(&sources)
@@ -59,6 +61,8 @@ compile_package :: proc(
&symbols, &symbols,
vmem.arena_allocator(&lexer_arena), vmem.arena_allocator(&lexer_arena),
vmem.arena_allocator(&parser_arena), vmem.arena_allocator(&parser_arena),
c_options,
selected,
) )
if !loaded { if !loaded {
source.print_all(&diagnostics) source.print_all(&diagnostics)
@@ -83,7 +87,7 @@ compile_package :: proc(
} }
source.print_all(&diagnostics) source.print_all(&diagnostics)
if !backend.compile(llvm_path, output_path, link_arguments, selected) { if !backend.compile(llvm_path, output_path, link_arguments, selected, c_options) {
return 2 return 2
} }
if len(diagnostics.items) > 0 { if len(diagnostics.items) > 0 {
+1
View File
@@ -93,6 +93,7 @@ Expr_Kind :: enum u8 {
Unwrap, Unwrap,
Orelse, Orelse,
Widen, Widen,
Weaken_Pointer,
Negate, Negate,
Add, Add,
Pointer_Add, Pointer_Add,
+1
View File
@@ -87,6 +87,7 @@ Opcode :: enum u8 {
Orelse_Begin, Orelse_Begin,
Orelse, Orelse,
Widen, Widen,
Weaken_Pointer,
Neg_Checked, Neg_Checked,
Add_Checked, Add_Checked,
Pointer_Add, Pointer_Add,
+21 -2
View File
@@ -112,7 +112,7 @@ valid_value :: proc(
switch instructions[value_id].op { switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some, case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse, .Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call: .Widen, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin, case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Trap, .Return, .Return_Void: .Store, .Trap, .Return, .Return_Void:
@@ -707,6 +707,13 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types)) fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
case .Weaken_Pointer:
if !valid_instruction(instructions, instruction.a) ||
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer weakening operand")
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
case .Neg_Checked: case .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) { if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand") emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
@@ -985,7 +992,19 @@ emit_constructor :: proc(emitter: ^Emitter) {
} }
emit_functions :: proc(emitter: ^Emitter) { emit_functions :: proc(emitter: ^Emitter) {
for function in emitter.module.functions { for function, function_index in emitter.module.functions {
if function.implementation == .Declaration {
duplicate := false
for previous in emitter.module.functions[:function_index] {
if previous.link_name == function.link_name {
duplicate = true
break
}
}
if duplicate {
continue
}
}
if function.implementation == .Declaration { if function.implementation == .Declaration {
strings.write_string(&emitter.builder, "declare ") strings.write_string(&emitter.builder, "declare ")
} else { } else {
+236 -3
View File
@@ -1,11 +1,14 @@
package loader package loader
import "../ast" import "../ast"
import "../cimport"
import "../lexer" import "../lexer"
import "../parser" import "../parser"
import "../source" import "../source"
import "../symbol" import "../symbol"
import "../target"
import "../types" import "../types"
import "core:fmt"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
import "core:path/filepath" import "core:path/filepath"
@@ -19,6 +22,10 @@ State :: struct {
symbols: ^symbol.Table, symbols: ^symbol.Table,
token_allocator: mem.Allocator, token_allocator: mem.Allocator,
allocator: mem.Allocator, allocator: mem.Allocator,
c_options: cimport.Options,
selected: target.Target,
record_identities: [dynamic]string,
record_types: [dynamic]types.Type,
root_failed: bool, root_failed: bool,
} }
@@ -104,6 +111,214 @@ resolve_import_path :: proc(state: ^State, importing_path, import_path: string)
return joined, false return joined, false
} }
header_package_name :: proc(path: string, symbols: ^symbol.Table) -> symbol.Id {
base := filepath.base(path)
extension := filepath.ext(base)
if len(extension) > 0 {
base = base[:len(base)-len(extension)]
}
return symbol.intern(symbols, base)
}
find_record_identity :: proc(state: ^State, identity: string) -> types.Type {
for existing, index in state.record_identities {
if existing == identity {
return state.record_types[index]
}
}
return types.INVALID
}
translate_c_type :: proc(
state: ^State,
result: ^cimport.Result,
value: cimport.Type_Id,
pkg: ast.Package_Id,
record_mapping: []types.Type,
type_mapping: []types.Type,
) -> types.Type {
if value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
return types.INVALID
}
if types.is_valid(type_mapping[value]) {
return type_mapping[value]
}
item := result.types[value]
translated := types.INVALID
switch item.kind {
case .Invalid: translated = types.INVALID
case .Void: translated = types.VOID
case .C_Char: translated = types.C_CHAR
case .C_Schar: translated = types.C_SCHAR
case .C_Uchar: translated = types.C_UCHAR
case .C_Short: translated = types.C_SHORT
case .C_Ushort: translated = types.C_USHORT
case .C_Int: translated = types.C_INT
case .C_Uint: translated = types.C_UINT
case .C_Long: translated = types.C_LONG
case .C_Ulong: translated = types.C_ULONG
case .C_Longlong: translated = types.C_LONGLONG
case .C_Ulonglong: translated = types.C_ULONGLONG
case .C_Float: translated = types.C_FLOAT
case .C_Double: translated = types.C_DOUBLE
case .C_Longdouble: translated = types.C_LONGDOUBLE
case .Pointer:
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if types.is_valid(child) {
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
translated = types.optional(&state.module.type_store, pointer)
}
case .Record:
if int(item.record) < len(record_mapping) {
translated = record_mapping[item.record]
}
}
type_mapping[value] = translated
return translated
}
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type) -> bool {
if left.result != result || len(left.params) != len(params) {
return false
}
for param, index in params {
if left.params[index].type != param.type {
return false
}
}
return true
}
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator)
if !ok {
id := add_placeholder(state, path)
source.addf(state.diagnostics, import_span, "could not resolve C header '%s'", path)
return id
}
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
delete(canonical, state.allocator)
return existing
}
pkg_id := ast.package_id(len(state.module.packages))
append(&state.module.packages, ast.Package{
path=canonical,
name=header_package_name(canonical, state.symbols),
available=false,
kind=.C_Header,
})
result := cimport.import_header(state.c_options, canonical, state.selected, state.allocator)
defer cimport.destroy_result(&result)
if !result.available {
message := result.error_message if len(result.error_message) > 0 else "C header import failed"
source.addf(state.diagnostics, import_span, "could not import C header '%s': %s", path, message)
if result.infrastructure {
state.root_failed = true
}
return pkg_id
}
state.module.packages[pkg_id].available = true
record_mapping := make([]types.Type, len(result.records), state.allocator)
defer delete(record_mapping, state.allocator)
for record, index in result.records {
record_type := find_record_identity(state, record.identity)
if !types.is_valid(record_type) {
name := record.name
if len(name) == 0 {
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)
append(&state.record_identities, strings.clone(record.identity, state.allocator))
append(&state.record_types, record_type)
}
record_mapping[index] = record_type
}
type_mapping := make([]types.Type, len(result.types), state.allocator)
defer delete(type_mapping, state.allocator)
for alias in result.aliases {
name := symbol.intern(state.symbols, alias.name)
id := types.named(&state.module.type_store, u32(pkg_id), u32(name))
child := translate_c_type(state, &result, alias.type, pkg_id, record_mapping, type_mapping)
_ = types.define_alias(&state.module.type_store, id, child)
if len(alias.reason) > 0 {
append(&state.module.unsupported, ast.Unsupported{
pkg=pkg_id,
name=name,
reason=strings.clone(alias.reason, state.allocator),
})
}
}
for function in result.functions {
params := make([]ast.Param, len(function.params), state.allocator)
for param_type, index in function.params {
params[index] = ast.Param{
name=symbol.intern(state.symbols, fmt.tprintf("arg%d", index)),
span=import_span,
type=translate_c_type(state, &result, param_type, pkg_id, record_mapping, type_mapping),
}
}
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 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"
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"
}
name := symbol.intern(state.symbols, function.name)
duplicate := false
for &existing in state.module.functions {
if existing.pkg != pkg_id || existing.name != name {
continue
}
duplicate = true
if !function_signatures_equal(existing, params, function_result) && len(existing.unsupported_reason) == 0 {
existing.unsupported_reason = fmt.aprintf(
"conflicting C declarations for '%s'",
function.name,
allocator=state.allocator,
)
}
break
}
if duplicate {
delete(params, state.allocator)
continue
}
append(&state.module.functions, ast.Function{
span=import_span,
name=name,
pkg=pkg_id,
file=ast.INVALID_FILE,
c_abi=true,
imported=true,
has_body=false,
params=params,
result=function_result,
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
for item in result.unsupported {
append(&state.module.unsupported, ast.Unsupported{
pkg=pkg_id,
name=symbol.intern(state.symbols, item.name),
reason=strings.clone(item.reason, state.allocator),
})
}
return pkg_id
}
load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id { load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator) canonical, ok := filepath.abs(path, state.allocator)
if !ok || !os.is_dir(path) { if !ok || !os.is_dir(path) {
@@ -191,7 +406,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
continue continue
} }
target_path, target_ok := resolve_import_path(state, canonical, import_item.path) target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
target := load_package(state, target_path, import_item.span) target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else load_package(state, target_path, import_item.span)
state.module.imports[import_id].target = target state.module.imports[import_id].target = target
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available { if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
state.module.imports[import_id].valid = false state.module.imports[import_id].valid = false
@@ -293,14 +508,19 @@ canonical_type :: proc(
import_item := module.imports[import_id] import_item := module.imports[import_id]
resolved := types.find_named(&module.type_store, u32(import_item.target), item.name) resolved := types.find_named(&module.type_store, u32(import_item.target), item.name)
if types.is_valid(resolved) { if types.is_valid(resolved) {
mapping[index] = resolved mapping[index] = canonical_type(module, resolved, mapping, visiting)
return resolved return mapping[index]
} }
} }
} }
mapping[index] = value mapping[index] = value
return value return value
} }
if item.kind == .Alias {
resolved := canonical_type(module, item.child, mapping, visiting)
mapping[index] = value if !types.is_valid(resolved) else resolved
return mapping[index]
}
if item.kind == .Struct { if item.kind == .Struct {
mapping[index] = value mapping[index] = value
fields := types.fields_for(&module.type_store, value) fields := types.fields_for(&module.type_store, value)
@@ -347,6 +567,8 @@ load :: proc(
symbols: ^symbol.Table, symbols: ^symbol.Table,
token_allocator := context.allocator, token_allocator := context.allocator,
allocator := context.allocator, allocator := context.allocator,
c_options := cimport.Options{},
selected := target.DEFAULT,
) -> (ast.Module, bool) { ) -> (ast.Module, bool) {
module := ast.init_module(allocator) module := ast.init_module(allocator)
state := State{ state := State{
@@ -356,6 +578,17 @@ load :: proc(
symbols=symbols, symbols=symbols,
token_allocator=token_allocator, token_allocator=token_allocator,
allocator=allocator, allocator=allocator,
c_options=c_options,
selected=selected,
}
state.record_identities.allocator = allocator
state.record_types.allocator = allocator
defer {
for identity in state.record_identities {
delete(identity, allocator)
}
delete(state.record_identities)
delete(state.record_types)
} }
root := load_package(&state, root_path, source.Span{}, true) root := load_package(&state, root_path, source.Span{}, true)
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE { if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
+3 -2
View File
@@ -324,7 +324,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}) })
} }
_ = pop(&stack) _ = pop(&stack)
case .Widen: case .Widen, .Weaken_Pointer:
stack[frame_index].stage = 1 stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left}) append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate: case .Negate:
@@ -358,7 +358,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
} }
if frame.stage == 1 { if frame.stage == 1 {
last = append_instruction(state, ir.Instruction{ last = append_instruction(state, ir.Instruction{
op=.Widen, span=expr.span, type=expr.type, target=ir.INVALID_REF, op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else .Widen,
span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
}) })
_ = pop(&stack) _ = pop(&stack)
+15 -2
View File
@@ -59,6 +59,7 @@ Kind :: enum u8 {
Slice, Slice,
Optional, Optional,
Named, Named,
Alias,
Struct, Struct,
} }
@@ -132,7 +133,7 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> 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) normalized_file := file if qualifier != 0 else u32(0)
for existing, index in store.nodes { for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Struct) && if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier && existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file { existing.file == normalized_file {
return DYNAMIC_START+Type(index) return DYNAMIC_START+Type(index)
@@ -143,7 +144,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 { find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
for existing, index in store.nodes { for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Struct) && if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier { existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index) return DYNAMIC_START+Type(index)
} }
@@ -151,6 +152,18 @@ find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
return INVALID return INVALID
} }
define_alias :: proc(store: ^Store, id, child: Type) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
}
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Alias
store.nodes[index].child = child
store.nodes[index].declared = true
return true
}
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool { define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
existing, ok := node(store, id) existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared { if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared {
+169 -5
View File
@@ -3,6 +3,7 @@ package main
import compiler_core "./compiler" import compiler_core "./compiler"
import "./compiler/ast" import "./compiler/ast"
import "./compiler/backend" import "./compiler/backend"
import "./compiler/cimport"
import "./compiler/checker" import "./compiler/checker"
import "./compiler/hir" import "./compiler/hir"
import "./compiler/ir" import "./compiler/ir"
@@ -18,6 +19,7 @@ import "./compiler/target"
import "./compiler/token" import "./compiler/token"
import "./compiler/types" import "./compiler/types"
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" import "core:strings"
@@ -395,20 +397,26 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
options, valid := parse_cli_args([]string{ options, valid := parse_cli_args([]string{
"brolang", "brolang",
"app", "app",
"--link", "--c-link",
"native.c", "native.c",
"-o", "-o",
"app.out", "app.out",
"--library-path", "--c-library-path",
"vendor/lib", "vendor/lib",
"--library", "--c-library",
"thing", "thing",
"--link", "--c-link",
"helper.o", "helper.o",
"--c-include-path",
"vendor/include",
"--c-define",
"FEATURE=1",
"--target", "--target",
"aarch64-macos", "aarch64-macos",
}) })
defer delete(options.link_arguments) defer delete(options.link_arguments)
defer delete(options.c_options.include_paths)
defer delete(options.c_options.defines)
testing.expect(t, valid) testing.expect(t, valid)
testing.expect_value(t, options.input_path, "app") testing.expect_value(t, options.input_path, "app")
@@ -419,6 +427,8 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
testing.expect_value(t, options.link_arguments[1].kind, linker.Kind.Library_Path) 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[2].kind, linker.Kind.Library)
testing.expect_value(t, options.link_arguments[3].value, "helper.o") testing.expect_value(t, options.link_arguments[3].value, "helper.o")
testing.expect_value(t, options.c_options.include_paths[0], "vendor/include")
testing.expect_value(t, options.c_options.defines[0], "FEATURE=1")
testing.expect_value(t, target.name(options.target), "aarch64-macos") testing.expect_value(t, target.name(options.target), "aarch64-macos")
_, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"}) _, unknown_valid := parse_cli_args([]string{"brolang", "app", "-o", "out", "--unknown", "value"})
@@ -426,11 +436,17 @@ cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T)
_, duplicate_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "one", "-o", "two"}) _, 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"}) _, duplicate_empty_output_valid := parse_cli_args([]string{"brolang", "app", "-o", "", "-o", "two"})
_, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"}) _, invalid_target := parse_cli_args([]string{"brolang", "app", "-o", "out", "--target", "x86_64-linux"})
_, legacy_link := parse_cli_args([]string{"brolang", "app", "-o", "out", "--link", "native.c"})
_, legacy_library_path := parse_cli_args([]string{"brolang", "app", "-o", "out", "--library-path", "vendor/lib"})
_, legacy_library := parse_cli_args([]string{"brolang", "app", "-o", "out", "--library", "thing"})
testing.expect(t, !unknown_valid) testing.expect(t, !unknown_valid)
testing.expect(t, !incomplete_valid) testing.expect(t, !incomplete_valid)
testing.expect(t, !duplicate_output_valid) testing.expect(t, !duplicate_output_valid)
testing.expect(t, !duplicate_empty_output_valid) testing.expect(t, !duplicate_empty_output_valid)
testing.expect(t, !invalid_target) testing.expect(t, !invalid_target)
testing.expect(t, !legacy_link)
testing.expect(t, !legacy_library_path)
testing.expect(t, !legacy_library)
} }
@(test) @(test)
@@ -1319,6 +1335,44 @@ foreign_function_links_from_c_source :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 42) testing.expect_value(t, state.exit_code, 42)
} }
@(test)
restricted_c_header_imports_compile_and_link :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-header-import"
defer _ = os.remove(output)
arguments := []linker.Argument{{kind=.Input, value="examples/interop/header/native.c"}}
c_options := cimport.Options{
include_paths=[]string{"examples/interop/header/include"},
defines=[]string{"BROLANG_FEATURE"},
}
status := compiler_core.compile_package("examples/interop/header/app", output, arguments, target.DEFAULT, c_options)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
unsupported_c_header_members_diagnose_only_when_referenced :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-header-unsupported"
defer _ = os.remove(output)
c_options := cimport.Options{include_paths=[]string{"examples/interop/header/include"}}
status := compiler_core.compile_package("examples/interop/header_unsupported", output, nil, target.DEFAULT, c_options)
testing.expect_value(t, status, 1)
state := run_executable(output)
testing.expect(t, !state.success)
}
@(test)
compatible_c_header_redeclarations_share_one_llvm_declaration :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-header-duplicate"
defer _ = os.remove(output)
arguments := []linker.Argument{{kind=.Input, value="examples/interop/header/native.c"}}
c_options := cimport.Options{include_paths=[]string{"examples/interop/header/include"}}
status := compiler_core.compile_package("examples/interop/header_duplicate", output, arguments, target.DEFAULT, c_options)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test) @(test)
interop_foundation_matches_zig_compiled_apple_silicon_c_fixture :: proc(t: ^testing.T) { interop_foundation_matches_zig_compiled_apple_silicon_c_fixture :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-interop-foundation" output := "/tmp/brolang-test-interop-foundation"
@@ -1570,7 +1624,11 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
{kind=.Library, value="thing"}, {kind=.Library, value="thing"},
{kind=.Input, value="helper.o"}, {kind=.Input, value="helper.o"},
} }
command := backend.build_command("module.ll", "program", arguments) c_options := cimport.Options{
include_paths=[]string{"vendor/include"},
defines=[]string{"FEATURE=1"},
}
command := backend.build_command("module.ll", "program", arguments, target.DEFAULT, c_options)
defer backend.destroy_command(command) defer backend.destroy_command(command)
expected := []string{ expected := []string{
@@ -1580,7 +1638,10 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
"-target", "-target",
"aarch64-macos", "aarch64-macos",
"-Wno-override-module", "-Wno-override-module",
"-Wno-unused-command-line-argument",
"module.ll", "module.ll",
"-Ivendor/include",
"-DFEATURE=1",
"native.c", "native.c",
"-Lvendor/lib", "-Lvendor/lib",
"-lthing", "-lthing",
@@ -1594,6 +1655,109 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
} }
} }
Fake_Cimport_State :: struct {
calls: int,
available: bool,
infrastructure: bool,
saw_options: bool,
}
fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, allocator: mem.Allocator) -> cimport.Result {
state := (^Fake_Cimport_State)(user_data)
state.calls += 1
state.saw_options =
len(request.include_paths) == 2 &&
request.include_paths[0] == "first/include" &&
request.include_paths[1] == "second/include" &&
len(request.defines) == 2 &&
request.defines[0] == "FIRST=1" &&
request.defines[1] == "SECOND" &&
request.target == target.DEFAULT
result := cimport.init_result(allocator)
if !state.available {
result.infrastructure = state.infrastructure
result.error_message = fmt.aprintf("fake importer unavailable", allocator=allocator)
return result
}
append(&result.types, cimport.Type{kind=.C_Int, child=cimport.INVALID_TYPE})
append(&result.functions, cimport.Function{
name=fmt.aprintf("fake_value", allocator=allocator),
result=cimport.Type_Id(0),
reason=fmt.aprintf("", allocator=allocator),
})
result.available = true
return result
}
@(test)
cimport_backend_is_replaceable :: proc(t: ^testing.T) {
state := Fake_Cimport_State{available=true}
options := cimport.Options{backend={import_header=fake_cimport_backend, user_data=&state}}
result := cimport.import_header(options, "fake.h")
defer cimport.destroy_result(&result)
testing.expect(t, result.available)
testing.expect_value(t, state.calls, 1)
testing.expect_value(t, len(result.functions), 1)
testing.expect_value(t, result.functions[0].name, "fake_value")
}
@(test)
loader_injects_and_caches_cimport_backend_per_compilation :: 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)
state := Fake_Cimport_State{available=true}
options := cimport.Options{
include_paths=[]string{"first/include", "second/include"},
defines=[]string{"FIRST=1", "SECOND"},
backend={import_header=fake_cimport_backend, user_data=&state},
}
module, loaded := loader.load(
"examples/interop/header_cache",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, state.calls, 1)
testing.expect(t, state.saw_options)
testing.expect_value(t, len(module.imports), 2)
testing.expect_value(t, module.imports[0].target, module.imports[1].target)
testing.expect_value(t, len(module.functions), 2)
}
@(test)
cimport_infrastructure_failure_makes_compilation_unavailable :: 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)
state := Fake_Cimport_State{infrastructure=true}
options := cimport.Options{backend={import_header=fake_cimport_backend, user_data=&state}}
module, loaded := loader.load(
"examples/interop/header_cache",
&sources,
&diagnostics,
&symbols,
c_options=options,
)
defer ast.destroy_module(&module)
testing.expect(t, !loaded)
testing.expect_value(t, state.calls, 1)
testing.expect(t, len(diagnostics.items) > 0)
}
@(test) @(test)
source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t: ^testing.T) { source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t: ^testing.T) {
store := source.init_store() store := source.init_store()
+13
View File
@@ -0,0 +1,13 @@
native :: import "../include/native.h"
pass_alias :: func(value native.imported_int_alias) native.imported_int {
return value
}
main :: func() void {
_ = native.imported_add(pass_alias(20), 22)
_ = native.imported_scalar(3, 4)
_ = native.child_value(7)
_ = native.imported_read(native.imported_handle()?)
_ = native.configured_value(9)
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef BROLANG_CHILD_H
#define BROLANG_CHILD_H
int child_value(int value);
#endif
+40
View File
@@ -0,0 +1,40 @@
#ifndef BROLANG_NATIVE_H
#define BROLANG_NATIVE_H
#include <child.h>
typedef int imported_int;
typedef imported_int imported_int_alias;
typedef struct Imported_Handle Imported_Handle;
typedef void (*Imported_Callback)(int value);
typedef struct Imported_Value {
int value;
} Imported_Value;
typedef union Imported_Union {
int value;
} Imported_Union;
typedef enum Imported_Enum {
IMPORTED_ENUM_VALUE,
} Imported_Enum;
int imported_add(imported_int_alias left, int right);
unsigned long imported_scalar(unsigned char value, unsigned long extra);
Imported_Handle *imported_handle(void);
int imported_read(const Imported_Handle *handle);
Imported_Value imported_by_value(Imported_Value value);
int imported_volatile(volatile int *value);
_Bool imported_bool(_Bool value);
extern int imported_global;
static inline int imported_inline(int value) {
return value;
}
#ifdef BROLANG_FEATURE
int configured_value(int value);
#endif
#define IMPORTED_MACRO 42
int imported_variadic(const char *format, ...);
#endif
+33
View File
@@ -0,0 +1,33 @@
#include "include/native.h"
struct Imported_Handle {
int value;
};
static struct Imported_Handle handle = {7};
int child_value(int value) {
return value;
}
int imported_add(imported_int left, int right) {
return left + right;
}
unsigned long imported_scalar(unsigned char value, unsigned long extra) {
return value + extra;
}
Imported_Handle *imported_handle(void) {
return &handle;
}
int imported_read(const Imported_Handle *value) {
return value->value;
}
#ifdef BROLANG_FEATURE
int configured_value(int value) {
return value;
}
#endif
+4
View File
@@ -0,0 +1,4 @@
first :: import "../header/include/native.h"
second :: import "../header/include/native.h"
main :: func() void {}
@@ -0,0 +1,7 @@
native :: import "../header/include/native.h"
child :: import "../header/include/child.h"
main :: func() void {
_ = native.child_value(1)
_ = child.child_value(2)
}
@@ -0,0 +1,15 @@
native :: import "../header/include/native.h"
use_callback :: c_func(value native.Imported_Callback) void
use_union :: c_func(value native.Imported_Union) void
use_enum :: c_func(value native.Imported_Enum) void
main :: func() void {
_ = native.imported_variadic("value")
_ = native.IMPORTED_MACRO
_ = native.imported_by_value()
_ = native.imported_volatile()
_ = native.imported_bool()
_ = native.imported_global
_ = native.imported_inline(1)
}
+28 -10
View File
@@ -1,6 +1,7 @@
package main package main
import "./compiler" import "./compiler"
import "./compiler/cimport"
import "./compiler/linker" import "./compiler/linker"
import "./compiler/target" import "./compiler/target"
import "core:fmt" import "core:fmt"
@@ -10,6 +11,7 @@ Cli_Options :: struct {
input_path: string, input_path: string,
output_path: string, output_path: string,
link_arguments: []linker.Argument, link_arguments: []linker.Argument,
c_options: cimport.Options,
target: target.Target, target: target.Target,
} }
@@ -20,13 +22,24 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
options := Cli_Options{input_path=args[1], target=target.DEFAULT} options := Cli_Options{input_path=args[1], target=target.DEFAULT}
link_arguments: [dynamic]linker.Argument link_arguments: [dynamic]linker.Argument
link_arguments.allocator = allocator link_arguments.allocator = allocator
include_paths: [dynamic]string
include_paths.allocator = allocator
defines: [dynamic]string
defines.allocator = allocator
success := false
defer {
if !success {
delete(link_arguments)
delete(include_paths)
delete(defines)
}
}
output_set := false output_set := false
cursor := 2 cursor := 2
for cursor < len(args) { for cursor < len(args) {
option := args[cursor] option := args[cursor]
cursor += 1 cursor += 1
if cursor >= len(args) { if cursor >= len(args) {
delete(link_arguments)
return {}, false return {}, false
} }
value := args[cursor] value := args[cursor]
@@ -34,40 +47,43 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
switch option { switch option {
case "-o": case "-o":
if output_set { if output_set {
delete(link_arguments)
return {}, false return {}, false
} }
output_set = true output_set = true
options.output_path = value options.output_path = value
case "--link": case "--c-link":
append(&link_arguments, linker.Argument{kind=.Input, value=value}) append(&link_arguments, linker.Argument{kind=.Input, value=value})
case "--library-path": case "--c-library-path":
append(&link_arguments, linker.Argument{kind=.Library_Path, value=value}) append(&link_arguments, linker.Argument{kind=.Library_Path, value=value})
case "--library": case "--c-library":
append(&link_arguments, linker.Argument{kind=.Library, value=value}) append(&link_arguments, linker.Argument{kind=.Library, value=value})
case "--c-include-path":
append(&include_paths, value)
case "--c-define":
append(&defines, value)
case "--target": case "--target":
selected, ok := target.parse(value) selected, ok := target.parse(value)
if !ok { if !ok {
delete(link_arguments)
return {}, false return {}, false
} }
options.target = selected options.target = selected
case: case:
delete(link_arguments)
return {}, false return {}, false
} }
} }
if !output_set || len(options.output_path) == 0 { if !output_set || len(options.output_path) == 0 {
delete(link_arguments)
return {}, false return {}, false
} }
options.link_arguments = link_arguments[:] options.link_arguments = link_arguments[:]
options.c_options.include_paths = include_paths[:]
options.c_options.defines = defines[:]
success = true
return options, true return options, true
} }
print_usage :: proc() { print_usage :: proc() {
fmt.eprintln( fmt.eprintln(
"usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--link <path> | --library-path <dir> | --library <name>]...", "usage: brolang <package-directory> -o <executable> [--target aarch64-macos] [--c-link <path> | --c-library-path <dir> | --c-library <name> | --c-include-path <dir> | --c-define <name[=value]>]...",
) )
} }
@@ -78,7 +94,9 @@ main :: proc() {
os2.exit(2) os2.exit(2)
} }
defer delete(options.link_arguments) defer delete(options.link_arguments)
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target) defer delete(options.c_options.include_paths)
defer delete(options.c_options.defines)
status := compiler.compile_package(options.input_path, options.output_path, options.link_arguments, options.target, options.c_options)
if status != 0 { if status != 0 {
os2.exit(status) os2.exit(status)
} }