extern variables and object-like macro consts

This commit is contained in:
2026-06-17 21:52:05 +02:00
parent f5605fd3ec
commit 19e9fbdd4b
33 changed files with 3136 additions and 116 deletions
+645 -1
View File
@@ -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),
})
}