Files
brolang/compiler/checker/checker.odin
T

2050 lines
59 KiB
Odin

package checker
import "../ast"
import "../hir"
import "../source"
import "../symbol"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:mem"
import "core:slice"
import "core:strings"
Spec_Id :: distinct u32
INVALID_SPEC :: Spec_Id(0xffff_ffff)
spec_id :: proc(index: int) -> Spec_Id {
assert(index >= 0 && u64(index) < u64(INVALID_SPEC))
return Spec_Id(index)
}
spec_index :: proc(id: Spec_Id, count: int) -> (int, bool) {
index := int(id)
return index, id != INVALID_SPEC && index < count
}
Spec :: struct {
template: ast.Function_Id,
args: []types.Type,
result: types.Type,
hir_id: hir.Function_Id,
}
Infer_Local :: struct {
name: symbol.Id,
type: types.Type,
}
Build_Local :: struct {
name: symbol.Id,
type: types.Type,
mutable: bool,
id: hir.Local_Id,
}
Constant_Kind :: enum {
Unknown,
Not_Constant,
Value,
Overflow,
}
Constant :: struct {
kind: Constant_Kind,
value: i128,
}
Function_Index_Entry :: struct {
scope: ast.Package_Id,
name: symbol.Id,
id: ast.Function_Id,
}
Global_Index_Entry :: struct {
scope: ast.Package_Id,
name: symbol.Id,
id: ast.Global_Id,
}
Import_Index_Entry :: struct {
scope: ast.File_Id,
name: symbol.Id,
id: ast.Import_Id,
}
Checker :: struct {
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
module: hir.Module,
specs: [dynamic]Spec,
function_index: []Function_Index_Entry,
global_index: []Global_Index_Entry,
import_index: []Import_Index_Entry,
global_types: []types.Type,
constants: []Constant,
template_diagnostics: []source.Diagnostic_Id,
constant_stack: [dynamic]Constant_Frame,
ast_expr_stack: [dynamic]ast.Expr_Id,
hir_expr_stack: [dynamic]hir.Expr_Id,
infer_stack: [dynamic]Infer_Frame,
build_stack: [dynamic]Build_Expr_Frame,
cycle_stack: [dynamic]Cycle_Frame,
main_symbol: symbol.Id,
sink_symbol: symbol.Id,
allocator: mem.Allocator,
}
symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
return symbol.resolve(checker.symbols, id)
}
Constant_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
}
eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return Constant{kind = .Not_Constant}
}
stack := checker.constant_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.constant_stack = stack
}
append(&stack, Constant_Frame{expr=expr_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if checker.constants[frame.expr].kind != .Unknown {
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 0 {
stack[frame_index].stage = 1
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.ast_module.exprs) &&
checker.constants[expr.left].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.left})
}
continue
}
if frame.stage == 1 {
stack[frame_index].stage = 2
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) &&
checker.constants[expr.right].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.right})
}
continue
}
left := Constant{kind = .Not_Constant}
right := Constant{kind = .Not_Constant}
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
left = checker.constants[expr.left]
}
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) {
right = checker.constants[expr.right]
}
result := Constant{kind = .Not_Constant}
if left.kind == .Overflow || right.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if left.kind == .Value && right.kind == .Value {
value, overflow := intrinsics.overflow_add(left.value, right.value)
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
}
return checker.constants[expr_id]
}
fits_signed_type :: proc(value: i128, target: types.Type) -> bool {
if !types.is_signed(target) {
return false
}
limit := i128(1) << u32(target.bits - 1)
return value >= -limit && value < limit
}
fits_i64 :: proc(value: i128) -> bool {
return fits_signed_type(value, types.I64)
}
type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type {
switch value {
case .Int:
return types.INT
case .I8:
return types.I8
case .I16:
return types.I16
case .I32:
return types.I32
case .I64:
return types.I64
case .Void:
return types.VOID
case .Invalid:
return types.INVALID
}
return types.INVALID
}
function_index_less :: proc(left, right: Function_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return int(left.name) < int(right.name)
}
return left.id < right.id
}
global_index_less :: proc(left, right: Global_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return int(left.name) < int(right.name)
}
return left.id < right.id
}
import_index_less :: proc(left, right: Import_Index_Entry) -> bool {
if left.scope != right.scope {
return left.scope < right.scope
}
if left.name != right.name {
return int(left.name) < int(right.name)
}
return left.id < right.id
}
find_function_symbol :: proc(index: []Function_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Function_Id {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return ast.INVALID_FUNCTION
}
find_global_symbol :: proc(index: []Global_Index_Entry, scope: ast.Package_Id, name: symbol.Id) -> ast.Global_Id {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return ast.INVALID_GLOBAL
}
find_import_symbol :: proc(index: []Import_Index_Entry, scope: ast.File_Id, name: symbol.Id) -> ast.Import_Id {
low := 0
high := len(index)
for low < high {
middle := low + (high-low)/2
entry := index[middle]
if entry.scope < scope || entry.scope == scope && int(entry.name) < int(name) {
low = middle + 1
} else {
high = middle
}
}
if low < len(index) && index[low].scope == scope && index[low].name == name {
return index[low].id
}
return ast.INVALID_IMPORT
}
build_symbol_indexes :: proc(checker: ^Checker) {
checker.function_index = make([]Function_Index_Entry, len(checker.ast_module.functions), checker.allocator)
for function, id in checker.ast_module.functions {
checker.function_index[id] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)}
}
slice.sort_by(checker.function_index, function_index_less)
checker.global_index = make([]Global_Index_Entry, len(checker.ast_module.globals), checker.allocator)
for global, id in checker.ast_module.globals {
checker.global_index[id] = Global_Index_Entry{scope=global.pkg, name=global.name, id=ast.global_id(id)}
}
slice.sort_by(checker.global_index, global_index_less)
checker.import_index = make([]Import_Index_Entry, len(checker.ast_module.imports), checker.allocator)
for import_item, id in checker.ast_module.imports {
checker.import_index[id] = Import_Index_Entry{scope=import_item.file, name=import_item.alias, id=ast.import_id(id)}
}
slice.sort_by(checker.import_index, import_index_less)
}
find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Function_Id {
return find_function_symbol(checker.function_index, pkg, name)
}
find_global :: proc(checker: ^Checker, name: symbol.Id, pkg := ast.Package_Id(0)) -> ast.Global_Id {
return find_global_symbol(checker.global_index, pkg, name)
}
find_import :: proc(checker: ^Checker, file: ast.File_Id, alias: symbol.Id, mark_used := false) -> ast.Import_Id {
id := find_import_symbol(checker.import_index, file, alias)
if id != ast.INVALID_IMPORT && mark_used {
checker.ast_module.imports[id].used = true
}
return id
}
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg: ast.Package_Id, file: ast.File_Id, mark_used := false) -> (ast.Package_Id, bool) {
if !symbol.is_valid(expr.qualifier) {
return pkg, true
}
import_id := find_import(checker, file, expr.qualifier, mark_used)
if import_id == ast.INVALID_IMPORT {
return ast.INVALID_PACKAGE, false
}
import_item := checker.ast_module.imports[import_id]
if import_item.target == ast.INVALID_PACKAGE || int(import_item.target) >= len(checker.ast_module.packages) ||
!checker.ast_module.packages[import_item.target].available {
return import_item.target, false
}
return import_item.target, true
}
add_package_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, file: ast.File_Id) -> source.Diagnostic_Id {
if find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", symbol_text(checker, expr.qualifier))
}
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", symbol_text(checker, expr.qualifier))
}
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
if find_template(checker, expr.name, target_pkg) != ast.INVALID_FUNCTION {
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", symbol_text(checker, expr.name))
}
if symbol.is_valid(expr.qualifier) {
return source.addf(
checker.diagnostics,
expr.span,
"package '%s' has no member '%s'",
symbol_text(checker, expr.qualifier),
symbol_text(checker, expr.name),
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", symbol_text(checker, expr.name))
}
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: ast.Package_Id) -> source.Diagnostic_Id {
if find_global(checker, expr.name, target_pkg) != ast.INVALID_GLOBAL {
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", symbol_text(checker, expr.name))
}
if symbol.is_valid(expr.qualifier) {
return source.addf(
checker.diagnostics,
expr.span,
"package '%s' has no member '%s'",
symbol_text(checker, expr.qualifier),
symbol_text(checker, expr.name),
)
}
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", symbol_text(checker, expr.name))
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names {
if existing == name {
return true
}
}
return false
}
mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: ast.File_Id) {
stack := checker.ast_expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.ast_expr_stack = stack
}
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) {
continue
}
expr := checker.ast_module.exprs[id]
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
_ = find_import(checker, file, expr.qualifier, true)
}
switch expr.kind {
case .Call:
append(&stack, ..expr.args)
case .Add:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Name:
}
}
}
validate_declarations :: proc(checker: ^Checker) {
for function, function_id in checker.ast_module.functions {
locals: [dynamic]symbol.Id
locals.allocator = checker.allocator
for param in function.params {
if param.type == .Void {
source.add(
checker.diagnostics,
param.span,
"void is only valid as a function result type",
)
}
if contains_name(locals[:], param.name) {
source.addf(
checker.diagnostics,
param.span,
"duplicate parameter '%s'",
symbol_text(checker, param.name),
)
}
append(&locals, param.name)
}
if !function.has_body && !function.c_abi {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"bodyless function '%s' must use 'c func'",
symbol_text(checker, function.name),
)
}
if !function.has_body && function.c_abi {
for param in function.params {
if type_from_syntax(param.type).kind != .Concrete {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
param.span,
"foreign function '%s' requires concrete parameter types",
symbol_text(checker, function.name),
)
}
}
result := type_from_syntax(function.result)
if result.kind != .Concrete && result.kind != .Void {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"foreign function '%s' requires a concrete or void result type",
symbol_text(checker, function.name),
)
}
if function.pkg == 0 && function.name == checker.main_symbol {
checker.template_diagnostics[function_id] = source.add(
checker.diagnostics,
function.span,
"main must have a body",
)
}
}
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration, .Assignment, .Return, .Expression:
mark_expr_imports_used(checker, statement.expr, function.file)
case .Invalid:
}
}
delete(locals)
}
for function, function_id in checker.ast_module.functions {
if function.has_body || !function.c_abi {
continue
}
for other, other_id in checker.ast_module.functions {
if other_id == function_id || other.has_body || !other.c_abi || other.name != function.name {
continue
}
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"duplicate foreign symbol '%s'",
symbol_text(checker, function.name),
)
break
}
}
}
find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type {
for index := len(locals) - 1; index >= 0; index -= 1 {
if locals[index].name == name {
return locals[index].type
}
}
return types.INVALID
}
spec_signature_equal :: proc(spec: Spec, template: ast.Function_Id, args: []types.Type) -> bool {
if spec.template != template || len(spec.args) != len(args) {
return false
}
for arg, index in args {
if !types.equal(spec.args[index], arg) {
return false
}
}
return true
}
specialized_param_type :: proc(syntax: ast.Type_Syntax, actual: types.Type) -> types.Type {
declared := type_from_syntax(syntax)
if declared.kind == .Int_Constraint {
return actual
}
return declared
}
can_specialize :: proc(function: ast.Function, actual_args: []types.Type) -> bool {
for param, index in function.params {
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
}
if !types.is_concrete_integer(specialized_param_type(param.type, actual)) {
return false
}
}
return true
}
ensure_spec :: proc(checker: ^Checker, template: ast.Function_Id, actual_args: []types.Type) -> Spec_Id {
function := checker.ast_module.functions[template]
signature: [dynamic]types.Type
signature.allocator = checker.allocator
for param, index in function.params {
actual := types.INVALID
if index < len(actual_args) {
actual = actual_args[index]
}
append(&signature, specialized_param_type(param.type, actual))
}
for spec, index in checker.specs {
if spec_signature_equal(spec, template, signature[:]) {
delete(signature)
return spec_id(index)
}
}
result := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
result = types.I32
}
index := spec_id(len(checker.specs))
append(
&checker.specs,
Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION},
)
return index
}
Infer_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
left: types.Type,
arg_index: int,
args: []types.Type,
template: ast.Function_Id,
}
infer_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
locals: []Infer_Local,
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
) -> types.Type {
stack := checker.infer_stack
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.args, checker.allocator)
}
clear_dynamic_array(&stack)
checker.infer_stack = stack
}
append(&stack, Infer_Frame{expr=expr_id, template=ast.INVALID_FUNCTION})
last := types.INVALID
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if frame.expr == ast.INVALID_EXPR || int(frame.expr) >= len(checker.ast_module.exprs) {
last = types.INVALID
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
last = types.I64
_ = pop(&stack)
continue
}
if constant.kind == .Value {
last = types.smallest_signed_for_literal(i64(constant.value))
_ = pop(&stack)
continue
}
switch expr.kind {
case .Invalid:
last = types.INVALID
_ = pop(&stack)
case .Integer:
last = types.smallest_signed_for_literal(expr.integer)
_ = pop(&stack)
case .Name:
last = types.INVALID
if !symbol.is_valid(expr.qualifier) {
last = find_infer_local(locals, expr.name)
}
if !types.is_valid(last) {
target_pkg, available := expr_package(checker, expr, pkg, file)
if available {
global := find_global(checker, expr.name, target_pkg)
if global != ast.INVALID_GLOBAL {
last = checker.global_types[global]
}
}
}
_ = pop(&stack)
case .Add:
stack[frame_index].stage = 1
append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION})
case .Call:
target_pkg, available := expr_package(checker, expr, pkg, file)
template := ast.INVALID_FUNCTION
if available {
template = find_template(checker, expr.name, target_pkg)
}
if template == ast.INVALID_FUNCTION {
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 declared.kind == .Concrete || declared.kind == .Void else types.INVALID
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION})
}
}
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
append(&stack, Infer_Frame{expr=expr.right, template=ast.INVALID_FUNCTION})
continue
}
if frame.stage == 2 {
last = types.widest(frame.left, last)
_ = pop(&stack)
continue
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].args[frame.arg_index] = last
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION})
continue
}
}
function := checker.ast_module.functions[frame.template]
if can_specialize(function, stack[frame_index].args) {
spec := ensure_spec(checker, frame.template, stack[frame_index].args)
last = checker.specs[spec].result
} else {
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
last = types.I32
} else {
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
}
}
delete(stack[frame_index].args, checker.allocator)
stack[frame_index].args = nil
_ = pop(&stack)
}
}
return last
}
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id) -> types.Type {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
declared := type_from_syntax(function.result)
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
declared = types.I32
}
locals: [dynamic]Infer_Local
locals.allocator = checker.allocator
defer delete(locals)
for param, index in function.params {
param_type := types.INVALID
if index < len(spec.args) {
param_type = spec.args[index]
}
append(&locals, Infer_Local{name = param.name, type = param_type})
}
result := types.INVALID
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
declared_local := type_from_syntax(statement.type)
if declared_local.kind == .Concrete {
value_type = declared_local
}
append(&locals, Infer_Local{name = statement.name, type = value_type})
case .Assignment, .Expression:
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
case .Return:
if statement.expr != ast.INVALID_EXPR {
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file)
if !types.is_valid(result) {
result = returned
} else {
result = types.widest(result, returned)
}
}
}
}
if declared.kind == .Int_Constraint {
return result
}
return declared
}
merge_inferred_type :: proc(current: ^types.Type, inferred: types.Type) -> bool {
if !types.is_concrete_integer(inferred) {
return false
}
if !types.is_concrete_integer(current^) {
current^ = inferred
return true
}
merged := types.widest(current^, inferred)
if types.is_concrete_integer(merged) && !types.equal(current^, merged) {
current^ = merged
return true
}
return false
}
infer_all :: proc(checker: ^Checker) {
for global, index in checker.ast_module.globals {
declared := type_from_syntax(global.type)
if declared.kind == .Concrete {
checker.global_types[index] = declared
}
}
main_template := find_template(checker, checker.main_symbol, 0)
if main_template != ast.INVALID_FUNCTION {
ensure_spec(checker, main_template, nil)
}
for {
changed := false
spec_count := len(checker.specs)
for global, index in checker.ast_module.globals {
if type_from_syntax(global.type).kind == .Concrete {
continue
}
inferred := infer_expr(checker, global.expr, nil, global.pkg, global.file)
changed = merge_inferred_type(&checker.global_types[index], inferred) || changed
}
for index := 0; index < len(checker.specs); index += 1 {
id := spec_id(index)
inferred := infer_spec_result(checker, id)
changed = merge_inferred_type(&checker.specs[id].result, inferred) || changed
}
if len(checker.specs) != spec_count {
changed = true
}
if !changed {
break
}
}
}
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> hir.Expr_Id {
id := hir.expr_id(len(checker.module.exprs))
append(&checker.module.exprs, expr)
return id
}
invalid_hir_expr :: proc(
checker: ^Checker,
span: source.Span,
diagnostic: source.Diagnostic_Id,
recovery_type := types.INVALID,
) -> hir.Expr_Id {
return add_hir_expr(
checker,
hir.Expr {
kind = .Invalid,
span = span,
type = recovery_type,
target = hir.INVALID_REF,
left = hir.INVALID_EXPR,
right = hir.INVALID_EXPR,
diagnostic = diagnostic,
},
)
}
add_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) {
for existing in values {
if existing == value {
return
}
}
append(values, value)
}
add_unique_function :: proc(values: ^[dynamic]hir.Function_Id, value: hir.Function_Id) {
for existing in values {
if existing == value {
return
}
}
append(values, value)
}
find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local, bool) {
for index := len(locals) - 1; index >= 0; index -= 1 {
if locals[index].name == name {
return locals[index], true
}
}
return Build_Local{}, false
}
coerce_expr :: proc(
checker: ^Checker,
expr_id: hir.Expr_Id,
expected: types.Type,
span: source.Span,
) -> hir.Expr_Id {
if expr_id == hir.INVALID_EXPR {
return expr_id
}
actual := checker.module.exprs[expr_id].type
if types.equal(actual, expected) {
return expr_id
}
if types.can_widen(actual, expected) {
return add_hir_expr(
checker,
hir.Expr {
kind = .Widen,
span = span,
type = expected,
left = expr_id,
target = hir.INVALID_REF,
right = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
id := source.addf(
checker.diagnostics,
span,
"cannot implicitly convert %s to %s",
types.name(actual),
types.name(expected),
)
return invalid_hir_expr(checker, span, id, expected)
}
build_constant_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
constant: Constant,
expected: types.Type,
) -> hir.Expr_Id {
recovery_type := types.I64
if types.is_signed(expected) {
recovery_type = expected
}
if constant.kind == .Overflow || !fits_i64(constant.value) {
id := source.add(
checker.diagnostics,
expr.span,
"integer constant expression exceeds signed i64 range",
)
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
value := i64(constant.value)
result_type := types.smallest_signed_for_literal(value)
if types.is_signed(expected) {
if !fits_signed_type(constant.value, expected) {
id := source.addf(
checker.diagnostics,
expr.span,
"integer constant %d does not fit in %s",
constant.value,
types.name(expected),
)
return invalid_hir_expr(checker, expr.span, id, expected)
}
result_type = expected
}
return add_hir_expr(
checker,
hir.Expr {
kind = .Integer,
span = expr.span,
type = result_type,
integer = value,
target = hir.INVALID_REF,
left = hir.INVALID_EXPR,
right = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
Build_Expr_Frame :: struct {
expr: ast.Expr_Id,
expected: types.Type,
stage: u8,
left: hir.Expr_Id,
arg_index: int,
built_args: []hir.Expr_Id,
arg_types: []types.Type,
template: ast.Function_Id,
}
build_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
locals: []Build_Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
expected := types.INVALID,
pkg := ast.Package_Id(0),
file := ast.File_Id(0),
) -> hir.Expr_Id {
stack := checker.build_stack
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.built_args, checker.allocator)
delete(frame.arg_types, checker.allocator)
}
clear_dynamic_array(&stack)
checker.build_stack = stack
}
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=ast.INVALID_FUNCTION})
last := hir.INVALID_EXPR
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if frame.expr == ast.INVALID_EXPR || int(frame.expr) >= len(checker.ast_module.exprs) {
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
last = invalid_hir_expr(checker, source.Span{}, id)
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Value || constant.kind == .Overflow {
last = build_constant_expr(checker, expr, constant, frame.expected)
_ = pop(&stack)
continue
}
switch expr.kind {
case .Invalid, .Integer:
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
_ = pop(&stack)
case .Name:
last = hir.INVALID_EXPR
if !symbol.is_valid(expr.qualifier) {
if local, ok := find_build_local(locals, expr.name); ok {
last = add_hir_expr(checker, hir.Expr{
kind=.Local, span=expr.span, type=local.type, target=hir.local_ref(local.id),
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
}
if last == hir.INVALID_EXPR {
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
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,
})
} else {
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
last = invalid_hir_expr(checker, expr.span, id)
}
}
_ = pop(&stack)
case .Add:
stack[frame_index].stage = 1
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=ast.INVALID_FUNCTION})
case .Call:
target_pkg, available := expr_package(checker, expr, pkg, file, true)
if !available {
id := add_package_resolution_diagnostic(checker, expr, file)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION {
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC {
last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
_ = pop(&stack)
continue
}
if len(expr.args) != len(checker.ast_module.functions[template].params) {
id := source.addf(
checker.diagnostics,
expr.span,
"function '%s' expects %d arguments, got %d",
symbol_text(checker, expr.name),
len(checker.ast_module.functions[template].params),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id)
_ = pop(&stack)
continue
}
stack[frame_index].template = template
stack[frame_index].built_args = make([]hir.Expr_Id, len(expr.args), checker.allocator)
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[0].type)
if arg_expected.kind != .Concrete {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION})
}
}
continue
}
if frame.stage == 1 {
stack[frame_index].left = last
stack[frame_index].stage = 2
append(&stack, Build_Expr_Frame{expr=expr.right, expected=types.INVALID, template=ast.INVALID_FUNCTION})
continue
}
if frame.stage == 2 {
left := frame.left
right := last
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_signed(result) {
id := source.add(checker.diagnostics, expr.span, "addition requires compatible signed integers")
last = invalid_hir_expr(checker, expr.span, id)
} else {
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
last = add_hir_expr(checker, hir.Expr{
kind=.Add, span=expr.span, type=result, left=left, right=right,
target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC,
})
}
_ = pop(&stack)
continue
}
if frame.stage == 3 {
if frame.arg_index < len(expr.args) {
stack[frame_index].built_args[frame.arg_index] = last
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
arg_expected := type_from_syntax(checker.ast_module.functions[frame.template].params[next].type)
if arg_expected.kind != .Concrete {
arg_expected = types.INVALID
}
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION})
continue
}
}
spec := ensure_spec(checker, frame.template, stack[frame_index].arg_types)
delete(stack[frame_index].arg_types, checker.allocator)
stack[frame_index].arg_types = nil
for _, index in stack[frame_index].built_args {
stack[frame_index].built_args[index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
checker.specs[spec].args[index],
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
add_unique_function(calls, function_id)
result := checker.specs[spec].result
if !types.is_valid(result) {
id := source.addf(
checker.diagnostics,
expr.span,
"could not resolve result type for specialization of '%s'",
symbol_text(checker, expr.name),
)
delete(stack[frame_index].built_args, checker.allocator)
stack[frame_index].built_args = nil
last = invalid_hir_expr(checker, expr.span, id)
} else {
last = add_hir_expr(checker, hir.Expr{
kind=.Call, span=expr.span, type=result, target=hir.function_ref(function_id),
left = hir.INVALID_EXPR, right = hir.INVALID_EXPR, args=stack[frame_index].built_args, diagnostic = source.INVALID_DIAGNOSTIC,
})
stack[frame_index].built_args = nil
}
_ = pop(&stack)
}
}
return last
}
make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
if function.pkg == 0 && function.name == checker.main_symbol {
return fmt.aprintf("main", allocator = checker.allocator)
}
if !function.has_body && function.c_abi {
return fmt.aprintf("%s", symbol_text(checker, function.name), allocator = checker.allocator)
}
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
strings.write_string(&builder, "bro_c__" if function.c_abi else "bro__")
fmt.sbprintf(&builder, "p%d__", function.pkg)
strings.write_string(&builder, symbol_text(checker, function.name))
for arg in spec.args {
strings.write_string(&builder, "__")
strings.write_string(&builder, types.name(arg))
}
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
}
build_function :: proc(checker: ^Checker, id: Spec_Id) {
spec := checker.specs[id]
function := checker.ast_module.functions[spec.template]
signature_diagnostic := source.INVALID_DIAGNOSTIC
if spec.result.kind != .Void && !types.is_concrete_integer(spec.result) {
checker.specs[id].result = types.I64
spec.result = types.I64
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete result type for '%s'",
symbol_text(checker, function.name),
)
}
for arg in spec.args {
if !types.is_concrete_integer(arg) {
signature_diagnostic = source.addf(
checker.diagnostics,
function.span,
"could not resolve a concrete parameter type for '%s'",
symbol_text(checker, function.name),
)
break
}
}
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
locals: [dynamic]Build_Local
locals.allocator = checker.allocator
hir_locals: [dynamic]hir.Local
hir_locals.allocator = checker.allocator
params: [dynamic]hir.Local_Id
params.allocator = checker.allocator
body: [dynamic]hir.Stmt_Id
body.allocator = checker.allocator
global_reads: [dynamic]hir.Global_Id
global_reads.allocator = checker.allocator
calls: [dynamic]hir.Function_Id
calls.allocator = checker.allocator
for param, index in function.params {
local_id := hir.local_id(len(hir_locals))
param_type := types.INVALID
if index < len(spec.args) {
param_type = spec.args[index]
}
append(&hir_locals, hir.Local{name = param.name, type = param_type, parameter = true})
append(&locals, Build_Local{name = param.name, type = param_type, id = local_id})
append(&params, local_id)
}
problematic := signature_diagnostic != source.INVALID_DIAGNOSTIC ||
checker.template_diagnostics[spec.template] != source.INVALID_DIAGNOSTIC
if !function.has_body {
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
&checker.module.functions,
hir.Function {
name = function.name,
link_name = make_link_name(checker, id),
calling_convention = .C if function.c_abi else .Brolang,
implementation = .Declaration,
linkage = .External if function.c_abi else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
params = params[:],
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads,
calls = calls[:],
problematic = problematic,
diagnostic = checker.template_diagnostics[spec.template],
},
)
delete(locals)
return
}
has_return := false
if signature_diagnostic != source.INVALID_DIAGNOSTIC {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = function.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = signature_diagnostic,
},
)
}
for statement_id in function.body {
statement := checker.ast_module.statements[statement_id]
switch statement.kind {
case .Declaration:
declared := type_from_syntax(statement.type)
expected := types.INVALID
if declared.kind == .Concrete {
expected = declared
}
value := build_expr(
checker,
statement.expr,
locals[:],
&global_reads,
&calls,
expected,
function.pkg,
function.file,
)
value_type := checker.module.exprs[value].type
if declared.kind == .Concrete {
value = coerce_expr(checker, value, declared, statement.span)
value_type = checker.module.exprs[value].type
} else if declared.kind == .Void {
id := source.add(
checker.diagnostics,
statement.span,
"locals cannot have type void",
)
value = invalid_hir_expr(checker, statement.span, id)
value_type = types.INVALID
}
if _, found := find_build_local(locals[:], statement.name); found {
id := source.addf(
checker.diagnostics,
statement.span,
"duplicate local '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
local_id := hir.local_id(len(hir_locals))
append(
&hir_locals,
hir.Local {
name = statement.name,
type = value_type,
mutable = !statement.immutable,
},
)
append(
&locals,
Build_Local {
name = statement.name,
type = value_type,
mutable = !statement.immutable,
id = local_id,
},
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Declaration,
span = statement.span,
local = local_id,
expr = value,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Assignment:
if statement.name == checker.sink_symbol {
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
if checker.module.exprs[value].type.kind == .Void {
id := source.add(
checker.diagnostics,
statement.span,
"cannot assign a void expression to '_'",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Sink,
span = statement.span,
expr = value,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
continue
}
local, found := find_build_local(locals[:], statement.name)
if !found {
id := source.addf(
checker.diagnostics,
statement.span,
"cannot assign unresolved local '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
if !local.mutable {
id := source.addf(
checker.diagnostics,
statement.span,
"cannot assign immutable local '%s'",
symbol_text(checker, statement.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
value := build_expr(
checker,
statement.expr,
locals[:],
&global_reads,
&calls,
local.type,
function.pkg,
function.file,
)
value = coerce_expr(checker, value, local.type, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Assignment,
span = statement.span,
expr = value,
local = local.id,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Return:
has_return = true
if statement.expr == ast.INVALID_EXPR {
if spec.result.kind != .Void {
id := source.add(
checker.diagnostics,
statement.span,
"'return _' is only valid in a void function",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Return,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
continue
}
if spec.result.kind == .Void {
id := source.add(
checker.diagnostics,
statement.span,
"void function cannot return a value",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
continue
}
value := build_expr(
checker,
statement.expr,
locals[:],
&global_reads,
&calls,
spec.result,
function.pkg,
function.file,
)
value = coerce_expr(checker, value, spec.result, statement.span)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Return,
span = statement.span,
expr = value,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
problematic = problematic || checker.module.exprs[value].kind == .Invalid
case .Expression:
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
if checker.module.exprs[value].type.kind != .Void {
id := source.add(
checker.diagnostics,
statement.span,
"non-void expression result must be consumed or assigned to '_'",
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = id,
},
)
problematic = true
} else {
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Expression,
span = statement.span,
expr = value,
local = hir.INVALID_LOCAL,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
case .Invalid:
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = statement.span,
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = statement.diagnostic,
},
)
problematic = true
}
}
if spec.result.kind != .Void && !has_return {
id := source.addf(
checker.diagnostics,
function.span,
"function '%s' does not return a value",
symbol_text(checker, function.name),
)
append(&body, hir.stmt_id(len(checker.module.statements)))
append(
&checker.module.statements,
hir.Stmt{kind = .Trap, span = function.span, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id},
)
problematic = true
}
assert(spec.hir_id == hir.function_id(len(checker.module.functions)))
append(
&checker.module.functions,
hir.Function {
name = function.name,
link_name = make_link_name(checker, id),
calling_convention = .C if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Brolang,
implementation = .Definition,
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
params = params[:],
result = spec.result,
locals = hir_locals[:],
body = body[:],
direct_global_reads = global_reads,
calls = calls[:],
problematic = problematic,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
delete(locals)
}
expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool {
module := &checker.module
stack := checker.hir_expr_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.hir_expr_stack = stack
}
append(&stack, expr_id)
for len(stack) > 0 {
id := pop(&stack)
if id == hir.INVALID_EXPR || int(id) >= len(module.exprs) {
return true
}
expr := module.exprs[id]
if expr.kind == .Invalid {
return true
}
if expr.left != hir.INVALID_EXPR {
append(&stack, expr.left)
}
if expr.right != hir.INVALID_EXPR {
append(&stack, expr.right)
}
append(&stack, ..expr.args)
}
return false
}
build_globals :: proc(checker: ^Checker) {
for global, global_index in checker.ast_module.globals {
dependencies: [dynamic]hir.Global_Id
dependencies.allocator = checker.allocator
calls: [dynamic]hir.Function_Id
calls.allocator = checker.allocator
declared := type_from_syntax(global.type)
expected := types.INVALID
if declared.kind == .Concrete {
expected = declared
}
expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file)
global_type := checker.global_types[global_index]
if declared.kind == .Concrete {
expr = coerce_expr(checker, expr, declared, global.span)
global_type = checker.module.exprs[expr].type
} else if types.is_concrete_integer(checker.module.exprs[expr].type) {
global_type = checker.module.exprs[expr].type
}
diagnostic := source.INVALID_DIAGNOSTIC
if !types.is_concrete_integer(global_type) {
diagnostic = source.addf(
checker.diagnostics,
global.span,
"could not resolve a concrete type for global '%s'",
symbol_text(checker, global.name),
)
global_type = types.I64
expr = invalid_hir_expr(checker, global.span, diagnostic, global_type)
}
if global.type == .Void {
diagnostic = source.add(
checker.diagnostics,
global.span,
"void is only valid as a function result type",
)
expr = invalid_hir_expr(checker, global.span, diagnostic)
}
if !global.immutable {
diagnostic = source.add(
checker.diagnostics,
global.span,
"mutable declarations are only valid inside functions",
)
expr = invalid_hir_expr(checker, global.span, diagnostic)
}
is_static := checker.module.exprs[expr].kind == .Integer && diagnostic == source.INVALID_DIAGNOSTIC
static_value: i64
if is_static {
static_value = checker.module.exprs[expr].integer
}
_ = hir.global_id(len(checker.module.globals))
append(
&checker.module.globals,
hir.Global {
name = global.name,
type = global_type,
expr = expr,
static_value = static_value,
is_static = is_static,
dependencies = dependencies,
calls = calls[:],
direct_problem = expr_problematic(checker, expr),
problematic = expr_problematic(checker, expr),
diagnostic = diagnostic,
},
)
}
}
propagate_problems :: proc(checker: ^Checker) {
changed := true
for changed {
changed = false
for &function in checker.module.functions {
if function.problematic {
continue
}
for call in function.calls {
if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) &&
checker.module.functions[call].problematic {
function.problematic = true
changed = true
break
}
}
}
for &global in checker.module.globals {
if global.problematic {
continue
}
for dependency in global.dependencies {
if dependency != hir.INVALID_GLOBAL &&
int(dependency) < len(checker.module.globals) &&
checker.module.globals[dependency].problematic {
global.problematic = true
changed = true
break
}
}
if global.problematic {
continue
}
for call in global.calls {
if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) &&
checker.module.functions[call].problematic {
global.problematic = true
changed = true
break
}
}
}
}
}
append_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) -> bool {
for existing in values^ {
if existing == value {
return false
}
}
append(values, value)
return true
}
propagate_global_reads :: proc(checker: ^Checker) {
changed := true
for changed {
changed = false
for &function in checker.module.functions {
for call in function.calls {
if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) {
continue
}
for global_id in checker.module.functions[call].direct_global_reads {
if append_unique_global(&function.direct_global_reads, global_id) {
changed = true
}
}
}
}
}
for &global in checker.module.globals {
for call in global.calls {
if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) {
continue
}
for dependency in checker.module.functions[call].direct_global_reads {
_ = append_unique_global(&global.dependencies, dependency)
}
}
}
}
Cycle_Frame :: struct {
global: hir.Global_Id,
next_dependency: int,
}
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id, states: []u8) {
if states[global_id] == 2 {
return
}
stack := checker.cycle_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.cycle_stack = stack
}
append(&stack, Cycle_Frame{global=global_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := &stack[frame_index]
if states[frame.global] == 0 {
states[frame.global] = 1
}
dependencies := checker.module.globals[frame.global].dependencies
if frame.next_dependency >= len(dependencies) {
states[frame.global] = 2
if checker.module.globals[frame.global].problematic && frame_index > 0 {
checker.module.globals[stack[frame_index-1].global].problematic = true
}
_ = pop(&stack)
continue
}
dependency := dependencies[frame.next_dependency]
frame.next_dependency += 1
if dependency == hir.INVALID_GLOBAL || int(dependency) >= len(states) {
continue
}
if states[dependency] == 1 {
id := source.addf(
checker.diagnostics,
checker.ast_module.globals[dependency].span,
"global initialization cycle involving '%s'",
symbol_text(checker, checker.module.globals[dependency].name),
)
checker.module.globals[dependency].diagnostic = id
checker.module.globals[dependency].problematic = true
checker.module.globals[frame.global].problematic = true
continue
}
if states[dependency] == 2 {
if checker.module.globals[dependency].problematic {
checker.module.globals[frame.global].problematic = true
}
continue
}
append(&stack, Cycle_Frame{global=dependency})
}
}
synthesize_trap_main :: proc(checker: ^Checker) {
id := source.add(checker.diagnostics, source.Span{}, "missing or unusable main function")
statement_id := hir.stmt_id(len(checker.module.statements))
_ = hir.function_id(len(checker.module.functions))
append(
&checker.module.statements,
hir.Stmt{kind = .Trap, span = source.Span{}, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id},
)
body := make([]hir.Stmt_Id, 1, checker.allocator)
body[0] = statement_id
append(
&checker.module.functions,
hir.Function {
name = checker.main_symbol,
link_name = fmt.aprintf("main", allocator = checker.allocator),
calling_convention = .C,
implementation = .Definition,
linkage = .External,
is_main = true,
result = types.VOID,
body = body,
problematic = true,
diagnostic = id,
},
)
}
replace_main_with_trap :: proc(checker: ^Checker, diagnostic: source.Diagnostic_Id) {
for &function in checker.module.functions {
if !function.is_main {
continue
}
delete(function.params, checker.allocator)
delete(function.body, checker.allocator)
function.params = nil
function.result = types.VOID
function.calling_convention = .C
function.implementation = .Definition
function.linkage = .External
function.problematic = true
function.diagnostic = diagnostic
statement_id := hir.stmt_id(len(checker.module.statements))
append(
&checker.module.statements,
hir.Stmt {
kind = .Trap,
span = source.Span{},
expr = hir.INVALID_EXPR,
local = hir.INVALID_LOCAL,
diagnostic = diagnostic,
},
)
function.body = make([]hir.Stmt_Id, 1, checker.allocator)
function.body[0] = statement_id
return
}
synthesize_trap_main(checker)
}
check :: proc(
ast_module: ^ast.Module,
diagnostics: ^source.Diagnostics,
symbols: ^symbol.Table,
allocator := context.allocator,
) -> hir.Module {
checker := Checker {
ast_module = ast_module,
diagnostics = diagnostics,
symbols = symbols,
module = hir.init_module(allocator),
main_symbol = symbol.intern(symbols, "main"),
sink_symbol = symbol.intern(symbols, "_"),
allocator = allocator,
}
checker.specs.allocator = allocator
checker.constant_stack.allocator = allocator
checker.ast_expr_stack.allocator = allocator
checker.hir_expr_stack.allocator = allocator
checker.infer_stack.allocator = allocator
checker.build_stack.allocator = allocator
checker.cycle_stack.allocator = allocator
build_symbol_indexes(&checker)
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
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 {
diagnostic = source.INVALID_DIAGNOSTIC
}
defer {
for spec in checker.specs {
delete(spec.args, allocator)
}
delete(checker.specs)
delete(checker.function_index, allocator)
delete(checker.global_index, allocator)
delete(checker.import_index, allocator)
delete(checker.global_types, allocator)
delete(checker.constants, allocator)
delete(checker.template_diagnostics, allocator)
delete(checker.constant_stack)
delete(checker.ast_expr_stack)
delete(checker.hir_expr_stack)
delete(checker.infer_stack)
delete(checker.build_stack)
delete(checker.cycle_stack)
}
for function, index in ast_module.functions {
for previous in ast_module.functions[:index] {
if previous.pkg == function.pkg && previous.name == function.name {
source.addf(diagnostics, function.span, "duplicate function '%s'", symbol_text(&checker, function.name))
}
}
for global in ast_module.globals {
if global.pkg == function.pkg && global.name == function.name {
source.addf(diagnostics, function.span, "package declaration '%s' conflicts with a global", symbol_text(&checker, function.name))
}
}
}
for global, index in ast_module.globals {
for previous in ast_module.globals[:index] {
if previous.pkg == global.pkg && previous.name == global.name {
source.addf(diagnostics, global.span, "duplicate global '%s'", symbol_text(&checker, global.name))
}
}
}
validate_declarations(&checker)
infer_all(&checker)
for index in 0..<len(checker.specs) {
checker.specs[index].hir_id = hir.function_id(index)
}
build_globals(&checker)
for index := 0; index < len(checker.specs); index += 1 {
build_function(&checker, spec_id(index))
}
propagate_global_reads(&checker)
main_template := find_template(&checker, checker.main_symbol, 0)
main_declarations := 0
for function in ast_module.functions {
if function.pkg == 0 && function.name == checker.main_symbol {
main_declarations += 1
}
}
if main_declarations == 0 {
synthesize_trap_main(&checker)
} else {
template := ast_module.functions[main_template]
if main_declarations != 1 ||
!template.has_body ||
len(template.params) != 0 ||
!(template.result == .Void || template.result == .I32 || template.result == .Int) {
id := checker.template_diagnostics[main_template]
if id == source.INVALID_DIAGNOSTIC {
id = source.add(
diagnostics,
template.span,
"main must be unique, have a body, take no parameters, and return void, i32, or int",
)
}
replace_main_with_trap(&checker, id)
}
}
propagate_problems(&checker)
states := make([]u8, len(checker.module.globals), allocator)
for index in 0 ..< len(checker.module.globals) {
detect_global_cycles_visit(&checker, hir.global_id(index), states)
}
delete(states, allocator)
propagate_problems(&checker)
for import_item in ast_module.imports {
if import_item.valid && !import_item.used {
source.addf(diagnostics, import_item.span, "unused import '%s'", symbol_text(&checker, import_item.alias))
}
}
return checker.module
}