diff --git a/LANGUAGE.md b/LANGUAGE.md
index d6555f5..d9c813c 100644
--- a/LANGUAGE.md
+++ b/LANGUAGE.md
@@ -32,6 +32,9 @@
- Apple Silicon C ABI scalar and pointer lowering, including narrow integer extension attributes
- directory packages with merged declarations
- 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
@@ -39,6 +42,8 @@
- lazy semantic checking of demanded function specializations
- demand-driven LLVM declarations for referenced foreign functions
- 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
## PLANNED
@@ -53,9 +58,7 @@
- tuples and native variadic functions
- C unions, C enums, and by-value C record ABI lowering
-### c imports
+### advanced c imports
-- c headers imported as synthetic package namespaces
-- typedefs, enums, opaque records, and external variables
-- function pointers, callbacks, macros, and static inline functions
+- C enums, external variables, function pointers, callbacks, macros, and static inline functions
- target-specific by-value C record and union ABI lowering
diff --git a/README.md b/README.md
index b4b6ed7..c7e744f 100644
--- a/README.md
+++ b/README.md
@@ -22,12 +22,28 @@ invocation in command-line order:
```sh
./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.
-`--library-path
` becomes `-L`, and `--library ` becomes
-`-l`.
+`--c-link` accepts C sources, object files, archives, and direct library paths.
+`--c-library-path ` becomes `-L`, and `--c-library ` becomes
+`-l`. `--c-include-path ` and `--c-define ` 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/`:
@@ -63,10 +79,11 @@ Current prototype features:
- 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
- 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
- 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
+- Ordered linking of additional C sources, objects, archives, and libraries
- Checked signed addition and unary negation
- Static, eager runtime, and deferred problematic globals
- Runtime diagnostics followed by `llvm.trap`
diff --git a/TODO.md b/TODO.md
index dc17cc6..bd00fd1 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,4 +1,4 @@
-# "quick"/"easy" fixes
+# "quick" / "easy" fixes
- for global initialization cycles, report also starting and ending lines
@@ -29,15 +29,14 @@
- `Some :: c_struct`: opaque c-layout struct
- 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
- - `import "relative/path/to/header.h"`
- - `other :: import "relative/path/to/header.h"`
+ - `native :: import "relative/path/to/header.h"`
- import functions, typedefs, scalar types, and pointers to opaque records
- keep implementation linking separate from header imports
- cache imports by canonical header path and target/include/define configuration
- 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
- represent c variadics as a fixed parameter count plus a variadic flag
diff --git a/compiler/ast/ast.odin b/compiler/ast/ast.odin
index bbbe126..311153a 100644
--- a/compiler/ast/ast.odin
+++ b/compiler/ast/ast.odin
@@ -128,10 +128,12 @@ Function :: struct {
pkg: Package_Id,
file: File_Id,
c_abi: bool,
+ imported: bool,
has_body: bool,
params: []Param,
result: Type_Syntax,
body: []Stmt_Id,
+ unsupported_reason: string,
diagnostic: source.Diagnostic_Id,
}
@@ -167,6 +169,18 @@ Package :: struct {
path: string,
name: symbol.Id,
available: bool,
+ kind: Package_Kind,
+}
+
+Package_Kind :: enum u8 {
+ Native,
+ C_Header,
+}
+
+Unsupported :: struct {
+ pkg: Package_Id,
+ name: symbol.Id,
+ reason: string,
}
Module :: struct {
@@ -177,6 +191,7 @@ Module :: struct {
imports: [dynamic]Import,
files: [dynamic]File,
packages: [dynamic]Package,
+ unsupported: [dynamic]Unsupported,
strings: [dynamic]string,
type_store: types.Store,
allocator: mem.Allocator,
@@ -193,6 +208,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.imports.allocator = allocator
module.files.allocator = allocator
module.packages.allocator = allocator
+ module.unsupported.allocator = allocator
module.strings.allocator = allocator
return module
}
@@ -204,6 +220,7 @@ destroy_module :: proc(module: ^Module) {
for function in module.functions {
delete(function.params, module.allocator)
delete(function.body, module.allocator)
+ delete(function.unsupported_reason, module.allocator)
}
for import_item in module.imports {
delete(import_item.path, module.allocator)
@@ -211,6 +228,9 @@ destroy_module :: proc(module: ^Module) {
for pkg in module.packages {
delete(pkg.path, module.allocator)
}
+ for item in module.unsupported {
+ delete(item.reason, module.allocator)
+ }
for value in module.strings {
delete(value, module.allocator)
}
@@ -221,6 +241,7 @@ destroy_module :: proc(module: ^Module) {
delete(module.imports)
delete(module.files)
delete(module.packages)
+ delete(module.unsupported)
delete(module.strings)
types.destroy_store(&module.type_store)
}
diff --git a/compiler/backend/backend.odin b/compiler/backend/backend.odin
index e3b5805..c1cb66a 100644
--- a/compiler/backend/backend.odin
+++ b/compiler/backend/backend.odin
@@ -2,6 +2,7 @@ package backend
import "../linker"
import "../target"
+import "../cimport"
import "core:fmt"
import "core:mem"
import "core:os"
@@ -16,6 +17,7 @@ build_command :: proc(
llvm_path, output_path: string,
link_arguments: []linker.Argument,
selected := target.DEFAULT,
+ c_options := cimport.Options{},
allocator := context.allocator,
) -> []string {
command: [dynamic]string
@@ -26,7 +28,14 @@ build_command :: proc(
append_owned(&command, "-target", allocator)
append_owned(&command, target.name(selected), allocator)
append_owned(&command, "-Wno-override-module", allocator)
+ append_owned(&command, "-Wno-unused-command-line-argument", 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 {
switch argument.kind {
case .Input:
@@ -53,12 +62,13 @@ compile :: proc(
llvm_path, output_path: string,
link_arguments: []linker.Argument = nil,
selected := target.DEFAULT,
+ c_options := cimport.Options{},
) -> bool {
pid := os2.get_pid()
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
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)
state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=command},
diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin
index 0b9e5e2..da3f722 100644
--- a/compiler/checker/checker.odin
+++ b/compiler/checker/checker.odin
@@ -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))
}
+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 {
for existing in names {
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) {
for function, function_id in checker.ast_module.functions {
+ if len(function.unsupported_reason) > 0 {
+ continue
+ }
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
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 {
source.add(
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) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
@@ -500,6 +568,10 @@ validate_declarations :: proc(checker: ^Checker) {
}
if !function.has_body && function.c_abi {
for param in function.params {
+ if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) !=
+ source.INVALID_DIAGNOSTIC {
+ continue
+ }
if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
@@ -510,7 +582,8 @@ validate_declarations :: proc(checker: ^Checker) {
}
}
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.diagnostics,
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 {
continue
}
+ if function.imported && other.imported && function_signatures_equal(function, other) {
+ continue
+ }
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
@@ -901,6 +977,11 @@ infer_expr :: proc(
_ = pop(&stack)
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 {
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
@@ -1185,8 +1266,15 @@ coerce_expr :: proc(
return expr_id
}
if types.can_weaken_pointer(actual, expected, &checker.module.types) {
- checker.module.exprs[expr_id].type = expected
- return expr_id
+ return add_hir_expr(checker, hir.Expr{
+ 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) {
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,
})
} 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)
}
}
@@ -1775,7 +1866,22 @@ build_expr :: proc(
}
template := find_template(checker, expr.name, target_pkg)
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)
_ = pop(&stack)
continue
diff --git a/compiler/cimport/cimport.odin b/compiler/cimport/cimport.odin
new file mode 100644
index 0000000..d86a7e0
--- /dev/null
+++ b/compiler/cimport/cimport.odin
@@ -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)
+}
diff --git a/compiler/cimport/libclang.odin b/compiler/cimport/libclang.odin
new file mode 100644
index 0000000..38e7e7a
--- /dev/null
+++ b/compiler/cimport/libclang.odin
@@ -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.. 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..= 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
+}
diff --git a/compiler/compiler.odin b/compiler/compiler.odin
index 7b787c8..a12a76c 100644
--- a/compiler/compiler.odin
+++ b/compiler/compiler.odin
@@ -1,6 +1,7 @@
package compiler
import "./backend"
+import "./cimport"
import "./checker"
import "./llvm"
import "./linker"
@@ -19,6 +20,7 @@ compile_package :: proc(
input_path, output_path: string,
link_arguments: []linker.Argument = nil,
selected := target.DEFAULT,
+ c_options := cimport.Options{},
) -> int {
sources := source.init_store()
defer source.destroy_store(&sources)
@@ -59,6 +61,8 @@ compile_package :: proc(
&symbols,
vmem.arena_allocator(&lexer_arena),
vmem.arena_allocator(&parser_arena),
+ c_options,
+ selected,
)
if !loaded {
source.print_all(&diagnostics)
@@ -83,7 +87,7 @@ compile_package :: proc(
}
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
}
if len(diagnostics.items) > 0 {
diff --git a/compiler/hir/hir.odin b/compiler/hir/hir.odin
index 7df3941..f10a134 100644
--- a/compiler/hir/hir.odin
+++ b/compiler/hir/hir.odin
@@ -93,6 +93,7 @@ Expr_Kind :: enum u8 {
Unwrap,
Orelse,
Widen,
+ Weaken_Pointer,
Negate,
Add,
Pointer_Add,
diff --git a/compiler/ir/ir.odin b/compiler/ir/ir.odin
index 1e9f320..eb2d826 100644
--- a/compiler/ir/ir.odin
+++ b/compiler/ir/ir.odin
@@ -87,6 +87,7 @@ Opcode :: enum u8 {
Orelse_Begin,
Orelse,
Widen,
+ Weaken_Pointer,
Neg_Checked,
Add_Checked,
Pointer_Add,
diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin
index 18af1f7..9be0972 100644
--- a/compiler/llvm/llvm.odin
+++ b/compiler/llvm/llvm.odin
@@ -112,7 +112,7 @@ valid_value :: proc(
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
- .Widen, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
+ .Widen, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.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))
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))
+ 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:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
@@ -985,7 +992,19 @@ emit_constructor :: 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 {
strings.write_string(&emitter.builder, "declare ")
} else {
diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin
index 2ce0242..20cf65f 100644
--- a/compiler/loader/loader.odin
+++ b/compiler/loader/loader.odin
@@ -1,11 +1,14 @@
package loader
import "../ast"
+import "../cimport"
import "../lexer"
import "../parser"
import "../source"
import "../symbol"
+import "../target"
import "../types"
+import "core:fmt"
import "core:mem"
import "core:os"
import "core:path/filepath"
@@ -19,6 +22,10 @@ State :: struct {
symbols: ^symbol.Table,
token_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,
}
@@ -104,6 +111,214 @@ resolve_import_path :: proc(state: ^State, importing_path, import_path: string)
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 {
canonical, ok := filepath.abs(path, state.allocator)
if !ok || !os.is_dir(path) {
@@ -191,7 +406,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
continue
}
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
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
state.module.imports[import_id].valid = false
@@ -293,14 +508,19 @@ canonical_type :: proc(
import_item := module.imports[import_id]
resolved := types.find_named(&module.type_store, u32(import_item.target), item.name)
if types.is_valid(resolved) {
- mapping[index] = resolved
- return resolved
+ mapping[index] = canonical_type(module, resolved, mapping, visiting)
+ return mapping[index]
}
}
}
mapping[index] = 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 {
mapping[index] = value
fields := types.fields_for(&module.type_store, value)
@@ -347,6 +567,8 @@ load :: proc(
symbols: ^symbol.Table,
token_allocator := context.allocator,
allocator := context.allocator,
+ c_options := cimport.Options{},
+ selected := target.DEFAULT,
) -> (ast.Module, bool) {
module := ast.init_module(allocator)
state := State{
@@ -356,6 +578,17 @@ load :: proc(
symbols=symbols,
token_allocator=token_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)
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin
index 1750590..4d8d157 100644
--- a/compiler/lower/lower.odin
+++ b/compiler/lower/lower.odin
@@ -324,7 +324,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
- case .Widen:
+ case .Widen, .Weaken_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
@@ -358,7 +358,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}
if frame.stage == 1 {
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,
})
_ = pop(&stack)
diff --git a/compiler/types/types.odin b/compiler/types/types.odin
index b14439e..ee7b500 100644
--- a/compiler/types/types.odin
+++ b/compiler/types/types.odin
@@ -59,6 +59,7 @@ Kind :: enum u8 {
Slice,
Optional,
Named,
+ Alias,
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 {
normalized_file := file if qualifier != 0 else u32(0)
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.file == normalized_file {
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 {
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 {
return DYNAMIC_START+Type(index)
}
@@ -151,6 +152,18 @@ find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
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 {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared {
diff --git a/compiler_tests.odin b/compiler_tests.odin
index 5d80262..c4b30a2 100644
--- a/compiler_tests.odin
+++ b/compiler_tests.odin
@@ -3,6 +3,7 @@ package main
import compiler_core "./compiler"
import "./compiler/ast"
import "./compiler/backend"
+import "./compiler/cimport"
import "./compiler/checker"
import "./compiler/hir"
import "./compiler/ir"
@@ -18,6 +19,7 @@ import "./compiler/target"
import "./compiler/token"
import "./compiler/types"
import "core:fmt"
+import "core:mem"
import "core:os"
import "core:os/os2"
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{
"brolang",
"app",
- "--link",
+ "--c-link",
"native.c",
"-o",
"app.out",
- "--library-path",
+ "--c-library-path",
"vendor/lib",
- "--library",
+ "--c-library",
"thing",
- "--link",
+ "--c-link",
"helper.o",
+ "--c-include-path",
+ "vendor/include",
+ "--c-define",
+ "FEATURE=1",
"--target",
"aarch64-macos",
})
defer delete(options.link_arguments)
+ defer delete(options.c_options.include_paths)
+ defer delete(options.c_options.defines)
testing.expect(t, valid)
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[2].kind, linker.Kind.Library)
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")
_, 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_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"})
+ _, 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, !incomplete_valid)
testing.expect(t, !duplicate_output_valid)
testing.expect(t, !duplicate_empty_output_valid)
testing.expect(t, !invalid_target)
+ testing.expect(t, !legacy_link)
+ testing.expect(t, !legacy_library_path)
+ testing.expect(t, !legacy_library)
}
@(test)
@@ -1319,6 +1335,44 @@ foreign_function_links_from_c_source :: proc(t: ^testing.T) {
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)
interop_foundation_matches_zig_compiled_apple_silicon_c_fixture :: proc(t: ^testing.T) {
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=.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)
expected := []string{
@@ -1580,7 +1638,10 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
"-target",
"aarch64-macos",
"-Wno-override-module",
+ "-Wno-unused-command-line-argument",
"module.ll",
+ "-Ivendor/include",
+ "-DFEATURE=1",
"native.c",
"-Lvendor/lib",
"-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)
source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t: ^testing.T) {
store := source.init_store()
diff --git a/examples/interop/header/app/main.bro b/examples/interop/header/app/main.bro
new file mode 100644
index 0000000..fd7d119
--- /dev/null
+++ b/examples/interop/header/app/main.bro
@@ -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)
+}
diff --git a/examples/interop/header/include/child.h b/examples/interop/header/include/child.h
new file mode 100644
index 0000000..a762f95
--- /dev/null
+++ b/examples/interop/header/include/child.h
@@ -0,0 +1,6 @@
+#ifndef BROLANG_CHILD_H
+#define BROLANG_CHILD_H
+
+int child_value(int value);
+
+#endif
diff --git a/examples/interop/header/include/native.h b/examples/interop/header/include/native.h
new file mode 100644
index 0000000..36838d6
--- /dev/null
+++ b/examples/interop/header/include/native.h
@@ -0,0 +1,40 @@
+#ifndef BROLANG_NATIVE_H
+#define BROLANG_NATIVE_H
+
+#include
+
+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
diff --git a/examples/interop/header/native.c b/examples/interop/header/native.c
new file mode 100644
index 0000000..bce8d6f
--- /dev/null
+++ b/examples/interop/header/native.c
@@ -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
diff --git a/examples/interop/header_cache/main.bro b/examples/interop/header_cache/main.bro
new file mode 100644
index 0000000..323cd8d
--- /dev/null
+++ b/examples/interop/header_cache/main.bro
@@ -0,0 +1,4 @@
+first :: import "../header/include/native.h"
+second :: import "../header/include/native.h"
+
+main :: func() void {}
diff --git a/examples/interop/header_duplicate/main.bro b/examples/interop/header_duplicate/main.bro
new file mode 100644
index 0000000..1913a8a
--- /dev/null
+++ b/examples/interop/header_duplicate/main.bro
@@ -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)
+}
diff --git a/examples/interop/header_unsupported/main.bro b/examples/interop/header_unsupported/main.bro
new file mode 100644
index 0000000..3999bd6
--- /dev/null
+++ b/examples/interop/header_unsupported/main.bro
@@ -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)
+}
diff --git a/main.odin b/main.odin
index 8489dbd..f9c6aaf 100644
--- a/main.odin
+++ b/main.odin
@@ -1,6 +1,7 @@
package main
import "./compiler"
+import "./compiler/cimport"
import "./compiler/linker"
import "./compiler/target"
import "core:fmt"
@@ -10,6 +11,7 @@ Cli_Options :: struct {
input_path: string,
output_path: string,
link_arguments: []linker.Argument,
+ c_options: cimport.Options,
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}
link_arguments: [dynamic]linker.Argument
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
cursor := 2
for cursor < len(args) {
option := args[cursor]
cursor += 1
if cursor >= len(args) {
- delete(link_arguments)
return {}, false
}
value := args[cursor]
@@ -34,40 +47,43 @@ parse_cli_args :: proc(args: []string, allocator := context.allocator) -> (Cli_O
switch option {
case "-o":
if output_set {
- delete(link_arguments)
return {}, false
}
output_set = true
options.output_path = value
- case "--link":
+ case "--c-link":
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})
- case "--library":
+ case "--c-library":
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":
selected, ok := target.parse(value)
if !ok {
- delete(link_arguments)
return {}, false
}
options.target = selected
case:
- delete(link_arguments)
return {}, false
}
}
if !output_set || len(options.output_path) == 0 {
- delete(link_arguments)
return {}, false
}
options.link_arguments = link_arguments[:]
+ options.c_options.include_paths = include_paths[:]
+ options.c_options.defines = defines[:]
+ success = true
return options, true
}
print_usage :: proc() {
fmt.eprintln(
- "usage: brolang -o [--target aarch64-macos] [--link | --library-path | --library ]...",
+ "usage: brolang -o [--target aarch64-macos] [--c-link | --c-library-path | --c-library | --c-include-path | --c-define ]...",
)
}
@@ -78,7 +94,9 @@ main :: proc() {
os2.exit(2)
}
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 {
os2.exit(status)
}