1674 lines
43 KiB
Odin
1674 lines
43 KiB
Odin
package checker
|
|
|
|
import "../ast"
|
|
import "../hir"
|
|
import "../source"
|
|
import "../types"
|
|
import "base:intrinsics"
|
|
import "core:fmt"
|
|
import "core:mem"
|
|
import "core:slice"
|
|
import "core:strings"
|
|
|
|
Spec :: struct {
|
|
template: int,
|
|
args: []types.Type,
|
|
result: types.Type,
|
|
hir_id: int,
|
|
}
|
|
|
|
Infer_Local :: struct {
|
|
name: string,
|
|
type: types.Type,
|
|
}
|
|
|
|
Build_Local :: struct {
|
|
name: string,
|
|
type: types.Type,
|
|
mutable: bool,
|
|
id: int,
|
|
}
|
|
|
|
Constant_Kind :: enum {
|
|
Unknown,
|
|
Not_Constant,
|
|
Value,
|
|
Overflow,
|
|
}
|
|
|
|
Constant :: struct {
|
|
kind: Constant_Kind,
|
|
value: i128,
|
|
}
|
|
|
|
Symbol_Index_Entry :: struct {
|
|
scope: int,
|
|
name: string,
|
|
id: int,
|
|
}
|
|
|
|
Checker :: struct {
|
|
ast_module: ^ast.Module,
|
|
diagnostics: ^source.Diagnostics,
|
|
module: hir.Module,
|
|
specs: [dynamic]Spec,
|
|
function_index: []Symbol_Index_Entry,
|
|
global_index: []Symbol_Index_Entry,
|
|
import_index: []Symbol_Index_Entry,
|
|
global_types: []types.Type,
|
|
constants: []Constant,
|
|
allocator: mem.Allocator,
|
|
}
|
|
|
|
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
|
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
|
return Constant{kind = .Not_Constant}
|
|
}
|
|
if checker.constants[expr_id].kind != .Unknown {
|
|
return checker.constants[expr_id]
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
result: Constant
|
|
switch expr.kind {
|
|
case .Integer:
|
|
result = Constant{kind = .Value, value = i128(expr.integer)}
|
|
case .Add:
|
|
left := eval_constant(checker, expr.left)
|
|
right := eval_constant(checker, expr.right)
|
|
if left.kind == .Overflow || right.kind == .Overflow {
|
|
result = Constant{kind = .Overflow}
|
|
} else if left.kind != .Value || right.kind != .Value {
|
|
result = Constant{kind = .Not_Constant}
|
|
} else {
|
|
value, overflow := intrinsics.overflow_add(left.value, right.value)
|
|
if overflow {
|
|
result = Constant{kind = .Overflow}
|
|
} else {
|
|
result = Constant{kind = .Value, value = value}
|
|
}
|
|
}
|
|
case .Invalid, .Name, .Call:
|
|
result = Constant{kind = .Not_Constant}
|
|
}
|
|
checker.constants[expr_id] = result
|
|
return result
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
symbol_index_less :: proc(left, right: Symbol_Index_Entry) -> bool {
|
|
if left.scope != right.scope {
|
|
return left.scope < right.scope
|
|
}
|
|
if left.name != right.name {
|
|
return left.name < right.name
|
|
}
|
|
return left.id < right.id
|
|
}
|
|
|
|
find_symbol :: proc(index: []Symbol_Index_Entry, scope: int, name: string) -> int {
|
|
low := 0
|
|
high := len(index)
|
|
for low < high {
|
|
middle := low + (high-low)/2
|
|
entry := index[middle]
|
|
if entry.scope < scope || entry.scope == scope && entry.name < name {
|
|
low = middle + 1
|
|
} else {
|
|
high = middle
|
|
}
|
|
}
|
|
if low < len(index) && index[low].scope == scope && index[low].name == name {
|
|
return index[low].id
|
|
}
|
|
return -1
|
|
}
|
|
|
|
build_symbol_indexes :: proc(checker: ^Checker) {
|
|
checker.function_index = make([]Symbol_Index_Entry, len(checker.ast_module.functions), checker.allocator)
|
|
for function, id in checker.ast_module.functions {
|
|
checker.function_index[id] = Symbol_Index_Entry{scope=function.pkg, name=function.name, id=id}
|
|
}
|
|
slice.sort_by(checker.function_index, symbol_index_less)
|
|
|
|
checker.global_index = make([]Symbol_Index_Entry, len(checker.ast_module.globals), checker.allocator)
|
|
for global, id in checker.ast_module.globals {
|
|
checker.global_index[id] = Symbol_Index_Entry{scope=global.pkg, name=global.name, id=id}
|
|
}
|
|
slice.sort_by(checker.global_index, symbol_index_less)
|
|
|
|
checker.import_index = make([]Symbol_Index_Entry, len(checker.ast_module.imports), checker.allocator)
|
|
for import_item, id in checker.ast_module.imports {
|
|
checker.import_index[id] = Symbol_Index_Entry{scope=import_item.file, name=import_item.alias, id=id}
|
|
}
|
|
slice.sort_by(checker.import_index, symbol_index_less)
|
|
}
|
|
|
|
find_template :: proc(checker: ^Checker, name: string, pkg := 0) -> int {
|
|
return find_symbol(checker.function_index, pkg, name)
|
|
}
|
|
|
|
find_global :: proc(checker: ^Checker, name: string, pkg := 0) -> int {
|
|
return find_symbol(checker.global_index, pkg, name)
|
|
}
|
|
|
|
find_import :: proc(checker: ^Checker, file: int, alias: string, mark_used := false) -> int {
|
|
id := find_symbol(checker.import_index, file, alias)
|
|
if id >= 0 && mark_used {
|
|
checker.ast_module.imports[id].used = true
|
|
}
|
|
return id
|
|
}
|
|
|
|
expr_package :: proc(checker: ^Checker, expr: ast.Expr, pkg, file: int, mark_used := false) -> (int, bool) {
|
|
if expr.qualifier == "" {
|
|
return pkg, true
|
|
}
|
|
import_id := find_import(checker, file, expr.qualifier, mark_used)
|
|
if import_id < 0 {
|
|
return -1, false
|
|
}
|
|
import_item := checker.ast_module.imports[import_id]
|
|
if import_item.target < 0 || 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: int) -> int {
|
|
if find_import(checker, file, expr.qualifier) < 0 {
|
|
return source.addf(checker.diagnostics, expr.span, "unknown package alias '%s'", expr.qualifier)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unavailable imported package '%s'", expr.qualifier)
|
|
}
|
|
|
|
add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: int) -> int {
|
|
if find_template(checker, expr.text, target_pkg) >= 0 {
|
|
return source.addf(checker.diagnostics, expr.span, "'%s' is a function, not a global value", expr.text)
|
|
}
|
|
if expr.qualifier != "" {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"package '%s' has no member '%s'",
|
|
expr.qualifier,
|
|
expr.text,
|
|
)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unresolved global '%s'", expr.text)
|
|
}
|
|
|
|
add_call_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target_pkg: int) -> int {
|
|
if find_global(checker, expr.text, target_pkg) >= 0 {
|
|
return source.addf(checker.diagnostics, expr.span, "'%s' is a global, not a function", expr.text)
|
|
}
|
|
if expr.qualifier != "" {
|
|
return source.addf(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"package '%s' has no member '%s'",
|
|
expr.qualifier,
|
|
expr.text,
|
|
)
|
|
}
|
|
return source.addf(checker.diagnostics, expr.span, "unresolved function '%s'", expr.text)
|
|
}
|
|
|
|
contains_name :: proc(names: []string, name: string) -> bool {
|
|
for existing in names {
|
|
if existing == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
|
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
|
return
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
switch expr.kind {
|
|
case .Name:
|
|
if expr.qualifier != "" {
|
|
_ = find_import(checker, file, expr.qualifier, true)
|
|
}
|
|
case .Call:
|
|
if expr.qualifier != "" {
|
|
_ = find_import(checker, file, expr.qualifier, true)
|
|
}
|
|
for arg in expr.args {
|
|
mark_expr_imports_used(checker, arg, file)
|
|
}
|
|
case .Add:
|
|
mark_expr_imports_used(checker, expr.left, file)
|
|
mark_expr_imports_used(checker, expr.right, file)
|
|
case .Invalid, .Integer:
|
|
}
|
|
}
|
|
|
|
validate_declarations :: proc(checker: ^Checker) {
|
|
for function in checker.ast_module.functions {
|
|
locals: [dynamic]string
|
|
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'",
|
|
param.name,
|
|
)
|
|
}
|
|
append(&locals, param.name)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
find_infer_local :: proc(locals: []Infer_Local, name: string) -> 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: int, 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: int, actual_args: []types.Type) -> int {
|
|
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 index
|
|
}
|
|
}
|
|
result := type_from_syntax(function.result)
|
|
if function.pkg == 0 && function.name == "main" && function.result == .Int {
|
|
result = types.I32
|
|
}
|
|
index := len(checker.specs)
|
|
append(
|
|
&checker.specs,
|
|
Spec{template = template, args = signature[:], result = result, hir_id = -1},
|
|
)
|
|
return index
|
|
}
|
|
|
|
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type {
|
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
|
return types.INVALID
|
|
}
|
|
constant := eval_constant(checker, expr_id)
|
|
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
|
return types.I64
|
|
}
|
|
if constant.kind == .Value {
|
|
return types.smallest_signed_for_literal(i64(constant.value))
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
switch expr.kind {
|
|
case .Invalid:
|
|
return types.INVALID
|
|
case .Integer:
|
|
return types.smallest_signed_for_literal(expr.integer)
|
|
case .Name:
|
|
if expr.qualifier == "" {
|
|
local_type := find_infer_local(locals, expr.text)
|
|
if types.is_valid(local_type) {
|
|
return local_type
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
if !available {
|
|
return types.INVALID
|
|
}
|
|
global := find_global(checker, expr.text, target_pkg)
|
|
if global >= 0 {
|
|
return checker.global_types[global]
|
|
}
|
|
return types.INVALID
|
|
case .Add:
|
|
left := infer_expr(checker, expr.left, locals, pkg, file)
|
|
right := infer_expr(checker, expr.right, locals, pkg, file)
|
|
return types.widest(left, right)
|
|
case .Call:
|
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
if !available {
|
|
return types.INVALID
|
|
}
|
|
template := find_template(checker, expr.text, target_pkg)
|
|
if template < 0 {
|
|
return types.INVALID
|
|
}
|
|
args := make([]types.Type, len(expr.args), checker.allocator)
|
|
for arg, index in expr.args {
|
|
args[index] = infer_expr(checker, arg, locals, pkg, file)
|
|
}
|
|
function := checker.ast_module.functions[template]
|
|
if !can_specialize(function, args) {
|
|
delete(args, checker.allocator)
|
|
declared := type_from_syntax(function.result)
|
|
if function.pkg == 0 && function.name == "main" && function.result == .Int {
|
|
return types.I32
|
|
}
|
|
if declared.kind == .Concrete || declared.kind == .Void {
|
|
return declared
|
|
}
|
|
return types.INVALID
|
|
}
|
|
spec := ensure_spec(checker, template, args)
|
|
delete(args, checker.allocator)
|
|
return checker.specs[spec].result
|
|
}
|
|
return types.INVALID
|
|
}
|
|
|
|
infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
|
|
spec := checker.specs[spec_id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
declared := type_from_syntax(function.result)
|
|
if function.pkg == 0 && function.name == "main" && 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 >= 0 {
|
|
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, "main", 0)
|
|
if main_template >= 0 {
|
|
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 spec_id := 0; spec_id < len(checker.specs); spec_id += 1 {
|
|
inferred := infer_spec_result(checker, spec_id)
|
|
changed = merge_inferred_type(&checker.specs[spec_id].result, inferred) || changed
|
|
}
|
|
if len(checker.specs) != spec_count {
|
|
changed = true
|
|
}
|
|
if !changed {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> int {
|
|
id := len(checker.module.exprs)
|
|
append(&checker.module.exprs, expr)
|
|
return id
|
|
}
|
|
|
|
invalid_hir_expr :: proc(
|
|
checker: ^Checker,
|
|
span: source.Span,
|
|
diagnostic: int,
|
|
recovery_type := types.INVALID,
|
|
) -> int {
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Invalid,
|
|
span = span,
|
|
type = recovery_type,
|
|
target = -1,
|
|
left = -1,
|
|
right = -1,
|
|
diagnostic = diagnostic,
|
|
},
|
|
)
|
|
}
|
|
|
|
add_unique :: proc(values: ^[dynamic]int, value: int) {
|
|
for existing in values {
|
|
if existing == value {
|
|
return
|
|
}
|
|
}
|
|
append(values, value)
|
|
}
|
|
|
|
find_build_local :: proc(locals: []Build_Local, name: string) -> (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: int,
|
|
expected: types.Type,
|
|
span: source.Span,
|
|
) -> int {
|
|
if expr_id < 0 {
|
|
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 = -1,
|
|
right = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
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,
|
|
) -> int {
|
|
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 = -1,
|
|
left = -1,
|
|
right = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
|
|
build_expr :: proc(
|
|
checker: ^Checker,
|
|
expr_id: int,
|
|
locals: []Build_Local,
|
|
global_reads: ^[dynamic]int,
|
|
calls: ^[dynamic]int,
|
|
expected := types.INVALID,
|
|
pkg := 0,
|
|
file := 0,
|
|
) -> int {
|
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
|
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
|
return invalid_hir_expr(checker, source.Span{}, id)
|
|
}
|
|
expr := checker.ast_module.exprs[expr_id]
|
|
constant := eval_constant(checker, expr_id)
|
|
if constant.kind == .Value || constant.kind == .Overflow {
|
|
return build_constant_expr(checker, expr, constant, expected)
|
|
}
|
|
switch expr.kind {
|
|
case .Invalid:
|
|
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
|
case .Integer:
|
|
unreachable()
|
|
case .Name:
|
|
if expr.qualifier == "" {
|
|
if local, ok := find_build_local(locals, expr.text); ok {
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Local,
|
|
span = expr.span,
|
|
type = local.type,
|
|
target = local.id,
|
|
left = -1,
|
|
right = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
if !available {
|
|
id := add_package_resolution_diagnostic(checker, expr, file)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
global := find_global(checker, expr.text, target_pkg)
|
|
if global >= 0 {
|
|
add_unique(global_reads, global)
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Global,
|
|
span = expr.span,
|
|
type = checker.global_types[global],
|
|
target = global,
|
|
left = -1,
|
|
right = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
case .Add:
|
|
left := build_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
right := build_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
left_type := checker.module.exprs[left].type
|
|
right_type := checker.module.exprs[right].type
|
|
result := types.widest(left_type, right_type)
|
|
if !types.is_signed(result) {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
expr.span,
|
|
"addition requires compatible signed integers",
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
|
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Add,
|
|
span = expr.span,
|
|
type = result,
|
|
left = left,
|
|
right = right,
|
|
target = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
case .Call:
|
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
if !available {
|
|
id := add_package_resolution_diagnostic(checker, expr, file)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
template := find_template(checker, expr.text, target_pkg)
|
|
if template < 0 {
|
|
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
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",
|
|
expr.text,
|
|
len(checker.ast_module.functions[template].params),
|
|
len(expr.args),
|
|
)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
built_args := make([]int, len(expr.args), checker.allocator)
|
|
arg_types := make([]types.Type, len(expr.args), checker.allocator)
|
|
for arg, index in expr.args {
|
|
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[index].type)
|
|
if arg_expected.kind != .Concrete {
|
|
arg_expected = types.INVALID
|
|
}
|
|
built_args[index] = build_expr(
|
|
checker,
|
|
arg,
|
|
locals,
|
|
global_reads,
|
|
calls,
|
|
arg_expected,
|
|
pkg,
|
|
file,
|
|
)
|
|
arg_types[index] = checker.module.exprs[built_args[index]].type
|
|
}
|
|
spec := ensure_spec(checker, template, arg_types)
|
|
delete(arg_types, checker.allocator)
|
|
for _, index in built_args {
|
|
built_args[index] = coerce_expr(
|
|
checker,
|
|
built_args[index],
|
|
checker.specs[spec].args[index],
|
|
checker.module.exprs[built_args[index]].span,
|
|
)
|
|
}
|
|
add_unique(calls, spec)
|
|
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'",
|
|
expr.text,
|
|
)
|
|
delete(built_args, checker.allocator)
|
|
return invalid_hir_expr(checker, expr.span, id)
|
|
}
|
|
return add_hir_expr(
|
|
checker,
|
|
hir.Expr {
|
|
kind = .Call,
|
|
span = expr.span,
|
|
type = result,
|
|
target = spec,
|
|
left = -1,
|
|
right = -1,
|
|
args = built_args,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
return invalid_hir_expr(
|
|
checker,
|
|
expr.span,
|
|
source.add(checker.diagnostics, expr.span, "invalid expression"),
|
|
)
|
|
}
|
|
|
|
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
|
|
spec := checker.specs[spec_id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
if function.pkg == 0 && function.name == "main" {
|
|
return fmt.aprintf("main", 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, 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, spec_id: int) {
|
|
if checker.specs[spec_id].hir_id >= 0 {
|
|
return
|
|
}
|
|
spec := checker.specs[spec_id]
|
|
function := checker.ast_module.functions[spec.template]
|
|
signature_diagnostic := -1
|
|
if spec.result.kind != .Void && !types.is_concrete_integer(spec.result) {
|
|
checker.specs[spec_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'",
|
|
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'",
|
|
function.name,
|
|
)
|
|
break
|
|
}
|
|
}
|
|
hir_id := len(checker.module.functions)
|
|
checker.specs[spec_id].hir_id = hir_id
|
|
|
|
locals: [dynamic]Build_Local
|
|
locals.allocator = checker.allocator
|
|
hir_locals: [dynamic]hir.Local
|
|
hir_locals.allocator = checker.allocator
|
|
params: [dynamic]int
|
|
params.allocator = checker.allocator
|
|
body: [dynamic]int
|
|
body.allocator = checker.allocator
|
|
global_reads: [dynamic]int
|
|
global_reads.allocator = checker.allocator
|
|
calls: [dynamic]int
|
|
calls.allocator = checker.allocator
|
|
|
|
for param, index in function.params {
|
|
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(¶ms, local_id)
|
|
}
|
|
|
|
problematic := signature_diagnostic >= 0
|
|
has_return := false
|
|
if signature_diagnostic >= 0 {
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = function.span,
|
|
expr = -1,
|
|
local = -1,
|
|
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'",
|
|
statement.name,
|
|
)
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = id,
|
|
},
|
|
)
|
|
problematic = true
|
|
continue
|
|
}
|
|
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, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Declaration,
|
|
span = statement.span,
|
|
local = local_id,
|
|
expr = value,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
case .Assignment:
|
|
if statement.name == "_" {
|
|
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, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = id,
|
|
},
|
|
)
|
|
problematic = true
|
|
} else {
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Sink,
|
|
span = statement.span,
|
|
expr = value,
|
|
local = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
continue
|
|
}
|
|
local, found := find_build_local(locals[:], statement.name)
|
|
if !found {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"cannot assign unresolved local '%s'",
|
|
statement.name,
|
|
)
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = id,
|
|
},
|
|
)
|
|
problematic = true
|
|
continue
|
|
}
|
|
if !local.mutable {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"cannot assign immutable local '%s'",
|
|
statement.name,
|
|
)
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
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, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Assignment,
|
|
span = statement.span,
|
|
expr = value,
|
|
local = local.id,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
case .Return:
|
|
has_return = true
|
|
if statement.expr < 0 {
|
|
if spec.result.kind != .Void {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"'return _' is only valid in a void function",
|
|
)
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = id,
|
|
},
|
|
)
|
|
problematic = true
|
|
} else {
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Return,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
continue
|
|
}
|
|
if spec.result.kind == .Void {
|
|
id := source.add(
|
|
checker.diagnostics,
|
|
statement.span,
|
|
"void function cannot return a value",
|
|
)
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
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, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Return,
|
|
span = statement.span,
|
|
expr = value,
|
|
local = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
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, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = id,
|
|
},
|
|
)
|
|
problematic = true
|
|
} else {
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Expression,
|
|
span = statement.span,
|
|
expr = value,
|
|
local = -1,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
}
|
|
case .Invalid:
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = statement.span,
|
|
expr = -1,
|
|
local = -1,
|
|
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",
|
|
function.name,
|
|
)
|
|
append(&body, len(checker.module.statements))
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt{kind = .Trap, span = function.span, expr = -1, local = -1, diagnostic = id},
|
|
)
|
|
problematic = true
|
|
}
|
|
|
|
append(
|
|
&checker.module.functions,
|
|
hir.Function {
|
|
name = function.name,
|
|
link_name = make_link_name(checker, spec_id),
|
|
c_abi = function.c_abi || (function.pkg == 0 && function.name == "main"),
|
|
is_main = function.pkg == 0 && function.name == "main",
|
|
params = params[:],
|
|
result = spec.result,
|
|
locals = hir_locals[:],
|
|
body = body[:],
|
|
direct_global_reads = global_reads[:],
|
|
calls = calls[:],
|
|
problematic = problematic,
|
|
diagnostic = -1,
|
|
},
|
|
)
|
|
delete(locals)
|
|
}
|
|
|
|
expr_problematic :: proc(module: ^hir.Module, expr_id: int) -> bool {
|
|
if expr_id < 0 || expr_id >= len(module.exprs) {
|
|
return true
|
|
}
|
|
expr := module.exprs[expr_id]
|
|
if expr.kind == .Invalid {
|
|
return true
|
|
}
|
|
if expr.left >= 0 && expr_problematic(module, expr.left) {
|
|
return true
|
|
}
|
|
if expr.right >= 0 && expr_problematic(module, expr.right) {
|
|
return true
|
|
}
|
|
for arg in expr.args {
|
|
if expr_problematic(module, arg) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
build_globals :: proc(checker: ^Checker) {
|
|
for global, global_id in checker.ast_module.globals {
|
|
dependencies: [dynamic]int
|
|
dependencies.allocator = checker.allocator
|
|
calls: [dynamic]int
|
|
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_id]
|
|
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 := -1
|
|
if !types.is_concrete_integer(global_type) {
|
|
diagnostic = source.addf(
|
|
checker.diagnostics,
|
|
global.span,
|
|
"could not resolve a concrete type for global '%s'",
|
|
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 < 0
|
|
static_value: i64
|
|
if is_static {
|
|
static_value = checker.module.exprs[expr].integer
|
|
}
|
|
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.module, expr),
|
|
problematic = expr_problematic(&checker.module, 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 >= 0 && call < len(checker.specs) {
|
|
hir_id := checker.specs[call].hir_id
|
|
if hir_id >= 0 && checker.module.functions[hir_id].problematic {
|
|
function.problematic = true
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for &global in checker.module.globals {
|
|
if global.problematic {
|
|
continue
|
|
}
|
|
for dependency in global.dependencies {
|
|
if dependency >= 0 &&
|
|
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 >= 0 && call < len(checker.specs) {
|
|
hir_id := checker.specs[call].hir_id
|
|
if hir_id >= 0 && checker.module.functions[hir_id].problematic {
|
|
global.problematic = true
|
|
changed = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
resolve_call_targets :: proc(checker: ^Checker) {
|
|
for &expr in checker.module.exprs {
|
|
if expr.kind == .Call && expr.target >= 0 && expr.target < len(checker.specs) {
|
|
expr.target = checker.specs[expr.target].hir_id
|
|
}
|
|
}
|
|
}
|
|
|
|
append_unique_slice :: proc(values: ^[]int, value: int, allocator: mem.Allocator) -> bool {
|
|
for existing in values^ {
|
|
if existing == value {
|
|
return false
|
|
}
|
|
}
|
|
replacement := make([]int, len(values^) + 1, allocator)
|
|
copy(replacement, values^)
|
|
replacement[len(values^)] = value
|
|
delete(values^, allocator)
|
|
values^ = replacement
|
|
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 < 0 || call >= len(checker.specs) {
|
|
continue
|
|
}
|
|
callee := checker.specs[call].hir_id
|
|
if callee < 0 || callee >= len(checker.module.functions) {
|
|
continue
|
|
}
|
|
for global_id in checker.module.functions[callee].direct_global_reads {
|
|
if append_unique_slice(
|
|
&function.direct_global_reads,
|
|
global_id,
|
|
checker.allocator,
|
|
) {
|
|
changed = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for &global in checker.module.globals {
|
|
for call in global.calls {
|
|
if call < 0 || call >= len(checker.specs) {
|
|
continue
|
|
}
|
|
function_id := checker.specs[call].hir_id
|
|
if function_id < 0 || function_id >= len(checker.module.functions) {
|
|
continue
|
|
}
|
|
for dependency in checker.module.functions[function_id].direct_global_reads {
|
|
_ = append_unique_slice(&global.dependencies, dependency, checker.allocator)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []u8) {
|
|
if states[global_id] == 2 {
|
|
return
|
|
}
|
|
if states[global_id] == 1 {
|
|
id := source.addf(
|
|
checker.diagnostics,
|
|
checker.ast_module.globals[global_id].span,
|
|
"global initialization cycle involving '%s'",
|
|
checker.module.globals[global_id].name,
|
|
)
|
|
checker.module.globals[global_id].diagnostic = id
|
|
checker.module.globals[global_id].problematic = true
|
|
return
|
|
}
|
|
states[global_id] = 1
|
|
for dependency in checker.module.globals[global_id].dependencies {
|
|
if dependency >= 0 && dependency < len(states) {
|
|
detect_global_cycles_visit(checker, dependency, states)
|
|
if checker.module.globals[dependency].problematic {
|
|
checker.module.globals[global_id].problematic = true
|
|
}
|
|
}
|
|
}
|
|
states[global_id] = 2
|
|
}
|
|
|
|
synthesize_trap_main :: proc(checker: ^Checker) {
|
|
id := source.add(checker.diagnostics, source.Span{}, "missing or unusable main function")
|
|
statement_id := len(checker.module.statements)
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt{kind = .Trap, span = source.Span{}, expr = -1, local = -1, diagnostic = id},
|
|
)
|
|
body := make([]int, 1, checker.allocator)
|
|
body[0] = statement_id
|
|
append(
|
|
&checker.module.functions,
|
|
hir.Function {
|
|
name = "main",
|
|
link_name = fmt.aprintf("main", allocator = checker.allocator),
|
|
c_abi = true,
|
|
is_main = true,
|
|
result = types.VOID,
|
|
body = body,
|
|
problematic = true,
|
|
diagnostic = id,
|
|
},
|
|
)
|
|
}
|
|
|
|
replace_main_with_trap :: proc(checker: ^Checker, diagnostic: int) {
|
|
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.problematic = true
|
|
function.diagnostic = diagnostic
|
|
statement_id := len(checker.module.statements)
|
|
append(
|
|
&checker.module.statements,
|
|
hir.Stmt {
|
|
kind = .Trap,
|
|
span = source.Span{},
|
|
expr = -1,
|
|
local = -1,
|
|
diagnostic = diagnostic,
|
|
},
|
|
)
|
|
function.body = make([]int, 1, checker.allocator)
|
|
function.body[0] = statement_id
|
|
return
|
|
}
|
|
synthesize_trap_main(checker)
|
|
}
|
|
|
|
check :: proc(
|
|
ast_module: ^ast.Module,
|
|
diagnostics: ^source.Diagnostics,
|
|
allocator := context.allocator,
|
|
) -> hir.Module {
|
|
checker := Checker {
|
|
ast_module = ast_module,
|
|
diagnostics = diagnostics,
|
|
module = hir.init_module(allocator),
|
|
allocator = allocator,
|
|
}
|
|
checker.specs.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)
|
|
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)
|
|
}
|
|
|
|
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'", 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", 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'", global.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
validate_declarations(&checker)
|
|
infer_all(&checker)
|
|
build_globals(&checker)
|
|
for spec_id := 0; spec_id < len(checker.specs); spec_id += 1 {
|
|
build_function(&checker, spec_id)
|
|
}
|
|
resolve_call_targets(&checker)
|
|
propagate_global_reads(&checker)
|
|
|
|
main_template := find_template(&checker, "main", 0)
|
|
main_declarations := 0
|
|
for function in ast_module.functions {
|
|
if function.pkg == 0 && function.name == "main" {
|
|
main_declarations += 1
|
|
}
|
|
}
|
|
if main_declarations == 0 {
|
|
synthesize_trap_main(&checker)
|
|
} else {
|
|
template := ast_module.functions[main_template]
|
|
if main_declarations != 1 ||
|
|
len(template.params) != 0 ||
|
|
!(template.result == .Void || template.result == .I32 || template.result == .Int) {
|
|
id := source.add(
|
|
diagnostics,
|
|
template.span,
|
|
"main must be unique, 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 global_id in 0 ..< len(checker.module.globals) {
|
|
detect_global_cycles_visit(&checker, global_id, 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'", import_item.alias)
|
|
}
|
|
}
|
|
return checker.module
|
|
}
|