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
+160 -14
View File
@@ -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 {