restricted c header imports
This commit is contained in:
+7
-4
@@ -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,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`
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# "quick"/"easy" fixes
|
# "quick" / "easy" fixes
|
||||||
|
|
||||||
- for global initialization cycles, report also starting and ending lines
|
- for global initialization cycles, report also starting and ending lines
|
||||||
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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(¶ms, 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
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Unwrap,
|
Unwrap,
|
||||||
Orelse,
|
Orelse,
|
||||||
Widen,
|
Widen,
|
||||||
|
Weaken_Pointer,
|
||||||
Negate,
|
Negate,
|
||||||
Add,
|
Add,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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()
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef BROLANG_CHILD_H
|
||||||
|
#define BROLANG_CHILD_H
|
||||||
|
|
||||||
|
int child_value(int value);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user