restricted c header imports

This commit is contained in:
2026-06-14 14:42:38 +02:00
parent 4e860b033e
commit 638ca57f5c
24 changed files with 1377 additions and 45 deletions
+21
View File
@@ -128,10 +128,12 @@ Function :: struct {
pkg: Package_Id,
file: File_Id,
c_abi: bool,
imported: bool,
has_body: bool,
params: []Param,
result: Type_Syntax,
body: []Stmt_Id,
unsupported_reason: string,
diagnostic: source.Diagnostic_Id,
}
@@ -167,6 +169,18 @@ Package :: struct {
path: string,
name: symbol.Id,
available: bool,
kind: Package_Kind,
}
Package_Kind :: enum u8 {
Native,
C_Header,
}
Unsupported :: struct {
pkg: Package_Id,
name: symbol.Id,
reason: string,
}
Module :: struct {
@@ -177,6 +191,7 @@ Module :: struct {
imports: [dynamic]Import,
files: [dynamic]File,
packages: [dynamic]Package,
unsupported: [dynamic]Unsupported,
strings: [dynamic]string,
type_store: types.Store,
allocator: mem.Allocator,
@@ -193,6 +208,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
module.imports.allocator = allocator
module.files.allocator = allocator
module.packages.allocator = allocator
module.unsupported.allocator = allocator
module.strings.allocator = allocator
return module
}
@@ -204,6 +220,7 @@ destroy_module :: proc(module: ^Module) {
for function in module.functions {
delete(function.params, module.allocator)
delete(function.body, module.allocator)
delete(function.unsupported_reason, module.allocator)
}
for import_item in module.imports {
delete(import_item.path, module.allocator)
@@ -211,6 +228,9 @@ destroy_module :: proc(module: ^Module) {
for pkg in module.packages {
delete(pkg.path, module.allocator)
}
for item in module.unsupported {
delete(item.reason, module.allocator)
}
for value in module.strings {
delete(value, module.allocator)
}
@@ -221,6 +241,7 @@ destroy_module :: proc(module: ^Module) {
delete(module.imports)
delete(module.files)
delete(module.packages)
delete(module.unsupported)
delete(module.strings)
types.destroy_store(&module.type_store)
}
+11 -1
View File
@@ -2,6 +2,7 @@ package backend
import "../linker"
import "../target"
import "../cimport"
import "core:fmt"
import "core:mem"
import "core:os"
@@ -16,6 +17,7 @@ build_command :: proc(
llvm_path, output_path: string,
link_arguments: []linker.Argument,
selected := target.DEFAULT,
c_options := cimport.Options{},
allocator := context.allocator,
) -> []string {
command: [dynamic]string
@@ -26,7 +28,14 @@ build_command :: proc(
append_owned(&command, "-target", allocator)
append_owned(&command, target.name(selected), allocator)
append_owned(&command, "-Wno-override-module", allocator)
append_owned(&command, "-Wno-unused-command-line-argument", allocator)
append_owned(&command, llvm_path, allocator)
for path in c_options.include_paths {
append(&command, fmt.aprintf("-I%s", path, allocator=allocator))
}
for define in c_options.defines {
append(&command, fmt.aprintf("-D%s", define, allocator=allocator))
}
for argument in link_arguments {
switch argument.kind {
case .Input:
@@ -53,12 +62,13 @@ compile :: proc(
llvm_path, output_path: string,
link_arguments: []linker.Argument = nil,
selected := target.DEFAULT,
c_options := cimport.Options{},
) -> bool {
pid := os2.get_pid()
temporary_output := fmt.tprintf("%s.brolang-tmp-%d", output_path, pid)
defer _ = os.remove(temporary_output)
command := build_command(llvm_path, temporary_output, link_arguments, selected)
command := build_command(llvm_path, temporary_output, link_arguments, selected, c_options)
defer destroy_command(command)
state, stdout, stderr, err := os2.process_exec(
os2.Process_Desc{command=command},
+111 -5
View File
@@ -409,6 +409,62 @@ add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
}
find_unsupported :: proc(checker: ^Checker, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Unsupported, bool) {
for item in checker.ast_module.unsupported {
if item.pkg == pkg && item.name == name {
return item, true
}
}
return {}, false
}
add_unsupported_diagnostic :: proc(checker: ^Checker, span: source.Span, pkg: ast.Package_Id, name: symbol.Id) -> source.Diagnostic_Id {
if item, ok := find_unsupported(checker, pkg, name); ok {
return source.addf(
checker.diagnostics,
span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, name),
item.reason,
)
}
return source.INVALID_DIAGNOSTIC
}
add_unsupported_type_diagnostic :: proc(
checker: ^Checker,
span: source.Span,
value: types.Type,
depth := 0,
) -> source.Diagnostic_Id {
if depth > 64 {
return source.INVALID_DIAGNOSTIC
}
item, ok := types.node(&checker.module.types, value)
if !ok {
return source.INVALID_DIAGNOSTIC
}
if item.kind == .Alias {
return add_unsupported_diagnostic(checker, span, ast.Package_Id(item.pkg), symbol.Id(item.name))
}
if types.is_valid(item.child) {
return add_unsupported_type_diagnostic(checker, span, item.child, depth+1)
}
return source.INVALID_DIAGNOSTIC
}
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
if left.result != right.result || len(left.params) != len(right.params) {
return false
}
for param, index in left.params {
if param.type != right.params[index].type {
return false
}
}
return true
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names {
if existing == name {
@@ -454,9 +510,17 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions {
if len(function.unsupported_reason) > 0 {
continue
}
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
for param in function.params {
if diagnostic := add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
continue
}
if param.type == types.VOID {
source.add(
checker.diagnostics,
@@ -482,6 +546,10 @@ validate_declarations :: proc(checker: ^Checker) {
)
}
}
if diagnostic := add_unsupported_type_diagnostic(checker, function.span, type_from_syntax(function.result));
diagnostic != source.INVALID_DIAGNOSTIC {
checker.template_diagnostics[function_id] = diagnostic
}
if types.contains_c_struct_by_value(type_from_syntax(function.result), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
@@ -500,6 +568,10 @@ validate_declarations :: proc(checker: ^Checker) {
}
if !function.has_body && function.c_abi {
for param in function.params {
if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) !=
source.INVALID_DIAGNOSTIC {
continue
}
if !types.is_c_signature_type(type_from_syntax(param.type), &checker.module.types) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
@@ -510,7 +582,8 @@ validate_declarations :: proc(checker: ^Checker) {
}
}
result := type_from_syntax(function.result)
if !types.is_c_signature_type(result, &checker.module.types, true) {
if add_unsupported_type_diagnostic(checker, function.span, result) == source.INVALID_DIAGNOSTIC &&
!types.is_c_signature_type(result, &checker.module.types, true) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
@@ -547,6 +620,9 @@ validate_declarations :: proc(checker: ^Checker) {
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
continue
}
if function.imported && other.imported && function_signatures_equal(function, other) {
continue
}
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
@@ -901,6 +977,11 @@ infer_expr :: proc(
_ = pop(&stack)
continue
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
last = types.INVALID
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
declared := type_from_syntax(checker.ast_module.functions[template].result)
last = declared if is_runtime_type(checker, declared) || types.is_void(declared) else types.INVALID
@@ -1185,8 +1266,15 @@ coerce_expr :: proc(
return expr_id
}
if types.can_weaken_pointer(actual, expected, &checker.module.types) {
checker.module.exprs[expr_id].type = expected
return expr_id
return add_hir_expr(checker, hir.Expr{
kind=.Weaken_Pointer,
span=span,
type=expected,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
if types.is_optional(expected, &checker.module.types) {
child := types.child_type(expected, &checker.module.types)
@@ -1754,7 +1842,10 @@ build_expr :: proc(
target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
})
} else {
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_name_resolution_diagnostic(checker, expr, target_pkg)
}
last = invalid_hir_expr(checker, expr.span, id)
}
}
@@ -1775,7 +1866,22 @@ build_expr :: proc(
}
template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION {
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
id := add_unsupported_diagnostic(checker, expr.span, target_pkg, expr.name)
if id == source.INVALID_DIAGNOSTIC {
id = add_call_resolution_diagnostic(checker, expr, target_pkg)
}
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if len(checker.ast_module.functions[template].unsupported_reason) > 0 {
id := source.addf(
checker.diagnostics,
expr.span,
"C declaration '%s' is unavailable: %s",
symbol_text(checker, expr.name),
checker.ast_module.functions[template].unsupported_reason,
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
+140
View File
@@ -0,0 +1,140 @@
package cimport
import "../target"
import "core:mem"
Type_Id :: distinct u32
INVALID_TYPE :: Type_Id(0xffff_ffff)
Type_Kind :: enum u8 {
Invalid,
Void,
C_Char,
C_Schar,
C_Uchar,
C_Short,
C_Ushort,
C_Int,
C_Uint,
C_Long,
C_Ulong,
C_Longlong,
C_Ulonglong,
C_Float,
C_Double,
C_Longdouble,
Pointer,
Record,
}
Type :: struct {
kind: Type_Kind,
child: Type_Id,
record: u32,
mutable: bool,
}
Record :: struct {
name: string,
identity: string,
}
Alias :: struct {
name: string,
type: Type_Id,
reason: string,
}
Function :: struct {
name: string,
params: []Type_Id,
result: Type_Id,
reason: string,
}
Unsupported :: struct {
name: string,
reason: string,
}
Result :: struct {
types: [dynamic]Type,
records: [dynamic]Record,
aliases: [dynamic]Alias,
functions: [dynamic]Function,
unsupported: [dynamic]Unsupported,
error_message: string,
infrastructure: bool,
available: bool,
allocator: mem.Allocator,
}
init_result :: proc(allocator := context.allocator) -> Result {
result: Result
result.allocator = allocator
result.types.allocator = allocator
result.records.allocator = allocator
result.aliases.allocator = allocator
result.functions.allocator = allocator
result.unsupported.allocator = allocator
return result
}
destroy_result :: proc(result: ^Result) {
for record in result.records {
delete(record.name, result.allocator)
delete(record.identity, result.allocator)
}
for alias in result.aliases {
delete(alias.name, result.allocator)
delete(alias.reason, result.allocator)
}
for function in result.functions {
delete(function.name, result.allocator)
delete(function.params, result.allocator)
delete(function.reason, result.allocator)
}
for item in result.unsupported {
delete(item.name, result.allocator)
delete(item.reason, result.allocator)
}
delete(result.error_message, result.allocator)
delete(result.types)
delete(result.records)
delete(result.aliases)
delete(result.functions)
delete(result.unsupported)
}
Request :: struct {
path: string,
include_paths: []string,
defines: []string,
target: target.Target,
}
Backend_Proc :: proc(user_data: rawptr, request: Request, allocator: mem.Allocator) -> Result
Backend :: struct {
import_header: Backend_Proc,
user_data: rawptr,
}
Options :: struct {
include_paths: []string,
defines: []string,
backend: Backend,
}
import_header :: proc(options: Options, path: string, selected := target.DEFAULT, allocator := context.allocator) -> Result {
request := Request{
path=path,
include_paths=options.include_paths,
defines=options.defines,
target=selected,
}
if options.backend.import_header != nil {
return options.backend.import_header(options.backend.user_data, request, allocator)
}
return import_with_libclang(nil, request, allocator)
}
+464
View File
@@ -0,0 +1,464 @@
package cimport
import "../target"
import "base:runtime"
import "core:dynlib"
import "core:fmt"
import "core:mem"
import "core:os/os2"
import "core:strings"
CXCursor :: struct {
kind: i32,
xdata: i32,
data: [3]rawptr,
}
CXType :: struct {
kind: i32,
data: [2]rawptr,
}
CXString :: struct {
data: rawptr,
private_flags: u32,
}
CXIndex :: distinct rawptr
CXTranslationUnit :: distinct rawptr
CXDiagnostic :: distinct rawptr
Cursor_Visitor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32
Api :: struct {
library: dynlib.Library,
create_index: proc "c"(i32, i32) -> CXIndex,
dispose_index: proc "c"(CXIndex),
parse_translation_unit: proc "c"(CXIndex, cstring, [^]cstring, i32, rawptr, u32, u32, ^CXTranslationUnit) -> i32,
dispose_translation_unit: proc "c"(CXTranslationUnit),
get_translation_unit_cursor: proc "c"(CXTranslationUnit) -> CXCursor,
visit_children: proc "c"(CXCursor, Cursor_Visitor, rawptr) -> u32,
get_cursor_kind: proc "c"(CXCursor) -> i32,
get_cursor_spelling: proc "c"(CXCursor) -> CXString,
get_cursor_usr: proc "c"(CXCursor) -> CXString,
get_cursor_linkage: proc "c"(CXCursor) -> i32,
get_cursor_type: proc "c"(CXCursor) -> CXType,
get_typedef_underlying_type: proc "c"(CXCursor) -> CXType,
get_type_declaration: proc "c"(CXType) -> CXCursor,
get_canonical_type: proc "c"(CXType) -> CXType,
get_pointee_type: proc "c"(CXType) -> CXType,
get_result_type: proc "c"(CXType) -> CXType,
get_num_arg_types: proc "c"(CXType) -> i32,
get_arg_type: proc "c"(CXType, u32) -> CXType,
is_const_qualified_type: proc "c"(CXType) -> u32,
is_volatile_qualified_type: proc "c"(CXType) -> u32,
cursor_is_variadic: proc "c"(CXCursor) -> u32,
get_num_diagnostics: proc "c"(CXTranslationUnit) -> u32,
get_diagnostic: proc "c"(CXTranslationUnit, u32) -> CXDiagnostic,
get_diagnostic_severity: proc "c"(CXDiagnostic) -> i32,
get_diagnostic_spelling: proc "c"(CXDiagnostic) -> CXString,
dispose_diagnostic: proc "c"(CXDiagnostic),
get_cstring: proc "c"(CXString) -> cstring,
dispose_string: proc "c"(CXString),
}
CXCursor_StructDecl :: i32(2)
CXCursor_UnionDecl :: i32(3)
CXCursor_EnumDecl :: i32(5)
CXCursor_FunctionDecl :: i32(8)
CXCursor_VarDecl :: i32(9)
CXCursor_TypedefDecl :: i32(20)
CXCursor_MacroDefinition :: i32(501)
CXLinkage_External :: i32(4)
CXType_Invalid :: i32(0)
CXType_Unexposed :: i32(1)
CXType_Void :: i32(2)
CXType_Char_U :: i32(4)
CXType_UChar :: i32(5)
CXType_UShort :: i32(8)
CXType_UInt :: i32(9)
CXType_ULong :: i32(10)
CXType_ULongLong :: i32(11)
CXType_Char_S :: i32(13)
CXType_SChar :: i32(14)
CXType_Short :: i32(16)
CXType_Int :: i32(17)
CXType_Long :: i32(18)
CXType_LongLong :: i32(19)
CXType_Float :: i32(21)
CXType_Double :: i32(22)
CXType_LongDouble :: i32(23)
CXType_Pointer :: i32(101)
CXType_Record :: i32(105)
CXType_Enum :: i32(106)
CXType_Typedef :: i32(107)
CXType_FunctionNoProto :: i32(110)
CXType_FunctionProto :: i32(111)
CXType_Elaborated :: i32(119)
CXType_Attributed :: i32(163)
CXChildVisit_Continue :: i32(1)
CXTranslationUnit_DetailedPreprocessingRecord :: u32(0x01)
CXTranslationUnit_SkipFunctionBodies :: u32(0x40)
CXTranslationUnit_KeepGoing :: u32(0x200)
CXDiagnostic_Error :: i32(3)
load_proc :: proc(api: ^Api, name: string, destination: ^$T) -> bool {
address, found := dynlib.symbol_address(api.library, name)
if !found {
return false
}
destination^ = transmute(T)address
return true
}
load_api_from :: proc(path: string) -> (Api, bool) {
api: Api
library, loaded := dynlib.load_library(path)
if !loaded {
return {}, false
}
api.library = library
ok :=
load_proc(&api, "clang_createIndex", &api.create_index) &&
load_proc(&api, "clang_disposeIndex", &api.dispose_index) &&
load_proc(&api, "clang_parseTranslationUnit2", &api.parse_translation_unit) &&
load_proc(&api, "clang_disposeTranslationUnit", &api.dispose_translation_unit) &&
load_proc(&api, "clang_getTranslationUnitCursor", &api.get_translation_unit_cursor) &&
load_proc(&api, "clang_visitChildren", &api.visit_children) &&
load_proc(&api, "clang_getCursorKind", &api.get_cursor_kind) &&
load_proc(&api, "clang_getCursorSpelling", &api.get_cursor_spelling) &&
load_proc(&api, "clang_getCursorUSR", &api.get_cursor_usr) &&
load_proc(&api, "clang_getCursorLinkage", &api.get_cursor_linkage) &&
load_proc(&api, "clang_getCursorType", &api.get_cursor_type) &&
load_proc(&api, "clang_getTypedefDeclUnderlyingType", &api.get_typedef_underlying_type) &&
load_proc(&api, "clang_getTypeDeclaration", &api.get_type_declaration) &&
load_proc(&api, "clang_getCanonicalType", &api.get_canonical_type) &&
load_proc(&api, "clang_getPointeeType", &api.get_pointee_type) &&
load_proc(&api, "clang_getResultType", &api.get_result_type) &&
load_proc(&api, "clang_getNumArgTypes", &api.get_num_arg_types) &&
load_proc(&api, "clang_getArgType", &api.get_arg_type) &&
load_proc(&api, "clang_isConstQualifiedType", &api.is_const_qualified_type) &&
load_proc(&api, "clang_isVolatileQualifiedType", &api.is_volatile_qualified_type) &&
load_proc(&api, "clang_Cursor_isVariadic", &api.cursor_is_variadic) &&
load_proc(&api, "clang_getNumDiagnostics", &api.get_num_diagnostics) &&
load_proc(&api, "clang_getDiagnostic", &api.get_diagnostic) &&
load_proc(&api, "clang_getDiagnosticSeverity", &api.get_diagnostic_severity) &&
load_proc(&api, "clang_getDiagnosticSpelling", &api.get_diagnostic_spelling) &&
load_proc(&api, "clang_disposeDiagnostic", &api.dispose_diagnostic) &&
load_proc(&api, "clang_getCString", &api.get_cstring) &&
load_proc(&api, "clang_disposeString", &api.dispose_string)
if !ok {
_ = dynlib.unload_library(api.library)
return {}, false
}
return api, true
}
load_api :: proc() -> (Api, bool) {
if override, found := os2.lookup_env_alloc("BROLANG_LIBCLANG_PATH", context.temp_allocator); found {
if api, ok := load_api_from(override); ok {
return api, true
}
}
candidates := [?]string{
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib",
"/Library/Developer/CommandLineTools/usr/lib/libclang.dylib",
"/opt/homebrew/opt/llvm/lib/libclang.dylib",
"/opt/homebrew/opt/llvm@21/lib/libclang.dylib",
"libclang.dylib",
"libclang.so",
}
for candidate in candidates {
if api, ok := load_api_from(candidate); ok {
return api, true
}
}
return {}, false
}
clone_cx_string :: proc(api: ^Api, value: CXString, allocator: mem.Allocator) -> string {
defer api.dispose_string(value)
text := api.get_cstring(value)
if text == nil {
return fmt.aprintf("", allocator=allocator)
}
return fmt.aprintf("%s", string(text), allocator=allocator)
}
Context :: struct {
api: ^Api,
result: ^Result,
allocator: mem.Allocator,
}
add_type :: proc(ctx: ^Context, value: Type) -> Type_Id {
id := Type_Id(len(ctx.result.types))
append(&ctx.result.types, value)
return id
}
find_record :: proc(ctx: ^Context, identity: string) -> (u32, bool) {
for record, index in ctx.result.records {
if record.identity == identity {
return u32(index), true
}
}
return 0, false
}
add_record :: proc(ctx: ^Context, declaration: CXCursor, preferred_name: string) -> u32 {
identity := clone_cx_string(ctx.api, ctx.api.get_cursor_usr(declaration), ctx.allocator)
if len(identity) == 0 {
delete(identity, ctx.allocator)
identity = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
}
if index, ok := find_record(ctx, identity); ok {
delete(identity, ctx.allocator)
return index
}
name := fmt.aprintf("%s", preferred_name, allocator=ctx.allocator)
if len(name) == 0 {
delete(name, ctx.allocator)
name = clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
}
index := u32(len(ctx.result.records))
append(&ctx.result.records, Record{name=name, identity=identity})
return index
}
translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := "", depth := 0) -> Type_Id {
if depth > 64 || value.kind == CXType_Invalid || ctx.api.is_volatile_qualified_type(value) != 0 {
return INVALID_TYPE
}
switch value.kind {
case CXType_Void: return add_type(ctx, Type{kind=.Void, child=INVALID_TYPE})
case CXType_Char_U: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE})
case CXType_Char_S: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE})
case CXType_SChar: return add_type(ctx, Type{kind=.C_Schar, child=INVALID_TYPE})
case CXType_UChar: return add_type(ctx, Type{kind=.C_Uchar, child=INVALID_TYPE})
case CXType_Short: return add_type(ctx, Type{kind=.C_Short, child=INVALID_TYPE})
case CXType_UShort: return add_type(ctx, Type{kind=.C_Ushort, child=INVALID_TYPE})
case CXType_Int: return add_type(ctx, Type{kind=.C_Int, child=INVALID_TYPE})
case CXType_UInt: return add_type(ctx, Type{kind=.C_Uint, child=INVALID_TYPE})
case CXType_Long: return add_type(ctx, Type{kind=.C_Long, child=INVALID_TYPE})
case CXType_ULong: return add_type(ctx, Type{kind=.C_Ulong, child=INVALID_TYPE})
case CXType_LongLong: return add_type(ctx, Type{kind=.C_Longlong, child=INVALID_TYPE})
case CXType_ULongLong: return add_type(ctx, Type{kind=.C_Ulonglong, child=INVALID_TYPE})
case CXType_Float: return add_type(ctx, Type{kind=.C_Float, child=INVALID_TYPE})
case CXType_Double: return add_type(ctx, Type{kind=.C_Double, child=INVALID_TYPE})
case CXType_LongDouble: return add_type(ctx, Type{kind=.C_Longdouble, child=INVALID_TYPE})
case CXType_Pointer:
pointee := ctx.api.get_pointee_type(value)
child := translate_type(ctx, pointee, "", depth+1)
if child == INVALID_TYPE {
return INVALID_TYPE
}
return add_type(ctx, Type{
kind=.Pointer,
child=child,
mutable=ctx.api.is_const_qualified_type(pointee) == 0,
})
case CXType_Record:
declaration := ctx.api.get_type_declaration(value)
if ctx.api.get_cursor_kind(declaration) == CXCursor_UnionDecl {
return INVALID_TYPE
}
record := add_record(ctx, declaration, preferred_record_name)
return add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
case CXType_Typedef:
declaration := ctx.api.get_type_declaration(value)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(declaration), ctx.allocator)
defer delete(name, ctx.allocator)
return translate_type(ctx, ctx.api.get_typedef_underlying_type(declaration), name, depth+1)
case CXType_Elaborated, CXType_Attributed, CXType_Unexposed:
canonical := ctx.api.get_canonical_type(value)
if canonical.kind == value.kind {
return INVALID_TYPE
}
return translate_type(ctx, canonical, preferred_record_name, depth+1)
case CXType_Enum, CXType_FunctionNoProto, CXType_FunctionProto:
return INVALID_TYPE
}
return INVALID_TYPE
}
has_named :: proc(items: []Unsupported, name: string) -> bool {
for item in items {
if item.name == name {
return true
}
}
return false
}
add_unsupported :: proc(ctx: ^Context, name, reason: string) {
if len(name) == 0 || strings.has_prefix(name, "__") || has_named(ctx.result.unsupported[:], name) {
return
}
append(&ctx.result.unsupported, Unsupported{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
}
add_alias :: proc(ctx: ^Context, name: string, value: Type_Id, reason := "") {
if len(name) == 0 {
return
}
for alias in ctx.result.aliases {
if alias.name == name {
return
}
}
append(&ctx.result.aliases, Alias{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
type=value,
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
}
visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
context = runtime.default_context()
ctx := (^Context)(client_data)
kind := ctx.api.get_cursor_kind(cursor)
name := clone_cx_string(ctx.api, ctx.api.get_cursor_spelling(cursor), ctx.allocator)
defer delete(name, ctx.allocator)
switch kind {
case CXCursor_FunctionDecl:
if len(name) == 0 {
return CXChildVisit_Continue
}
reason := ""
linkage := ctx.api.get_cursor_linkage(cursor)
if linkage != CXLinkage_External {
reason = "static and non-external C functions are not supported"
} else if ctx.api.cursor_is_variadic(cursor) != 0 {
reason = "C variadic functions are not supported"
}
function_type := ctx.api.get_cursor_type(cursor)
result_type := translate_type(ctx, ctx.api.get_result_type(function_type))
if result_type == INVALID_TYPE && len(reason) == 0 {
reason = "function result type is not supported"
}
params: [dynamic]Type_Id
params.allocator = ctx.allocator
count := ctx.api.get_num_arg_types(function_type)
if count < 0 {
reason = "function declaration has no prototype"
} else {
for index in 0..<count {
param := translate_type(ctx, ctx.api.get_arg_type(function_type, u32(index)))
append(&params, param)
if param == INVALID_TYPE && len(reason) == 0 {
reason = "function parameter type is not supported"
}
}
}
append(&ctx.result.functions, Function{
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
params=params[:],
result=result_type,
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
case CXCursor_TypedefDecl:
value := translate_type(ctx, ctx.api.get_typedef_underlying_type(cursor), name)
reason := ""
if value == INVALID_TYPE {
reason = "typedef underlying type is not supported"
}
add_alias(ctx, name, value, reason)
case CXCursor_StructDecl:
if len(name) > 0 {
record := add_record(ctx, cursor, name)
value := add_type(ctx, Type{kind=.Record, child=INVALID_TYPE, record=record})
add_alias(ctx, name, value)
}
case CXCursor_UnionDecl:
add_unsupported(ctx, name, "C unions are not supported")
case CXCursor_EnumDecl:
add_unsupported(ctx, name, "C enums are not supported")
case CXCursor_VarDecl:
add_unsupported(ctx, name, "external C variables are not supported")
case CXCursor_MacroDefinition:
add_unsupported(ctx, name, "C macros are not supported")
case:
}
return CXChildVisit_Continue
}
import_with_libclang :: proc(_: rawptr, request: Request, allocator: mem.Allocator) -> Result {
result := init_result(allocator)
api, loaded := load_api()
if !loaded {
result.infrastructure = true
result.error_message = fmt.aprintf(
"could not load libclang; set BROLANG_LIBCLANG_PATH to a compatible library",
allocator=allocator,
)
return result
}
defer _ = dynlib.unload_library(api.library)
index := api.create_index(1, 0)
if index == nil {
result.infrastructure = true
result.error_message = fmt.aprintf("could not create libclang index", allocator=allocator)
return result
}
defer api.dispose_index(index)
arguments: [dynamic]string
arguments.allocator = context.temp_allocator
append(&arguments, "-x", "c", "-target", target.llvm_triple(request.target))
for path in request.include_paths {
append(&arguments, fmt.tprintf("-I%s", path))
}
for define in request.defines {
append(&arguments, fmt.tprintf("-D%s", define))
}
c_arguments := make([]cstring, len(arguments), context.temp_allocator)
for argument, argument_index in arguments {
c_arguments[argument_index] = strings.clone_to_cstring(argument, context.temp_allocator)
}
c_path := strings.clone_to_cstring(request.path, context.temp_allocator)
translation_unit: CXTranslationUnit
error_code := api.parse_translation_unit(
index,
c_path,
raw_data(c_arguments),
i32(len(c_arguments)),
nil,
0,
CXTranslationUnit_DetailedPreprocessingRecord | CXTranslationUnit_SkipFunctionBodies | CXTranslationUnit_KeepGoing,
&translation_unit,
)
if error_code != 0 || translation_unit == nil {
result.error_message = fmt.aprintf("libclang could not parse header '%s'", request.path, allocator=allocator)
return result
}
defer api.dispose_translation_unit(translation_unit)
for diagnostic_index in 0..<api.get_num_diagnostics(translation_unit) {
diagnostic := api.get_diagnostic(translation_unit, diagnostic_index)
severity := api.get_diagnostic_severity(diagnostic)
if severity >= CXDiagnostic_Error && len(result.error_message) == 0 {
result.error_message = clone_cx_string(&api, api.get_diagnostic_spelling(diagnostic), allocator)
}
api.dispose_diagnostic(diagnostic)
}
if len(result.error_message) > 0 {
return result
}
ctx := Context{api=&api, result=&result, allocator=allocator}
root := api.get_translation_unit_cursor(translation_unit)
_ = api.visit_children(root, visit_cursor, &ctx)
result.available = true
return result
}
+5 -1
View File
@@ -1,6 +1,7 @@
package compiler
import "./backend"
import "./cimport"
import "./checker"
import "./llvm"
import "./linker"
@@ -19,6 +20,7 @@ compile_package :: proc(
input_path, output_path: string,
link_arguments: []linker.Argument = nil,
selected := target.DEFAULT,
c_options := cimport.Options{},
) -> int {
sources := source.init_store()
defer source.destroy_store(&sources)
@@ -59,6 +61,8 @@ compile_package :: proc(
&symbols,
vmem.arena_allocator(&lexer_arena),
vmem.arena_allocator(&parser_arena),
c_options,
selected,
)
if !loaded {
source.print_all(&diagnostics)
@@ -83,7 +87,7 @@ compile_package :: proc(
}
source.print_all(&diagnostics)
if !backend.compile(llvm_path, output_path, link_arguments, selected) {
if !backend.compile(llvm_path, output_path, link_arguments, selected, c_options) {
return 2
}
if len(diagnostics.items) > 0 {
+1
View File
@@ -93,6 +93,7 @@ Expr_Kind :: enum u8 {
Unwrap,
Orelse,
Widen,
Weaken_Pointer,
Negate,
Add,
Pointer_Add,
+1
View File
@@ -87,6 +87,7 @@ Opcode :: enum u8 {
Orelse_Begin,
Orelse,
Widen,
Weaken_Pointer,
Neg_Checked,
Add_Checked,
Pointer_Add,
+21 -2
View File
@@ -112,7 +112,7 @@ valid_value :: proc(
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
.Widen, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Trap, .Return, .Return_Void:
@@ -707,6 +707,13 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
case .Weaken_Pointer:
if !valid_instruction(instructions, instruction.a) ||
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer weakening operand")
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a)
case .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand")
@@ -985,7 +992,19 @@ emit_constructor :: proc(emitter: ^Emitter) {
}
emit_functions :: proc(emitter: ^Emitter) {
for function in emitter.module.functions {
for function, function_index in emitter.module.functions {
if function.implementation == .Declaration {
duplicate := false
for previous in emitter.module.functions[:function_index] {
if previous.link_name == function.link_name {
duplicate = true
break
}
}
if duplicate {
continue
}
}
if function.implementation == .Declaration {
strings.write_string(&emitter.builder, "declare ")
} else {
+236 -3
View File
@@ -1,11 +1,14 @@
package loader
import "../ast"
import "../cimport"
import "../lexer"
import "../parser"
import "../source"
import "../symbol"
import "../target"
import "../types"
import "core:fmt"
import "core:mem"
import "core:os"
import "core:path/filepath"
@@ -19,6 +22,10 @@ State :: struct {
symbols: ^symbol.Table,
token_allocator: mem.Allocator,
allocator: mem.Allocator,
c_options: cimport.Options,
selected: target.Target,
record_identities: [dynamic]string,
record_types: [dynamic]types.Type,
root_failed: bool,
}
@@ -104,6 +111,214 @@ resolve_import_path :: proc(state: ^State, importing_path, import_path: string)
return joined, false
}
header_package_name :: proc(path: string, symbols: ^symbol.Table) -> symbol.Id {
base := filepath.base(path)
extension := filepath.ext(base)
if len(extension) > 0 {
base = base[:len(base)-len(extension)]
}
return symbol.intern(symbols, base)
}
find_record_identity :: proc(state: ^State, identity: string) -> types.Type {
for existing, index in state.record_identities {
if existing == identity {
return state.record_types[index]
}
}
return types.INVALID
}
translate_c_type :: proc(
state: ^State,
result: ^cimport.Result,
value: cimport.Type_Id,
pkg: ast.Package_Id,
record_mapping: []types.Type,
type_mapping: []types.Type,
) -> types.Type {
if value == cimport.INVALID_TYPE || int(value) < 0 || int(value) >= len(result.types) {
return types.INVALID
}
if types.is_valid(type_mapping[value]) {
return type_mapping[value]
}
item := result.types[value]
translated := types.INVALID
switch item.kind {
case .Invalid: translated = types.INVALID
case .Void: translated = types.VOID
case .C_Char: translated = types.C_CHAR
case .C_Schar: translated = types.C_SCHAR
case .C_Uchar: translated = types.C_UCHAR
case .C_Short: translated = types.C_SHORT
case .C_Ushort: translated = types.C_USHORT
case .C_Int: translated = types.C_INT
case .C_Uint: translated = types.C_UINT
case .C_Long: translated = types.C_LONG
case .C_Ulong: translated = types.C_ULONG
case .C_Longlong: translated = types.C_LONGLONG
case .C_Ulonglong: translated = types.C_ULONGLONG
case .C_Float: translated = types.C_FLOAT
case .C_Double: translated = types.C_DOUBLE
case .C_Longdouble: translated = types.C_LONGDOUBLE
case .Pointer:
child := translate_c_type(state, result, item.child, pkg, record_mapping, type_mapping)
if types.is_valid(child) {
pointer := types.pointer(&state.module.type_store, child, item.mutable, true)
translated = types.optional(&state.module.type_store, pointer)
}
case .Record:
if int(item.record) < len(record_mapping) {
translated = record_mapping[item.record]
}
}
type_mapping[value] = translated
return translated
}
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type) -> bool {
if left.result != result || len(left.params) != len(params) {
return false
}
for param, index in params {
if left.params[index].type != param.type {
return false
}
}
return true
}
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator)
if !ok {
id := add_placeholder(state, path)
source.addf(state.diagnostics, import_span, "could not resolve C header '%s'", path)
return id
}
if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE {
delete(canonical, state.allocator)
return existing
}
pkg_id := ast.package_id(len(state.module.packages))
append(&state.module.packages, ast.Package{
path=canonical,
name=header_package_name(canonical, state.symbols),
available=false,
kind=.C_Header,
})
result := cimport.import_header(state.c_options, canonical, state.selected, state.allocator)
defer cimport.destroy_result(&result)
if !result.available {
message := result.error_message if len(result.error_message) > 0 else "C header import failed"
source.addf(state.diagnostics, import_span, "could not import C header '%s': %s", path, message)
if result.infrastructure {
state.root_failed = true
}
return pkg_id
}
state.module.packages[pkg_id].available = true
record_mapping := make([]types.Type, len(result.records), state.allocator)
defer delete(record_mapping, state.allocator)
for record, index in result.records {
record_type := find_record_identity(state, record.identity)
if !types.is_valid(record_type) {
name := record.name
if len(name) == 0 {
name = fmt.tprintf("__c_record_%d", len(state.record_types))
}
record_type = types.named(&state.module.type_store, u32(pkg_id), u32(symbol.intern(state.symbols, name)))
_ = types.define_struct(&state.module.type_store, record_type, nil, true, true)
append(&state.record_identities, strings.clone(record.identity, state.allocator))
append(&state.record_types, record_type)
}
record_mapping[index] = record_type
}
type_mapping := make([]types.Type, len(result.types), state.allocator)
defer delete(type_mapping, state.allocator)
for alias in result.aliases {
name := symbol.intern(state.symbols, alias.name)
id := types.named(&state.module.type_store, u32(pkg_id), u32(name))
child := translate_c_type(state, &result, alias.type, pkg_id, record_mapping, type_mapping)
_ = types.define_alias(&state.module.type_store, id, child)
if len(alias.reason) > 0 {
append(&state.module.unsupported, ast.Unsupported{
pkg=pkg_id,
name=name,
reason=strings.clone(alias.reason, state.allocator),
})
}
}
for function in result.functions {
params := make([]ast.Param, len(function.params), state.allocator)
for param_type, index in function.params {
params[index] = ast.Param{
name=symbol.intern(state.symbols, fmt.tprintf("arg%d", index)),
span=import_span,
type=translate_c_type(state, &result, param_type, pkg_id, record_mapping, type_mapping),
}
}
function_result := translate_c_type(state, &result, function.result, pkg_id, record_mapping, type_mapping)
unsupported_reason := function.reason
if len(unsupported_reason) == 0 {
for param in params {
if types.contains_c_struct_by_value(param.type, &state.module.type_store) {
unsupported_reason = "C records passed by value are not supported"
break
}
}
}
if len(unsupported_reason) == 0 &&
types.contains_c_struct_by_value(function_result, &state.module.type_store) {
unsupported_reason = "C records returned by value are not supported"
}
name := symbol.intern(state.symbols, function.name)
duplicate := false
for &existing in state.module.functions {
if existing.pkg != pkg_id || existing.name != name {
continue
}
duplicate = true
if !function_signatures_equal(existing, params, function_result) && len(existing.unsupported_reason) == 0 {
existing.unsupported_reason = fmt.aprintf(
"conflicting C declarations for '%s'",
function.name,
allocator=state.allocator,
)
}
break
}
if duplicate {
delete(params, state.allocator)
continue
}
append(&state.module.functions, ast.Function{
span=import_span,
name=name,
pkg=pkg_id,
file=ast.INVALID_FILE,
c_abi=true,
imported=true,
has_body=false,
params=params,
result=function_result,
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
for item in result.unsupported {
append(&state.module.unsupported, ast.Unsupported{
pkg=pkg_id,
name=symbol.intern(state.symbols, item.name),
reason=strings.clone(item.reason, state.allocator),
})
}
return pkg_id
}
load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id {
canonical, ok := filepath.abs(path, state.allocator)
if !ok || !os.is_dir(path) {
@@ -191,7 +406,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
continue
}
target_path, target_ok := resolve_import_path(state, canonical, import_item.path)
target := load_package(state, target_path, import_item.span)
target := load_header(state, target_path, import_item.span) if filepath.ext(import_item.path) == ".h" else load_package(state, target_path, import_item.span)
state.module.imports[import_id].target = target
if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available {
state.module.imports[import_id].valid = false
@@ -293,14 +508,19 @@ canonical_type :: proc(
import_item := module.imports[import_id]
resolved := types.find_named(&module.type_store, u32(import_item.target), item.name)
if types.is_valid(resolved) {
mapping[index] = resolved
return resolved
mapping[index] = canonical_type(module, resolved, mapping, visiting)
return mapping[index]
}
}
}
mapping[index] = value
return value
}
if item.kind == .Alias {
resolved := canonical_type(module, item.child, mapping, visiting)
mapping[index] = value if !types.is_valid(resolved) else resolved
return mapping[index]
}
if item.kind == .Struct {
mapping[index] = value
fields := types.fields_for(&module.type_store, value)
@@ -347,6 +567,8 @@ load :: proc(
symbols: ^symbol.Table,
token_allocator := context.allocator,
allocator := context.allocator,
c_options := cimport.Options{},
selected := target.DEFAULT,
) -> (ast.Module, bool) {
module := ast.init_module(allocator)
state := State{
@@ -356,6 +578,17 @@ load :: proc(
symbols=symbols,
token_allocator=token_allocator,
allocator=allocator,
c_options=c_options,
selected=selected,
}
state.record_identities.allocator = allocator
state.record_types.allocator = allocator
defer {
for identity in state.record_identities {
delete(identity, allocator)
}
delete(state.record_identities)
delete(state.record_types)
}
root := load_package(&state, root_path, source.Span{}, true)
if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE {
+3 -2
View File
@@ -324,7 +324,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Widen:
case .Widen, .Weaken_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
@@ -358,7 +358,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}
if frame.stage == 1 {
last = append_instruction(state, ir.Instruction{
op=.Widen, span=expr.span, type=expr.type, target=ir.INVALID_REF,
op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else .Widen,
span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
+15 -2
View File
@@ -59,6 +59,7 @@ Kind :: enum u8 {
Slice,
Optional,
Named,
Alias,
Struct,
}
@@ -132,7 +133,7 @@ intern :: proc(store: ^Store, candidate: Node) -> Type {
named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xffff_ffff) -> Type {
normalized_file := file if qualifier != 0 else u32(0)
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Struct) &&
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier &&
existing.file == normalized_file {
return DYNAMIC_START+Type(index)
@@ -143,7 +144,7 @@ named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0, file: u32 = 0xf
find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
for existing, index in store.nodes {
if (existing.kind == .Named || existing.kind == .Struct) &&
if (existing.kind == .Named || existing.kind == .Alias || existing.kind == .Struct) &&
existing.pkg == pkg && existing.name == name && existing.qualifier == qualifier {
return DYNAMIC_START+Type(index)
}
@@ -151,6 +152,18 @@ find_named :: proc(store: ^Store, pkg, name: u32, qualifier: u32 = 0) -> Type {
return INVALID
}
define_alias :: proc(store: ^Store, id, child: Type) -> bool {
existing, ok := node(store, id)
if !ok || existing.kind != .Named || existing.declared {
return false
}
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Alias
store.nodes[index].child = child
store.nodes[index].declared = true
return true
}
define_struct :: proc(store: ^Store, id: Type, fields: []Field, c_layout, opaque: bool) -> bool {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct) || existing.declared {