extern variables and object-like macro consts
This commit is contained in:
@@ -134,6 +134,7 @@ Function :: struct {
|
||||
params: []Param,
|
||||
result: Type_Syntax,
|
||||
body: []Stmt_Id,
|
||||
link_name: string,
|
||||
unsupported_reason: string,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
@@ -141,10 +142,13 @@ Function :: struct {
|
||||
Global :: struct {
|
||||
span: source.Span,
|
||||
name: symbol.Id,
|
||||
link_name: string,
|
||||
pkg: Package_Id,
|
||||
file: File_Id,
|
||||
type: Type_Syntax,
|
||||
immutable: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
expr: Expr_Id,
|
||||
diagnostic: source.Diagnostic_Id,
|
||||
}
|
||||
@@ -184,6 +188,16 @@ Unsupported :: struct {
|
||||
reason: string,
|
||||
}
|
||||
|
||||
// Trampoline is a generated C wrapper source that must be compiled and linked
|
||||
// alongside the program so an internal-linkage (`static inline`) C function can
|
||||
// be called through an external symbol. `source` is the wrapper function only;
|
||||
// `header` is the absolute path it must `#include` (emitted once per header).
|
||||
Trampoline :: struct {
|
||||
symbol: string,
|
||||
source: string,
|
||||
header: string,
|
||||
}
|
||||
|
||||
Module :: struct {
|
||||
exprs: [dynamic]Expr,
|
||||
statements: [dynamic]Stmt,
|
||||
@@ -193,6 +207,7 @@ Module :: struct {
|
||||
files: [dynamic]File,
|
||||
packages: [dynamic]Package,
|
||||
unsupported: [dynamic]Unsupported,
|
||||
c_trampolines: [dynamic]Trampoline,
|
||||
strings: [dynamic]string,
|
||||
type_store: types.Store,
|
||||
allocator: mem.Allocator,
|
||||
@@ -210,6 +225,7 @@ init_module :: proc(allocator := context.allocator) -> Module {
|
||||
module.files.allocator = allocator
|
||||
module.packages.allocator = allocator
|
||||
module.unsupported.allocator = allocator
|
||||
module.c_trampolines.allocator = allocator
|
||||
module.strings.allocator = allocator
|
||||
return module
|
||||
}
|
||||
@@ -221,17 +237,26 @@ destroy_module :: proc(module: ^Module) {
|
||||
for function in module.functions {
|
||||
delete(function.params, module.allocator)
|
||||
delete(function.body, module.allocator)
|
||||
delete(function.link_name, module.allocator)
|
||||
delete(function.unsupported_reason, module.allocator)
|
||||
}
|
||||
for import_item in module.imports {
|
||||
delete(import_item.path, module.allocator)
|
||||
}
|
||||
for global in module.globals {
|
||||
delete(global.link_name, module.allocator)
|
||||
}
|
||||
for pkg in module.packages {
|
||||
delete(pkg.path, module.allocator)
|
||||
}
|
||||
for item in module.unsupported {
|
||||
delete(item.reason, module.allocator)
|
||||
}
|
||||
for trampoline in module.c_trampolines {
|
||||
delete(trampoline.symbol, module.allocator)
|
||||
delete(trampoline.source, module.allocator)
|
||||
delete(trampoline.header, module.allocator)
|
||||
}
|
||||
for value in module.strings {
|
||||
delete(value, module.allocator)
|
||||
}
|
||||
@@ -243,6 +268,7 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(module.files)
|
||||
delete(module.packages)
|
||||
delete(module.unsupported)
|
||||
delete(module.c_trampolines)
|
||||
delete(module.strings)
|
||||
types.destroy_store(&module.type_store)
|
||||
}
|
||||
|
||||
+160
-14
@@ -84,6 +84,8 @@ Checker :: struct {
|
||||
global_index: []Global_Index_Entry,
|
||||
import_index: []Import_Index_Entry,
|
||||
global_types: []types.Type,
|
||||
external_global_canonical: []ast.Global_Id,
|
||||
external_global_diagnostics: []source.Diagnostic_Id,
|
||||
constants: []Constant,
|
||||
template_diagnostics: []source.Diagnostic_Id,
|
||||
constant_stack: [dynamic]Constant_Frame,
|
||||
@@ -600,6 +602,72 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
||||
}
|
||||
}
|
||||
|
||||
validate_external_globals :: proc(checker: ^Checker) {
|
||||
for global, global_index in checker.ast_module.globals {
|
||||
checker.external_global_canonical[global_index] = ast.global_id(global_index)
|
||||
if !global.external {
|
||||
continue
|
||||
}
|
||||
switch global.link_name {
|
||||
case "main":
|
||||
checker.external_global_diagnostics[global_index] = source.add(
|
||||
checker.diagnostics,
|
||||
global.span,
|
||||
"external C variable 'main' conflicts with the program entry point",
|
||||
)
|
||||
case "write":
|
||||
checker.external_global_diagnostics[global_index] = source.add(
|
||||
checker.diagnostics,
|
||||
global.span,
|
||||
"external C variable 'write' conflicts with the compiler runtime",
|
||||
)
|
||||
}
|
||||
for previous, previous_index in checker.ast_module.globals[:global_index] {
|
||||
if !previous.external || previous.link_name != global.link_name {
|
||||
continue
|
||||
}
|
||||
canonical := checker.external_global_canonical[previous_index]
|
||||
if canonical == ast.INVALID_GLOBAL {
|
||||
canonical = ast.global_id(previous_index)
|
||||
}
|
||||
canonical_index := int(canonical)
|
||||
if canonical_index < 0 || canonical_index >= len(checker.ast_module.globals) {
|
||||
canonical = ast.global_id(previous_index)
|
||||
canonical_index = previous_index
|
||||
}
|
||||
checker.external_global_canonical[global_index] = canonical
|
||||
canonical_global := checker.ast_module.globals[canonical_index]
|
||||
canonical_type := checker.global_types[canonical_index]
|
||||
if !types.equal(checker.global_types[global_index], canonical_type) ||
|
||||
global.writable != canonical_global.writable {
|
||||
checker.external_global_diagnostics[global_index] = source.addf(
|
||||
checker.diagnostics,
|
||||
global.span,
|
||||
"conflicting external C variable declarations for '%s'",
|
||||
global.link_name,
|
||||
)
|
||||
}
|
||||
checker.global_types[global_index] = canonical_type
|
||||
break
|
||||
}
|
||||
for function in checker.ast_module.functions {
|
||||
if !function.c_abi || function.has_body || len(function.unsupported_reason) > 0 ||
|
||||
symbol_text(checker, function.name) != global.link_name {
|
||||
continue
|
||||
}
|
||||
if checker.external_global_diagnostics[global_index] == source.INVALID_DIAGNOSTIC {
|
||||
checker.external_global_diagnostics[global_index] = source.addf(
|
||||
checker.diagnostics,
|
||||
global.span,
|
||||
"external C variable '%s' conflicts with a C function declaration",
|
||||
global.link_name,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validate_declarations :: proc(checker: ^Checker) {
|
||||
for function, function_id in checker.ast_module.functions {
|
||||
if len(function.unsupported_reason) > 0 {
|
||||
@@ -1362,6 +1430,9 @@ infer_all :: proc(checker: ^Checker) {
|
||||
changed := false
|
||||
spec_count := len(checker.specs)
|
||||
for global, index in checker.ast_module.globals {
|
||||
if global.external {
|
||||
continue
|
||||
}
|
||||
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
|
||||
if is_runtime_type(checker, type_from_syntax(global.type)) {
|
||||
continue
|
||||
@@ -1392,6 +1463,9 @@ prune_specs :: proc(checker: ^Checker) {
|
||||
mark_spec_demanded(checker, find_spec(checker, main_template, nil), &stack)
|
||||
}
|
||||
for global in checker.ast_module.globals {
|
||||
if global.external {
|
||||
continue
|
||||
}
|
||||
_ = infer_expr(checker, global.expr, nil, global.pkg, global.file, &stack)
|
||||
}
|
||||
for len(stack) > 0 {
|
||||
@@ -1449,6 +1523,27 @@ add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id)
|
||||
append(values, value)
|
||||
}
|
||||
|
||||
build_global_reference :: proc(
|
||||
checker: ^Checker,
|
||||
global: ast.Global_Id,
|
||||
span: source.Span,
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
) -> hir.Expr_Id {
|
||||
if int(global) < len(checker.external_global_diagnostics) {
|
||||
diagnostic := checker.external_global_diagnostics[global]
|
||||
if diagnostic != source.INVALID_DIAGNOSTIC {
|
||||
return invalid_hir_expr(checker, span, diagnostic, checker.global_types[global])
|
||||
}
|
||||
}
|
||||
hir_global := hir.Global_Id(global)
|
||||
add_unique_global(global_reads, hir_global)
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=.Global, span=span, type=checker.global_types[global],
|
||||
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) {
|
||||
for existing in values {
|
||||
if existing == value {
|
||||
@@ -1689,13 +1784,23 @@ hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: [
|
||||
return local.mutable
|
||||
}
|
||||
}
|
||||
case .Global:
|
||||
id := hir.as_global(expr.target)
|
||||
return id != hir.INVALID_GLOBAL && int(id) < len(checker.module.globals) &&
|
||||
checker.module.globals[id].writable
|
||||
case .Deref:
|
||||
pointer_type := checker.module.exprs[expr.left].type
|
||||
return types.is_mutable(pointer_type, &checker.module.types)
|
||||
case .Index:
|
||||
container_type := checker.module.exprs[expr.left].type
|
||||
item, ok := types.container(container_type, &checker.module.types)
|
||||
return ok && item.mutable
|
||||
if !ok || !item.mutable {
|
||||
return false
|
||||
}
|
||||
if types.is_array(container_type, &checker.module.types) {
|
||||
return hir_location_writable(checker, expr.left, locals)
|
||||
}
|
||||
return true
|
||||
case .Field:
|
||||
base_type := checker.module.exprs[expr.left].type
|
||||
if types.is_pointer(base_type, &checker.module.types) {
|
||||
@@ -2188,12 +2293,7 @@ build_expr :: proc(
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
||||
hir_global := hir.Global_Id(global)
|
||||
add_unique_global(global_reads, hir_global)
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Global, span=expr.span, type=checker.global_types[global],
|
||||
target=hir.global_ref(hir_global), left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
last = build_global_reference(checker, global, expr.span, global_reads)
|
||||
} else {
|
||||
template := find_template(checker, expr.name, target_pkg)
|
||||
if template != ast.INVALID_FUNCTION && checker.ast_module.functions[template].c_abi {
|
||||
@@ -2241,13 +2341,7 @@ build_expr :: proc(
|
||||
}
|
||||
if callee == hir.INVALID_EXPR {
|
||||
if global := find_global(checker, expr.name, target_pkg); global != ast.INVALID_GLOBAL {
|
||||
hir_global := hir.Global_Id(global)
|
||||
add_unique_global(global_reads, hir_global)
|
||||
callee = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Global, span=expr.span, type=checker.global_types[global],
|
||||
target=hir.global_ref(hir_global), left=hir.INVALID_EXPR,
|
||||
right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
callee = build_global_reference(checker, global, expr.span, global_reads)
|
||||
callee_from_global = true
|
||||
}
|
||||
}
|
||||
@@ -2561,6 +2655,9 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
||||
return fmt.aprintf("main", allocator = checker.allocator)
|
||||
}
|
||||
if !function.has_body && function.c_abi {
|
||||
if len(function.link_name) > 0 {
|
||||
return strings.clone(function.link_name, checker.allocator)
|
||||
}
|
||||
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
|
||||
}
|
||||
builder := strings.builder_make(checker.allocator)
|
||||
@@ -3085,6 +3182,41 @@ expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
|
||||
|
||||
build_globals :: proc(checker: ^Checker) {
|
||||
for global, global_index in checker.ast_module.globals {
|
||||
if global.external {
|
||||
global_type := checker.global_types[global_index]
|
||||
writable := global.writable
|
||||
canonical := checker.external_global_canonical[global_index]
|
||||
if canonical != ast.INVALID_GLOBAL && int(canonical) < len(checker.ast_module.globals) {
|
||||
canonical_global := checker.ast_module.globals[canonical]
|
||||
writable = canonical_global.writable
|
||||
global_type = checker.global_types[canonical]
|
||||
}
|
||||
diagnostic := checker.external_global_diagnostics[global_index]
|
||||
if !is_runtime_type(checker, global_type) {
|
||||
if diagnostic == source.INVALID_DIAGNOSTIC {
|
||||
diagnostic = source.addf(
|
||||
checker.diagnostics,
|
||||
global.span,
|
||||
"could not resolve a concrete type for external global '%s'",
|
||||
symbol_text(checker, global.name),
|
||||
)
|
||||
}
|
||||
global_type = types.I64
|
||||
}
|
||||
_ = hir.global_id(len(checker.module.globals))
|
||||
append(&checker.module.globals, hir.Global{
|
||||
name=global.name,
|
||||
link_name=strings.clone(global.link_name, checker.allocator),
|
||||
type=global_type,
|
||||
expr=hir.INVALID_EXPR,
|
||||
external=true,
|
||||
writable=writable,
|
||||
direct_problem=diagnostic != source.INVALID_DIAGNOSTIC,
|
||||
problematic=diagnostic != source.INVALID_DIAGNOSTIC,
|
||||
diagnostic=diagnostic,
|
||||
})
|
||||
continue
|
||||
}
|
||||
dependencies: [dynamic]hir.Global_Id
|
||||
dependencies.allocator = checker.allocator
|
||||
calls: [dynamic]hir.Function_Id
|
||||
@@ -3139,10 +3271,13 @@ build_globals :: proc(checker: ^Checker) {
|
||||
&checker.module.globals,
|
||||
hir.Global {
|
||||
name = global.name,
|
||||
link_name = strings.clone(global.link_name, checker.allocator),
|
||||
type = global_type,
|
||||
expr = expr,
|
||||
static_value = static_value,
|
||||
is_static = is_static,
|
||||
external = false,
|
||||
writable = false,
|
||||
dependencies = dependencies,
|
||||
calls = calls[:],
|
||||
direct_problem = expr_problematic(checker, expr),
|
||||
@@ -3386,6 +3521,14 @@ check :: proc(
|
||||
checker.cycle_stack.allocator = allocator
|
||||
build_symbol_indexes(&checker)
|
||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||
checker.external_global_canonical = make([]ast.Global_Id, len(ast_module.globals), allocator)
|
||||
checker.external_global_diagnostics = make([]source.Diagnostic_Id, len(ast_module.globals), allocator)
|
||||
for &canonical in checker.external_global_canonical {
|
||||
canonical = ast.INVALID_GLOBAL
|
||||
}
|
||||
for &diagnostic in checker.external_global_diagnostics {
|
||||
diagnostic = source.INVALID_DIAGNOSTIC
|
||||
}
|
||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||
checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator)
|
||||
for &diagnostic in checker.template_diagnostics {
|
||||
@@ -3400,6 +3543,8 @@ check :: proc(
|
||||
delete(checker.global_index, allocator)
|
||||
delete(checker.import_index, allocator)
|
||||
delete(checker.global_types, allocator)
|
||||
delete(checker.external_global_canonical, allocator)
|
||||
delete(checker.external_global_diagnostics, allocator)
|
||||
delete(checker.constants, allocator)
|
||||
delete(checker.template_diagnostics, allocator)
|
||||
delete(checker.constant_stack)
|
||||
@@ -3433,6 +3578,7 @@ check :: proc(
|
||||
validate_type_nodes(&checker)
|
||||
validate_declarations(&checker)
|
||||
infer_all(&checker)
|
||||
validate_external_globals(&checker)
|
||||
prune_specs(&checker)
|
||||
build_globals(&checker)
|
||||
for index := 0; index < len(checker.specs); index += 1 {
|
||||
|
||||
@@ -68,16 +68,59 @@ Alias :: struct {
|
||||
}
|
||||
|
||||
Function :: struct {
|
||||
name: string,
|
||||
params: []Type_Id,
|
||||
result: Type_Id,
|
||||
variadic: bool,
|
||||
reason: string,
|
||||
name: string,
|
||||
params: []Type_Id,
|
||||
result: Type_Id,
|
||||
variadic: bool,
|
||||
link_name: string,
|
||||
reason: string,
|
||||
}
|
||||
|
||||
// Trampoline is a generated C wrapper that gives an external symbol to a
|
||||
// `static inline` (or otherwise internal-linkage) C function so brolang can
|
||||
// call it. The wrapper is compiled and linked alongside the program. `source`
|
||||
// is the wrapper function only; `header` is the absolute path the wrapper must
|
||||
// `#include`, emitted once per unique header by the driver.
|
||||
Trampoline :: struct {
|
||||
symbol: string,
|
||||
source: string,
|
||||
header: string,
|
||||
}
|
||||
|
||||
Variable :: struct {
|
||||
name: string,
|
||||
type: Type_Id,
|
||||
mutable: bool,
|
||||
reason: string,
|
||||
}
|
||||
|
||||
Macro_Value_Kind :: enum u8 {
|
||||
Invalid,
|
||||
Integer,
|
||||
Float,
|
||||
}
|
||||
|
||||
Macro_Value :: struct {
|
||||
kind: Macro_Value_Kind,
|
||||
type: Type_Id,
|
||||
integer: u64,
|
||||
negative: bool,
|
||||
}
|
||||
|
||||
Macro_Constant :: struct {
|
||||
name: string,
|
||||
type: Type_Id,
|
||||
type_name: string,
|
||||
value: Macro_Value,
|
||||
values: []Macro_Value,
|
||||
aggregate: bool,
|
||||
reason: string,
|
||||
}
|
||||
|
||||
Unsupported :: struct {
|
||||
name: string,
|
||||
reason: string,
|
||||
name: string,
|
||||
reason: string,
|
||||
final_macro: bool,
|
||||
}
|
||||
|
||||
Result :: struct {
|
||||
@@ -85,7 +128,10 @@ Result :: struct {
|
||||
records: [dynamic]Record,
|
||||
aliases: [dynamic]Alias,
|
||||
functions: [dynamic]Function,
|
||||
variables: [dynamic]Variable,
|
||||
macros: [dynamic]Macro_Constant,
|
||||
unsupported: [dynamic]Unsupported,
|
||||
trampolines: [dynamic]Trampoline,
|
||||
error_message: string,
|
||||
infrastructure: bool,
|
||||
available: bool,
|
||||
@@ -99,7 +145,10 @@ init_result :: proc(allocator := context.allocator) -> Result {
|
||||
result.records.allocator = allocator
|
||||
result.aliases.allocator = allocator
|
||||
result.functions.allocator = allocator
|
||||
result.variables.allocator = allocator
|
||||
result.macros.allocator = allocator
|
||||
result.unsupported.allocator = allocator
|
||||
result.trampolines.allocator = allocator
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -123,18 +172,37 @@ destroy_result :: proc(result: ^Result) {
|
||||
for function in result.functions {
|
||||
delete(function.name, result.allocator)
|
||||
delete(function.params, result.allocator)
|
||||
delete(function.link_name, result.allocator)
|
||||
delete(function.reason, result.allocator)
|
||||
}
|
||||
for variable in result.variables {
|
||||
delete(variable.name, result.allocator)
|
||||
delete(variable.reason, result.allocator)
|
||||
}
|
||||
for macro in result.macros {
|
||||
delete(macro.name, result.allocator)
|
||||
delete(macro.type_name, result.allocator)
|
||||
delete(macro.values, result.allocator)
|
||||
delete(macro.reason, result.allocator)
|
||||
}
|
||||
for item in result.unsupported {
|
||||
delete(item.name, result.allocator)
|
||||
delete(item.reason, result.allocator)
|
||||
}
|
||||
for trampoline in result.trampolines {
|
||||
delete(trampoline.symbol, result.allocator)
|
||||
delete(trampoline.source, result.allocator)
|
||||
delete(trampoline.header, result.allocator)
|
||||
}
|
||||
delete(result.error_message, result.allocator)
|
||||
delete(result.types)
|
||||
delete(result.records)
|
||||
delete(result.aliases)
|
||||
delete(result.functions)
|
||||
delete(result.variables)
|
||||
delete(result.macros)
|
||||
delete(result.unsupported)
|
||||
delete(result.trampolines)
|
||||
}
|
||||
|
||||
Request :: struct {
|
||||
|
||||
+908
-24
File diff suppressed because it is too large
Load Diff
+61
-1
@@ -15,6 +15,18 @@ import "core:fmt"
|
||||
import vmem "core:mem/virtual"
|
||||
import "core:os"
|
||||
import "core:os/os2"
|
||||
import "core:strings"
|
||||
|
||||
// write_escaped_c_string writes value into builder with `\` and `"` escaped so
|
||||
// it is safe to embed inside a C string literal (e.g. an `#include "..."`).
|
||||
write_escaped_c_string :: proc(builder: ^strings.Builder, value: string) {
|
||||
for b in transmute([]byte)value {
|
||||
if b == '\\' || b == '"' {
|
||||
strings.write_byte(builder, '\\')
|
||||
}
|
||||
strings.write_byte(builder, b)
|
||||
}
|
||||
}
|
||||
|
||||
compile_package :: proc(
|
||||
input_path, output_path: string,
|
||||
@@ -69,6 +81,54 @@ compile_package :: proc(
|
||||
fmt.eprintln("failed to load root package directory:", input_path)
|
||||
return 2
|
||||
}
|
||||
|
||||
// Generated C trampolines (for `static inline` imports) must be compiled and
|
||||
// linked with the program. Write them out and add the source as a link input
|
||||
// before the loader's arena (which owns the trampoline strings) is freed.
|
||||
trampoline_path := ""
|
||||
effective_link_arguments := link_arguments
|
||||
owns_arguments := false
|
||||
// Register the path defer first so it runs last (LIFO): the augmented
|
||||
// argument slice, whose Input value aliases trampoline_path, is freed before
|
||||
// the path string it points at.
|
||||
defer if len(trampoline_path) > 0 {
|
||||
_ = os.remove(trampoline_path)
|
||||
delete(trampoline_path)
|
||||
}
|
||||
defer if owns_arguments {
|
||||
delete(effective_link_arguments)
|
||||
}
|
||||
if len(ast_module.c_trampolines) > 0 {
|
||||
builder := strings.builder_make()
|
||||
defer strings.builder_destroy(&builder)
|
||||
seen_headers: map[string]bool
|
||||
defer delete(seen_headers)
|
||||
for trampoline in ast_module.c_trampolines {
|
||||
if seen_headers[trampoline.header] {
|
||||
continue
|
||||
}
|
||||
seen_headers[trampoline.header] = true
|
||||
strings.write_string(&builder, "#include \"")
|
||||
write_escaped_c_string(&builder, trampoline.header)
|
||||
strings.write_string(&builder, "\"\n")
|
||||
}
|
||||
for trampoline in ast_module.c_trampolines {
|
||||
strings.write_string(&builder, trampoline.source)
|
||||
}
|
||||
// Owned (stable allocator); freed in the path defer above after backend
|
||||
// compilation, which is the only consumer, has run.
|
||||
trampoline_path = fmt.aprintf("%s.brolang-trampolines-%d.c", output_path, os2.get_pid())
|
||||
if err := os.write_entire_file_or_err(trampoline_path, transmute([]byte)strings.to_string(builder)); err != nil {
|
||||
fmt.eprintln("failed to write C trampoline source:", err)
|
||||
return 2
|
||||
}
|
||||
augmented := make([]linker.Argument, len(link_arguments)+1)
|
||||
copy(augmented, link_arguments)
|
||||
augmented[len(link_arguments)] = linker.Argument{kind=.Input, value=trampoline_path}
|
||||
effective_link_arguments = augmented
|
||||
owns_arguments = true
|
||||
}
|
||||
|
||||
vmem.arena_free_all(&lexer_arena)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols, selected, vmem.arena_allocator(&checker_arena))
|
||||
vmem.arena_free_all(&parser_arena)
|
||||
@@ -87,7 +147,7 @@ compile_package :: proc(
|
||||
}
|
||||
|
||||
source.print_all(&diagnostics)
|
||||
if !backend.compile(llvm_path, output_path, link_arguments, selected, c_options) {
|
||||
if !backend.compile(llvm_path, output_path, effective_link_arguments, selected, c_options) {
|
||||
return 2
|
||||
}
|
||||
if len(diagnostics.items) > 0 {
|
||||
|
||||
@@ -161,10 +161,13 @@ Function :: struct {
|
||||
|
||||
Global :: struct {
|
||||
name: symbol.Id,
|
||||
link_name: string,
|
||||
type: types.Type,
|
||||
expr: Expr_Id,
|
||||
static_value: i64,
|
||||
is_static: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
dependencies: [dynamic]Global_Id,
|
||||
calls: []Function_Id,
|
||||
direct_problem: bool,
|
||||
@@ -210,6 +213,7 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(function.calls, module.allocator)
|
||||
}
|
||||
for global in module.globals {
|
||||
delete(global.link_name, module.allocator)
|
||||
delete(global.dependencies)
|
||||
delete(global.calls, module.allocator)
|
||||
}
|
||||
|
||||
@@ -128,8 +128,11 @@ Function :: struct {
|
||||
|
||||
Global :: struct {
|
||||
name: symbol.Id,
|
||||
link_name: string,
|
||||
type: types.Type,
|
||||
is_static: bool,
|
||||
external: bool,
|
||||
writable: bool,
|
||||
static_value: i64,
|
||||
initializer: []Instruction,
|
||||
problematic: bool,
|
||||
@@ -171,6 +174,7 @@ destroy_module :: proc(module: ^Module) {
|
||||
destroy_instructions(function.instructions, module.allocator)
|
||||
}
|
||||
for global in module.globals {
|
||||
delete(global.link_name, module.allocator)
|
||||
destroy_instructions(global.initializer, module.allocator)
|
||||
}
|
||||
for value in module.strings {
|
||||
|
||||
+43
-5
@@ -600,7 +600,15 @@ emit_instruction_stream :: proc(
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference type")
|
||||
continue
|
||||
}
|
||||
if global.is_static {
|
||||
if global.external {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = load %s, ptr @%s\n",
|
||||
instruction_index,
|
||||
llvm_type(global.type, &emitter.module.types),
|
||||
global.link_name,
|
||||
)
|
||||
} else if global.is_static {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = load %s, ptr @bro.g.%d\n",
|
||||
@@ -632,6 +640,15 @@ emit_instruction_stream :: proc(
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid global address")
|
||||
continue
|
||||
}
|
||||
global := emitter.module.globals[global_id]
|
||||
if global.external {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr @%s, i64 0\n",
|
||||
instruction_index, llvm_type(instruction.type, &emitter.module.types), global.link_name,
|
||||
)
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
" %%v%d = getelementptr %s, ptr @bro.g.%d, i64 0\n",
|
||||
@@ -1369,7 +1386,28 @@ emit_instruction_stream :: proc(
|
||||
|
||||
emit_globals :: proc(emitter: ^Emitter) {
|
||||
for global, global_id in emitter.module.globals {
|
||||
if global.is_static {
|
||||
if global.external {
|
||||
if global.problematic {
|
||||
continue
|
||||
}
|
||||
duplicate := false
|
||||
for previous in emitter.module.globals[:global_id] {
|
||||
if previous.external && !previous.problematic && previous.link_name == global.link_name {
|
||||
duplicate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if duplicate {
|
||||
continue
|
||||
}
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
"@%s = external %s %s\n",
|
||||
global.link_name,
|
||||
"global" if global.writable else "constant",
|
||||
llvm_type(global.type, &emitter.module.types),
|
||||
)
|
||||
} else if global.is_static {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
"@bro.g.%d = internal constant %s ",
|
||||
@@ -1462,7 +1500,7 @@ emit_strings :: proc(emitter: ^Emitter) {
|
||||
emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||
placeholder_function := ir.Function{result=types.I64}
|
||||
for global, global_id in emitter.module.globals {
|
||||
if global.is_static {
|
||||
if global.is_static || global.external {
|
||||
continue
|
||||
}
|
||||
type_name := llvm_type(global.type, &emitter.module.types)
|
||||
@@ -1500,7 +1538,7 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||
emit_constructor :: proc(emitter: ^Emitter) {
|
||||
count := 0
|
||||
for global in emitter.module.globals {
|
||||
if !global.is_static && !global.problematic {
|
||||
if !global.is_static && !global.external && !global.problematic {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
@@ -1513,7 +1551,7 @@ emit_constructor :: proc(emitter: ^Emitter) {
|
||||
)
|
||||
strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n")
|
||||
for global, global_id in emitter.module.globals {
|
||||
if !global.is_static && !global.problematic {
|
||||
if !global.is_static && !global.external && !global.problematic {
|
||||
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id)
|
||||
}
|
||||
}
|
||||
|
||||
+645
-1
@@ -9,6 +9,7 @@ import "../symbol"
|
||||
import "../target"
|
||||
import "../types"
|
||||
import "core:fmt"
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
import "core:os"
|
||||
import "core:path/filepath"
|
||||
@@ -296,6 +297,625 @@ c_record_by_value_reason :: proc(result: ^cimport.Result, value: cimport.Type_Id
|
||||
return ""
|
||||
}
|
||||
|
||||
add_import_unsupported :: proc(state: ^State, pkg: ast.Package_Id, name: string, reason: string) {
|
||||
if len(name) == 0 || len(reason) == 0 {
|
||||
return
|
||||
}
|
||||
append(&state.module.unsupported, ast.Unsupported{
|
||||
pkg=pkg,
|
||||
name=symbol.intern(state.symbols, name),
|
||||
reason=strings.clone(reason, state.allocator),
|
||||
})
|
||||
}
|
||||
|
||||
find_trampoline :: proc(result: ^cimport.Result, symbol: string) -> (cimport.Trampoline, bool) {
|
||||
for trampoline in result.trampolines {
|
||||
if trampoline.symbol == symbol {
|
||||
return trampoline, true
|
||||
}
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
|
||||
add_c_trampoline :: proc(state: ^State, trampoline: cimport.Trampoline) {
|
||||
if len(trampoline.symbol) == 0 || len(trampoline.source) == 0 {
|
||||
return
|
||||
}
|
||||
for existing in state.module.c_trampolines {
|
||||
if existing.symbol == trampoline.symbol {
|
||||
return
|
||||
}
|
||||
}
|
||||
append(&state.module.c_trampolines, ast.Trampoline{
|
||||
symbol=strings.clone(trampoline.symbol, state.allocator),
|
||||
source=strings.clone(trampoline.source, state.allocator),
|
||||
header=strings.clone(trampoline.header, state.allocator),
|
||||
})
|
||||
}
|
||||
|
||||
add_import_expr :: proc(state: ^State, expr: ast.Expr) -> ast.Expr_Id {
|
||||
id := ast.expr_id(len(state.module.exprs))
|
||||
append(&state.module.exprs, expr)
|
||||
return id
|
||||
}
|
||||
|
||||
add_macro_value_expr :: proc(state: ^State, value: cimport.Macro_Value, span: source.Span) -> ast.Expr_Id {
|
||||
#partial switch value.kind {
|
||||
case .Integer:
|
||||
if value.negative {
|
||||
magnitude := value.integer
|
||||
operand := add_import_expr(state, ast.Expr{
|
||||
kind=.Integer, span=span, integer=magnitude,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Negate, span=span, left=operand, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Integer, span=span, integer=value.integer,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case .Float:
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Float, span=span, integer=value.integer,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
case:
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Invalid, span=span, left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.add(state.diagnostics, span, "unsupported C macro value"),
|
||||
})
|
||||
}
|
||||
|
||||
MAX_MACRO_ZERO_DEPTH :: 64
|
||||
MAX_MACRO_ZERO_NODES :: 65_536
|
||||
|
||||
find_macro_record_name :: proc(
|
||||
state: ^State,
|
||||
pkg: ast.Package_Id,
|
||||
value: types.Type,
|
||||
) -> (symbol.Id, bool) {
|
||||
resolved := types.resolve_alias(value, &state.module.type_store)
|
||||
for item, index in state.module.type_store.nodes {
|
||||
if item.pkg != u32(pkg) || item.name == 0 {
|
||||
continue
|
||||
}
|
||||
candidate := types.DYNAMIC_START+types.Type(index)
|
||||
if types.resolve_alias(candidate, &state.module.type_store) == resolved {
|
||||
return symbol.Id(item.name), true
|
||||
}
|
||||
}
|
||||
return symbol.INVALID, false
|
||||
}
|
||||
|
||||
add_macro_zero_expr :: proc(
|
||||
state: ^State,
|
||||
pkg: ast.Package_Id,
|
||||
value_type: types.Type,
|
||||
span: source.Span,
|
||||
depth: int,
|
||||
remaining: ^int,
|
||||
) -> (ast.Expr_Id, bool) {
|
||||
if depth > MAX_MACRO_ZERO_DEPTH || remaining^ <= 0 {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
remaining^ -= 1
|
||||
store := &state.module.type_store
|
||||
resolved := types.resolve_alias(value_type, store)
|
||||
#partial switch types.kind(resolved, store) {
|
||||
case .Scalar:
|
||||
kind := ast.Expr_Kind.Integer
|
||||
if types.is_float(resolved, state.selected) {
|
||||
kind = .Float
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=kind, span=span, integer=0,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
case .Optional:
|
||||
if !types.is_optional_pointer(resolved, store) {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.None, span=span,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
case .Array:
|
||||
item, ok := types.node(store, resolved)
|
||||
if !ok || item.count > u64(max(int)) || item.count > u64(remaining^) {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
args := make([]ast.Expr_Id, int(item.count), state.allocator)
|
||||
for index in 0..<len(args) {
|
||||
value, value_ok := add_macro_zero_expr(
|
||||
state, pkg, item.child, span, depth+1, remaining,
|
||||
)
|
||||
if !value_ok {
|
||||
delete(args, state.allocator)
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
args[index] = value
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Array, span=span, args=args,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
case .Struct, .Union:
|
||||
item, ok := types.node(store, resolved)
|
||||
fields := types.fields_for(store, resolved)
|
||||
if !ok || len(fields) == 0 {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
name, found_name := find_macro_record_name(state, pkg, resolved)
|
||||
if !found_name {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
count := 1 if item.kind == .Union else len(fields)
|
||||
args := make([]ast.Expr_Id, count, state.allocator)
|
||||
for index in 0..<count {
|
||||
field := fields[index]
|
||||
value, value_ok := add_macro_zero_expr(
|
||||
state, pkg, field.type, span, depth+1, remaining,
|
||||
)
|
||||
if !value_ok {
|
||||
delete(args, state.allocator)
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
args[index] = add_import_expr(state, ast.Expr{
|
||||
kind=.Keyed, span=span, name=symbol.Id(field.name), left=value,
|
||||
right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Struct_Literal, span=span, name=name, args=args,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
case:
|
||||
}
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
|
||||
Converted_Macro_Value_Kind :: enum u8 {
|
||||
Invalid,
|
||||
Integer,
|
||||
Float,
|
||||
Null,
|
||||
}
|
||||
|
||||
Converted_Macro_Value :: struct {
|
||||
kind: Converted_Macro_Value_Kind,
|
||||
integer: u64,
|
||||
negative: bool,
|
||||
}
|
||||
|
||||
macro_integer_value :: proc(value: cimport.Macro_Value) -> i128 {
|
||||
magnitude := i128(value.integer)
|
||||
return -magnitude if value.negative else magnitude
|
||||
}
|
||||
|
||||
convert_macro_integer :: proc(
|
||||
value: i128,
|
||||
destination: types.Type,
|
||||
selected: target.Target,
|
||||
) -> (Converted_Macro_Value, bool) {
|
||||
bits := types.bits(destination, selected)
|
||||
if bits <= 0 || bits > 64 {
|
||||
return {}, false
|
||||
}
|
||||
if types.is_unsigned(destination, selected) {
|
||||
modulus := i128(1) << u32(bits)
|
||||
wrapped := value % modulus
|
||||
if wrapped < 0 {
|
||||
wrapped += modulus
|
||||
}
|
||||
return Converted_Macro_Value{
|
||||
kind=.Integer,
|
||||
integer=u64(wrapped),
|
||||
}, true
|
||||
}
|
||||
if !types.is_signed(destination, selected) {
|
||||
return {}, false
|
||||
}
|
||||
limit := i128(1) << u32(bits-1)
|
||||
if value < -limit || value >= limit {
|
||||
return {}, false
|
||||
}
|
||||
if value < 0 {
|
||||
return Converted_Macro_Value{
|
||||
kind=.Integer,
|
||||
integer=u64(-value),
|
||||
negative=true,
|
||||
}, true
|
||||
}
|
||||
return Converted_Macro_Value{kind=.Integer, integer=u64(value)}, true
|
||||
}
|
||||
|
||||
macro_float_value :: proc(
|
||||
value: cimport.Macro_Value,
|
||||
source_type: types.Type,
|
||||
selected: target.Target,
|
||||
) -> (f64, bool) {
|
||||
if value.kind != .Float || !types.is_float(source_type, selected) {
|
||||
return 0, false
|
||||
}
|
||||
number := transmute(f64)value.integer
|
||||
if types.bits(source_type, selected) == 32 {
|
||||
number = f64(f32(number))
|
||||
}
|
||||
if math.is_nan(number) || math.is_inf(number) {
|
||||
return 0, false
|
||||
}
|
||||
return number, true
|
||||
}
|
||||
|
||||
convert_macro_float :: proc(
|
||||
number: f64,
|
||||
destination: types.Type,
|
||||
selected: target.Target,
|
||||
) -> (Converted_Macro_Value, bool) {
|
||||
if !types.is_float(destination, selected) {
|
||||
return {}, false
|
||||
}
|
||||
converted := number
|
||||
if types.bits(destination, selected) == 32 {
|
||||
rounded := f64(f32(number))
|
||||
if math.is_inf(rounded) {
|
||||
return {}, false
|
||||
}
|
||||
converted = rounded
|
||||
}
|
||||
return Converted_Macro_Value{
|
||||
kind=.Float,
|
||||
integer=transmute(u64)converted,
|
||||
}, true
|
||||
}
|
||||
|
||||
convert_macro_field_value :: proc(
|
||||
state: ^State,
|
||||
result: ^cimport.Result,
|
||||
value: cimport.Macro_Value,
|
||||
field_type: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
record_mapping: []types.Type,
|
||||
type_mapping: []types.Type,
|
||||
) -> (Converted_Macro_Value, bool) {
|
||||
store := &state.module.type_store
|
||||
destination := types.resolve_alias(field_type, store)
|
||||
source_type := translate_c_type(
|
||||
state, result, value.type, pkg, record_mapping, type_mapping,
|
||||
)
|
||||
if !types.is_concrete_scalar(source_type) {
|
||||
return {}, false
|
||||
}
|
||||
if types.is_optional_pointer(destination, store) {
|
||||
if types.is_concrete_integer(source_type) &&
|
||||
value.kind == .Integer && macro_integer_value(value) == 0 {
|
||||
return Converted_Macro_Value{kind=.Null}, true
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
if types.is_concrete_integer(source_type) {
|
||||
if value.kind != .Integer {
|
||||
return {}, false
|
||||
}
|
||||
integer := macro_integer_value(value)
|
||||
if types.is_concrete_integer(destination) {
|
||||
return convert_macro_integer(integer, destination, state.selected)
|
||||
}
|
||||
if types.is_float(destination, state.selected) {
|
||||
return convert_macro_float(f64(integer), destination, state.selected)
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
number, number_ok := macro_float_value(value, source_type, state.selected)
|
||||
if !number_ok {
|
||||
return {}, false
|
||||
}
|
||||
if types.is_float(destination, state.selected) {
|
||||
return convert_macro_float(number, destination, state.selected)
|
||||
}
|
||||
if types.is_concrete_integer(destination) {
|
||||
truncated := math.trunc(number)
|
||||
bits := types.bits(destination, state.selected)
|
||||
if bits <= 0 || bits > 64 {
|
||||
return {}, false
|
||||
}
|
||||
if types.is_signed(destination, state.selected) {
|
||||
limit := f64(i128(1) << u32(bits-1))
|
||||
if truncated < -limit || truncated >= limit {
|
||||
return {}, false
|
||||
}
|
||||
} else if types.is_unsigned(destination, state.selected) {
|
||||
limit := f64(i128(1) << u32(bits))
|
||||
if truncated < 0 || truncated >= limit {
|
||||
return {}, false
|
||||
}
|
||||
} else {
|
||||
return {}, false
|
||||
}
|
||||
return convert_macro_integer(i128(truncated), destination, state.selected)
|
||||
}
|
||||
return {}, false
|
||||
}
|
||||
|
||||
add_converted_macro_value_expr :: proc(
|
||||
state: ^State,
|
||||
value: Converted_Macro_Value,
|
||||
span: source.Span,
|
||||
) -> (ast.Expr_Id, bool) {
|
||||
#partial switch value.kind {
|
||||
case .Integer:
|
||||
return add_macro_value_expr(state, cimport.Macro_Value{
|
||||
kind=.Integer,
|
||||
integer=value.integer,
|
||||
negative=value.negative,
|
||||
}, span), true
|
||||
case .Float:
|
||||
return add_macro_value_expr(state, cimport.Macro_Value{
|
||||
kind=.Float,
|
||||
integer=value.integer,
|
||||
}, span), true
|
||||
case .Null:
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.None, span=span,
|
||||
left=ast.INVALID_EXPR, right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
case:
|
||||
}
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
|
||||
add_macro_aggregate_expr :: proc(
|
||||
state: ^State,
|
||||
result: ^cimport.Result,
|
||||
macro: cimport.Macro_Constant,
|
||||
record_type: types.Type,
|
||||
pkg: ast.Package_Id,
|
||||
record_mapping: []types.Type,
|
||||
type_mapping: []types.Type,
|
||||
span: source.Span,
|
||||
) -> (ast.Expr_Id, bool) {
|
||||
fields := types.fields_for(&state.module.type_store, record_type)
|
||||
union_record := types.is_union(record_type, &state.module.type_store)
|
||||
initializer_count := 1 if union_record else len(fields)
|
||||
if len(fields) == 0 || len(macro.values) > initializer_count {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
converted := make([]Converted_Macro_Value, len(macro.values), state.allocator)
|
||||
defer delete(converted, state.allocator)
|
||||
for value, index in macro.values {
|
||||
converted[index], _ = convert_macro_field_value(
|
||||
state,
|
||||
result,
|
||||
value,
|
||||
fields[index].type,
|
||||
pkg,
|
||||
record_mapping,
|
||||
type_mapping,
|
||||
)
|
||||
if converted[index].kind == .Invalid {
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
}
|
||||
args := make([]ast.Expr_Id, initializer_count, state.allocator)
|
||||
remaining := MAX_MACRO_ZERO_NODES
|
||||
for index in 0..<initializer_count {
|
||||
field := fields[index]
|
||||
value_expr := ast.INVALID_EXPR
|
||||
value_ok := false
|
||||
if index < len(macro.values) {
|
||||
value_expr, value_ok = add_converted_macro_value_expr(
|
||||
state, converted[index], span,
|
||||
)
|
||||
} else {
|
||||
value_expr, value_ok = add_macro_zero_expr(
|
||||
state, pkg, field.type, span, 0, &remaining,
|
||||
)
|
||||
}
|
||||
if !value_ok {
|
||||
delete(args, state.allocator)
|
||||
return ast.INVALID_EXPR, false
|
||||
}
|
||||
args[index] = add_import_expr(state, ast.Expr{
|
||||
kind=.Keyed, span=span, name=symbol.Id(field.name), left=value_expr,
|
||||
right=ast.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
return add_import_expr(state, ast.Expr{
|
||||
kind=.Struct_Literal,
|
||||
span=span,
|
||||
name=symbol.intern(state.symbols, macro.type_name),
|
||||
args=args,
|
||||
left=ast.INVALID_EXPR,
|
||||
right=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
}), true
|
||||
}
|
||||
|
||||
find_global_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> (ast.Global_Id, bool) {
|
||||
for global, index in module.globals {
|
||||
if global.pkg == pkg && global.name == name {
|
||||
return ast.global_id(index), true
|
||||
}
|
||||
}
|
||||
return ast.INVALID_GLOBAL, false
|
||||
}
|
||||
|
||||
find_function_in_package :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool {
|
||||
for function in module.functions {
|
||||
if function.pkg == pkg && function.name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
remove_value_declarations_in_package :: proc(
|
||||
state: ^State,
|
||||
pkg: ast.Package_Id,
|
||||
name: symbol.Id,
|
||||
) {
|
||||
function_index := 0
|
||||
for function_index < len(state.module.functions) {
|
||||
function := state.module.functions[function_index]
|
||||
if function.pkg != pkg || function.name != name {
|
||||
function_index += 1
|
||||
continue
|
||||
}
|
||||
delete(function.params, state.allocator)
|
||||
delete(function.body, state.allocator)
|
||||
delete(function.link_name, state.allocator)
|
||||
delete(function.unsupported_reason, state.allocator)
|
||||
ordered_remove(&state.module.functions, function_index)
|
||||
}
|
||||
global_index := 0
|
||||
for global_index < len(state.module.globals) {
|
||||
global := state.module.globals[global_index]
|
||||
if global.pkg != pkg || global.name != name {
|
||||
global_index += 1
|
||||
continue
|
||||
}
|
||||
delete(global.link_name, state.allocator)
|
||||
ordered_remove(&state.module.globals, global_index)
|
||||
}
|
||||
unsupported_index := 0
|
||||
for unsupported_index < len(state.module.unsupported) {
|
||||
item := state.module.unsupported[unsupported_index]
|
||||
if item.pkg != pkg || item.name != name {
|
||||
unsupported_index += 1
|
||||
continue
|
||||
}
|
||||
delete(item.reason, state.allocator)
|
||||
ordered_remove(&state.module.unsupported, unsupported_index)
|
||||
}
|
||||
}
|
||||
|
||||
add_external_variable_global :: proc(
|
||||
state: ^State,
|
||||
result: ^cimport.Result,
|
||||
pkg: ast.Package_Id,
|
||||
variable: cimport.Variable,
|
||||
record_mapping: []types.Type,
|
||||
type_mapping: []types.Type,
|
||||
span: source.Span,
|
||||
) {
|
||||
name := symbol.intern(state.symbols, variable.name)
|
||||
variable_type := translate_c_type(state, result, variable.type, pkg, record_mapping, type_mapping)
|
||||
unsupported_reason := variable.reason
|
||||
if len(unsupported_reason) == 0 {
|
||||
unsupported_reason = c_record_by_value_reason(result, variable.type)
|
||||
}
|
||||
if len(unsupported_reason) == 0 && !types.is_runtime_value(variable_type, &state.module.type_store) {
|
||||
unsupported_reason = "C variable type is not supported"
|
||||
}
|
||||
if len(unsupported_reason) > 0 {
|
||||
add_import_unsupported(state, pkg, variable.name, unsupported_reason)
|
||||
return
|
||||
}
|
||||
if find_function_in_package(state.module, pkg, name) {
|
||||
add_import_unsupported(state, pkg, variable.name, "C variable conflicts with a function declaration")
|
||||
return
|
||||
}
|
||||
if existing, ok := find_global_in_package(state.module, pkg, name); ok {
|
||||
global := state.module.globals[existing]
|
||||
if !global.external || !types.equal(global.type, variable_type) || global.writable != variable.mutable {
|
||||
add_import_unsupported(state, pkg, variable.name, "conflicting C declarations for variable")
|
||||
}
|
||||
return
|
||||
}
|
||||
_ = ast.global_id(len(state.module.globals))
|
||||
append(&state.module.globals, ast.Global{
|
||||
span=span,
|
||||
name=name,
|
||||
link_name=strings.clone(variable.name, state.allocator),
|
||||
pkg=pkg,
|
||||
file=ast.INVALID_FILE,
|
||||
type=variable_type,
|
||||
immutable=true,
|
||||
external=true,
|
||||
writable=variable.mutable,
|
||||
expr=ast.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
add_macro_constant_global :: proc(
|
||||
state: ^State,
|
||||
result: ^cimport.Result,
|
||||
pkg: ast.Package_Id,
|
||||
macro: cimport.Macro_Constant,
|
||||
record_mapping: []types.Type,
|
||||
type_mapping: []types.Type,
|
||||
span: source.Span,
|
||||
) {
|
||||
name := symbol.intern(state.symbols, macro.name)
|
||||
remove_value_declarations_in_package(state, pkg, name)
|
||||
if macro.aggregate {
|
||||
type_name := symbol.intern(state.symbols, macro.type_name)
|
||||
named := types.find_named(&state.module.type_store, u32(pkg), u32(type_name))
|
||||
record_type := types.resolve_alias(named, &state.module.type_store)
|
||||
if !types.is_record(record_type, &state.module.type_store) ||
|
||||
types.is_opaque_struct(record_type, &state.module.type_store) {
|
||||
add_import_unsupported(state, pkg, macro.name, "C macro aggregate type is not supported")
|
||||
return
|
||||
}
|
||||
expr, ok := add_macro_aggregate_expr(
|
||||
state,
|
||||
result,
|
||||
macro,
|
||||
record_type,
|
||||
pkg,
|
||||
record_mapping,
|
||||
type_mapping,
|
||||
span,
|
||||
)
|
||||
if !ok {
|
||||
add_import_unsupported(state, pkg, macro.name, "C macro aggregate initializer is not representable")
|
||||
return
|
||||
}
|
||||
_ = ast.global_id(len(state.module.globals))
|
||||
append(&state.module.globals, ast.Global{
|
||||
span=span,
|
||||
name=name,
|
||||
pkg=pkg,
|
||||
file=ast.INVALID_FILE,
|
||||
type=record_type,
|
||||
immutable=true,
|
||||
expr=expr,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
return
|
||||
}
|
||||
macro_type := translate_c_type(state, result, macro.type, pkg, record_mapping, type_mapping)
|
||||
if !types.is_runtime_value(macro_type, &state.module.type_store) {
|
||||
add_import_unsupported(state, pkg, macro.name, "C macro constant type is not supported")
|
||||
return
|
||||
}
|
||||
expr := add_macro_value_expr(state, macro.value, span)
|
||||
_ = ast.global_id(len(state.module.globals))
|
||||
append(&state.module.globals, ast.Global{
|
||||
span=span,
|
||||
name=name,
|
||||
pkg=pkg,
|
||||
file=ast.INVALID_FILE,
|
||||
type=macro_type,
|
||||
immutable=true,
|
||||
expr=expr,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
load_header :: proc(state: ^State, path: string, import_span: source.Span) -> ast.Package_Id {
|
||||
canonical, ok := filepath.abs(path, state.allocator)
|
||||
if !ok {
|
||||
@@ -460,14 +1080,38 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
|
||||
variadic=function.variadic,
|
||||
params=params,
|
||||
result=function_result,
|
||||
link_name=strings.clone(function.link_name, state.allocator),
|
||||
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
// Emit the wrapper only for a `static inline` that survives as supported.
|
||||
// cimport may translate its signature fine yet the by-value record layout
|
||||
// checks above can still reject it; a wrapper for an uncallable function
|
||||
// would just be dead external code.
|
||||
if len(unsupported_reason) == 0 && len(function.link_name) > 0 {
|
||||
if trampoline, ok := find_trampoline(&result, function.link_name); ok {
|
||||
add_c_trampoline(state, trampoline)
|
||||
}
|
||||
}
|
||||
}
|
||||
for variable in result.variables {
|
||||
add_external_variable_global(
|
||||
state, &result, pkg_id, variable, record_mapping, type_mapping, import_span,
|
||||
)
|
||||
}
|
||||
for macro in result.macros {
|
||||
add_macro_constant_global(
|
||||
state, &result, pkg_id, macro, record_mapping, type_mapping, import_span,
|
||||
)
|
||||
}
|
||||
for item in result.unsupported {
|
||||
name := symbol.intern(state.symbols, item.name)
|
||||
if item.final_macro {
|
||||
remove_value_declarations_in_package(state, pkg_id, name)
|
||||
}
|
||||
append(&state.module.unsupported, ast.Unsupported{
|
||||
pkg=pkg_id,
|
||||
name=symbol.intern(state.symbols, item.name),
|
||||
name=name,
|
||||
reason=strings.clone(item.reason, state.allocator),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -644,10 +644,13 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
|
||||
_ = ir.global_id(len(module.globals))
|
||||
append(&module.globals, ir.Global{
|
||||
name=global.name,
|
||||
link_name=fmt.aprintf("%s", global.link_name, allocator=allocator),
|
||||
type=global.type,
|
||||
is_static=global.is_static,
|
||||
external=global.external,
|
||||
writable=global.writable,
|
||||
static_value=global.static_value,
|
||||
initializer=nil if global.is_static else lower_global_initializer(hir_module, global, allocator),
|
||||
initializer=nil if global.is_static || global.external else lower_global_initializer(hir_module, global, allocator),
|
||||
problematic=global.problematic,
|
||||
diagnostic=global.diagnostic,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user