From 4112b79c6bff7e26f85d5ecb2245eb9e4f7ea2e2 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Fri, 12 Jun 2026 16:55:16 +0200 Subject: [PATCH] compact compiler ids and spans to reduce memory usage --- TODO.md | 1 - benchmarks/symbols/README.md | 6 + benchmarks/symbols/main.odin | 5 + compiler/ast/ast.odin | 106 ++++-- compiler/checker/checker.odin | 622 +++++++++++++++++++--------------- compiler/compiler.odin | 1 + compiler/hir/hir.odin | 97 ++++-- compiler/ir/ir.odin | 66 +++- compiler/lexer/lexer.odin | 14 +- compiler/llvm/llvm.odin | 89 ++--- compiler/loader/loader.odin | 43 ++- compiler/lower/lower.odin | 174 +++++----- compiler/parser/parser.odin | 110 +++--- compiler/source/source.odin | 90 +++-- compiler/token/token.odin | 10 +- compiler_tests.odin | 111 +++++- 16 files changed, 972 insertions(+), 573 deletions(-) diff --git a/TODO.md b/TODO.md index d825369..b9af602 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,6 @@ # compiler hardening follow-ups -- migrate spans, AST/HIR/IR ids, and diagnostics from `int` to compact integer types - prune unreachable function specializations before HIR construction and emission - support unary minus, including the signed i64 minimum literal boundary - move ignored example binaries into a dedicated build directory and remove `.review_tmp` diff --git a/benchmarks/symbols/README.md b/benchmarks/symbols/README.md index 34f56ce..5fb5e3a 100644 --- a/benchmarks/symbols/README.md +++ b/benchmarks/symbols/README.md @@ -29,3 +29,9 @@ The measured run reduced token size by 14.3% and peak tracked memory by 9.8%. After the iterative compiler-hardening work on 2026-06-12, the same benchmark reported 13,222,443 peak bytes and 40,117 allocations. The reusable traversal stacks keep allocation count effectively unchanged from the interning baseline. + +After migrating persistent compiler references and spans to compact IDs on +2026-06-12, the benchmark reported 8,651,659 peak bytes and 40,117 allocations. +`Span` is 12 bytes and `Token` is 24 bytes; AST expressions, HIR expressions, +and IR instructions are 64, 88, and 88 bytes respectively. These sizes are +also printed by the benchmark to catch layout regressions. diff --git a/benchmarks/symbols/main.odin b/benchmarks/symbols/main.odin index 80b0c67..2b806b4 100644 --- a/benchmarks/symbols/main.odin +++ b/benchmarks/symbols/main.odin @@ -3,6 +3,7 @@ package main import "../../compiler/ast" import "../../compiler/checker" import "../../compiler/hir" +import "../../compiler/ir" import "../../compiler/lexer" import "../../compiler/parser" import "../../compiler/source" @@ -72,6 +73,10 @@ main :: proc() { fmt.printf("allocation_count: %d\n", tracker.total_allocation_count) fmt.printf("token_count: %d\n", metrics.token_count) fmt.printf("token_size_bytes: %d\n", size_of(token.Token)) + fmt.printf("span_size_bytes: %d\n", size_of(source.Span)) + fmt.printf("ast_expr_size_bytes: %d\n", size_of(ast.Expr)) + fmt.printf("hir_expr_size_bytes: %d\n", size_of(hir.Expr)) + fmt.printf("ir_instruction_size_bytes: %d\n", size_of(ir.Instruction)) fmt.printf("unique_symbol_count: %d\n", metrics.unique_symbol_count) fmt.printf("stored_symbol_bytes: %d\n", metrics.stored_symbol_bytes) fmt.printf("diagnostic_count: %d\n", metrics.diagnostic_count) diff --git a/compiler/ast/ast.odin b/compiler/ast/ast.odin index ef51002..3b4fb95 100644 --- a/compiler/ast/ast.odin +++ b/compiler/ast/ast.odin @@ -4,9 +4,63 @@ import "../source" import "../symbol" import "core:mem" -INVALID_ID :: -1 +Expr_Id :: distinct u32 +Stmt_Id :: distinct u32 +Function_Id :: distinct u32 +Global_Id :: distinct u32 +Import_Id :: distinct u32 +File_Id :: distinct u32 +Package_Id :: distinct u32 -Type_Syntax :: enum { +INVALID_EXPR :: Expr_Id(0xffff_ffff) +INVALID_STMT :: Stmt_Id(0xffff_ffff) +INVALID_FUNCTION :: Function_Id(0xffff_ffff) +INVALID_GLOBAL :: Global_Id(0xffff_ffff) +INVALID_IMPORT :: Import_Id(0xffff_ffff) +INVALID_FILE :: File_Id(0xffff_ffff) +INVALID_PACKAGE :: Package_Id(0xffff_ffff) + +expr_id :: proc(index: int) -> Expr_Id { + assert(index >= 0 && u64(index) < u64(INVALID_EXPR)) + return Expr_Id(index) +} + +stmt_id :: proc(index: int) -> Stmt_Id { + assert(index >= 0 && u64(index) < u64(INVALID_STMT)) + return Stmt_Id(index) +} + +function_id :: proc(index: int) -> Function_Id { + assert(index >= 0 && u64(index) < u64(INVALID_FUNCTION)) + return Function_Id(index) +} + +global_id :: proc(index: int) -> Global_Id { + assert(index >= 0 && u64(index) < u64(INVALID_GLOBAL)) + return Global_Id(index) +} + +import_id :: proc(index: int) -> Import_Id { + assert(index >= 0 && u64(index) < u64(INVALID_IMPORT)) + return Import_Id(index) +} + +file_id :: proc(index: int) -> File_Id { + assert(index >= 0 && u64(index) < u64(INVALID_FILE)) + return File_Id(index) +} + +package_id :: proc(index: int) -> Package_Id { + assert(index >= 0 && u64(index) < u64(INVALID_PACKAGE)) + return Package_Id(index) +} + +index :: proc(id: $T, invalid: T, count: int) -> (int, bool) { + value := int(id) + return value, id != invalid && value < count +} + +Type_Syntax :: enum u8 { Invalid, Int, I8, @@ -16,7 +70,7 @@ Type_Syntax :: enum { Void, } -Expr_Kind :: enum { +Expr_Kind :: enum u8 { Invalid, Integer, Name, @@ -25,15 +79,15 @@ Expr_Kind :: enum { } Expr :: struct { - kind: Expr_Kind, span: source.Span, + integer: i64, + args: []Expr_Id, qualifier: symbol.Id, name: symbol.Id, - integer: i64, - left: int, - right: int, - args: []int, - diagnostic: int, + left: Expr_Id, + right: Expr_Id, + diagnostic: source.Diagnostic_Id, + kind: Expr_Kind, } Param :: struct { @@ -42,7 +96,7 @@ Param :: struct { type: Type_Syntax, } -Stmt_Kind :: enum { +Stmt_Kind :: enum u8 { Invalid, Declaration, Assignment, @@ -56,49 +110,49 @@ Stmt :: struct { name: symbol.Id, type: Type_Syntax, immutable: bool, - expr: int, - diagnostic: int, + expr: Expr_Id, + diagnostic: source.Diagnostic_Id, } Function :: struct { span: source.Span, name: symbol.Id, - pkg: int, - file: int, + pkg: Package_Id, + file: File_Id, c_abi: bool, has_body: bool, params: []Param, result: Type_Syntax, - body: []int, - diagnostic: int, + body: []Stmt_Id, + diagnostic: source.Diagnostic_Id, } Global :: struct { span: source.Span, name: symbol.Id, - pkg: int, - file: int, + pkg: Package_Id, + file: File_Id, type: Type_Syntax, immutable: bool, - expr: int, - diagnostic: int, + expr: Expr_Id, + diagnostic: source.Diagnostic_Id, } Import :: struct { span: source.Span, alias: symbol.Id, path: string, - pkg: int, - file: int, - target: int, + pkg: Package_Id, + file: File_Id, + target: Package_Id, valid: bool, used: bool, - diagnostic: int, + diagnostic: source.Diagnostic_Id, } File :: struct { - source: int, - pkg: int, + source: source.Source_Id, + pkg: Package_Id, } Package :: struct { diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 04e411f..90f11cf 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -11,11 +11,24 @@ 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: int, + template: ast.Function_Id, args: []types.Type, result: types.Type, - hir_id: int, + hir_id: hir.Function_Id, } Infer_Local :: struct { @@ -27,7 +40,7 @@ Build_Local :: struct { name: symbol.Id, type: types.Type, mutable: bool, - id: int, + id: hir.Local_Id, } Constant_Kind :: enum { @@ -42,10 +55,22 @@ Constant :: struct { value: i128, } -Symbol_Index_Entry :: struct { - scope: int, +Function_Index_Entry :: struct { + scope: ast.Package_Id, name: symbol.Id, - id: int, + 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 { @@ -54,14 +79,15 @@ Checker :: struct { symbols: ^symbol.Table, module: hir.Module, specs: [dynamic]Spec, - function_index: []Symbol_Index_Entry, - global_index: []Symbol_Index_Entry, - import_index: []Symbol_Index_Entry, + function_index: []Function_Index_Entry, + global_index: []Global_Index_Entry, + import_index: []Import_Index_Entry, global_types: []types.Type, constants: []Constant, - template_diagnostics: []int, + template_diagnostics: []source.Diagnostic_Id, constant_stack: [dynamic]Constant_Frame, - expr_stack: [dynamic]int, + 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, @@ -75,12 +101,12 @@ symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string { } Constant_Frame :: struct { - expr: int, + expr: ast.Expr_Id, stage: u8, } -eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant { - if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) { +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 @@ -110,7 +136,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant { } if frame.stage == 0 { stack[frame_index].stage = 1 - if expr.left >= 0 && expr.left < len(checker.ast_module.exprs) && + 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}) } @@ -118,7 +144,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant { } if frame.stage == 1 { stack[frame_index].stage = 2 - if expr.right >= 0 && expr.right < len(checker.ast_module.exprs) && + 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}) } @@ -126,10 +152,10 @@ eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant { } left := Constant{kind = .Not_Constant} right := Constant{kind = .Not_Constant} - if expr.left >= 0 && expr.left < len(checker.constants) { + if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) { left = checker.constants[expr.left] } - if expr.right >= 0 && expr.right < len(checker.constants) { + if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) { right = checker.constants[expr.right] } result := Constant{kind = .Not_Constant} @@ -177,7 +203,7 @@ type_from_syntax :: proc(value: ast.Type_Syntax) -> types.Type { return types.INVALID } -symbol_index_less :: proc(left, right: Symbol_Index_Entry) -> bool { +function_index_less :: proc(left, right: Function_Index_Entry) -> bool { if left.scope != right.scope { return left.scope < right.scope } @@ -187,7 +213,27 @@ symbol_index_less :: proc(left, right: Symbol_Index_Entry) -> bool { return left.id < right.id } -find_symbol :: proc(index: []Symbol_Index_Entry, scope: int, name: symbol.Id) -> int { +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 { @@ -202,70 +248,106 @@ find_symbol :: proc(index: []Symbol_Index_Entry, scope: int, name: symbol.Id) -> if low < len(index) && index[low].scope == scope && index[low].name == name { return index[low].id } - return -1 + 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([]Symbol_Index_Entry, len(checker.ast_module.functions), checker.allocator) + 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] = Symbol_Index_Entry{scope=function.pkg, name=function.name, id=id} + checker.function_index[id] = Function_Index_Entry{scope=function.pkg, name=function.name, id=ast.function_id(id)} } - slice.sort_by(checker.function_index, symbol_index_less) + slice.sort_by(checker.function_index, function_index_less) - checker.global_index = make([]Symbol_Index_Entry, len(checker.ast_module.globals), checker.allocator) + 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] = Symbol_Index_Entry{scope=global.pkg, name=global.name, id=id} + checker.global_index[id] = Global_Index_Entry{scope=global.pkg, name=global.name, id=ast.global_id(id)} } - slice.sort_by(checker.global_index, symbol_index_less) + slice.sort_by(checker.global_index, global_index_less) - checker.import_index = make([]Symbol_Index_Entry, len(checker.ast_module.imports), checker.allocator) + 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] = Symbol_Index_Entry{scope=import_item.file, name=import_item.alias, id=id} + 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, symbol_index_less) + slice.sort_by(checker.import_index, import_index_less) } -find_template :: proc(checker: ^Checker, name: symbol.Id, pkg := 0) -> int { - return find_symbol(checker.function_index, pkg, name) +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 := 0) -> int { - return find_symbol(checker.global_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: int, alias: symbol.Id, mark_used := false) -> int { - id := find_symbol(checker.import_index, file, alias) - if id >= 0 && mark_used { +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, file: int, mark_used := false) -> (int, bool) { +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 < 0 { - return -1, false + if import_id == ast.INVALID_IMPORT { + return ast.INVALID_PACKAGE, false } import_item := checker.ast_module.imports[import_id] - if import_item.target < 0 || import_item.target >= len(checker.ast_module.packages) || + 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: int) -> int { - if find_import(checker, file, expr.qualifier) < 0 { +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: int) -> int { - if find_template(checker, expr.name, target_pkg) >= 0 { +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) { @@ -280,8 +362,8 @@ add_name_resolution_diagnostic :: proc(checker: ^Checker, expr: ast.Expr, target 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: int) -> int { - if find_global(checker, expr.name, target_pkg) >= 0 { +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) { @@ -305,17 +387,17 @@ contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool { return false } -mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) { - stack := checker.expr_stack +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.expr_stack = stack + checker.ast_expr_stack = stack } append(&stack, expr_id) for len(stack) > 0 { id := pop(&stack) - if id < 0 || id >= len(checker.ast_module.exprs) { + if id == ast.INVALID_EXPR || int(id) >= len(checker.ast_module.exprs) { continue } expr := checker.ast_module.exprs[id] @@ -428,7 +510,7 @@ find_infer_local :: proc(locals: []Infer_Local, name: symbol.Id) -> types.Type { return types.INVALID } -spec_signature_equal :: proc(spec: Spec, template: int, args: []types.Type) -> bool { +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 } @@ -461,7 +543,7 @@ can_specialize :: proc(function: ast.Function, actual_args: []types.Type) -> boo return true } -ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type) -> int { +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 @@ -475,31 +557,37 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type) for spec, index in checker.specs { if spec_signature_equal(spec, template, signature[:]) { delete(signature) - return index + 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 := len(checker.specs) + index := spec_id(len(checker.specs)) append( &checker.specs, - Spec{template = template, args = signature[:], result = result, hir_id = -1}, + Spec{template = template, args = signature[:], result = result, hir_id = hir.INVALID_FUNCTION}, ) return index } Infer_Frame :: struct { - expr: int, + expr: ast.Expr_Id, stage: u8, left: types.Type, arg_index: int, args: []types.Type, - template: int, + template: ast.Function_Id, } -infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type { +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 { @@ -509,13 +597,13 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : clear_dynamic_array(&stack) checker.infer_stack = stack } - append(&stack, Infer_Frame{expr=expr_id, template=-1}) + 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 < 0 || frame.expr >= len(checker.ast_module.exprs) { + if frame.expr == ast.INVALID_EXPR || int(frame.expr) >= len(checker.ast_module.exprs) { last = types.INVALID _ = pop(&stack) continue @@ -549,7 +637,7 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : target_pkg, available := expr_package(checker, expr, pkg, file) if available { global := find_global(checker, expr.name, target_pkg) - if global >= 0 { + if global != ast.INVALID_GLOBAL { last = checker.global_types[global] } } @@ -557,19 +645,19 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : _ = pop(&stack) case .Add: stack[frame_index].stage = 1 - append(&stack, Infer_Frame{expr=expr.left, template=-1}) + append(&stack, Infer_Frame{expr=expr.left, template=ast.INVALID_FUNCTION}) case .Call: target_pkg, available := expr_package(checker, expr, pkg, file) - template := -1 + template := ast.INVALID_FUNCTION if available { template = find_template(checker, expr.name, target_pkg) } - if template < 0 { + if template == ast.INVALID_FUNCTION { last = types.INVALID _ = pop(&stack) continue } - if checker.template_diagnostics[template] >= 0 { + 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) @@ -579,7 +667,7 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : 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=-1}) + append(&stack, Infer_Frame{expr=expr.args[0], template=ast.INVALID_FUNCTION}) } } continue @@ -587,7 +675,7 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : if frame.stage == 1 { stack[frame_index].left = last stack[frame_index].stage = 2 - append(&stack, Infer_Frame{expr=expr.right, template=-1}) + append(&stack, Infer_Frame{expr=expr.right, template=ast.INVALID_FUNCTION}) continue } if frame.stage == 2 { @@ -600,7 +688,7 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : 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=-1}) + append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=ast.INVALID_FUNCTION}) continue } } @@ -624,8 +712,8 @@ infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg : return last } -infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type { - spec := checker.specs[spec_id] +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 { @@ -657,7 +745,7 @@ infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type { case .Assignment, .Expression: _ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file) case .Return: - if statement.expr >= 0 { + if statement.expr != ast.INVALID_EXPR { returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file) if !types.is_valid(result) { result = returned @@ -698,7 +786,7 @@ infer_all :: proc(checker: ^Checker) { } main_template := find_template(checker, checker.main_symbol, 0) - if main_template >= 0 { + if main_template != ast.INVALID_FUNCTION { ensure_spec(checker, main_template, nil) } @@ -712,9 +800,10 @@ infer_all :: proc(checker: ^Checker) { 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 + 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 @@ -725,8 +814,8 @@ infer_all :: proc(checker: ^Checker) { } } -add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> int { - id := len(checker.module.exprs) +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 } @@ -734,24 +823,33 @@ add_hir_expr :: proc(checker: ^Checker, expr: hir.Expr) -> int { invalid_hir_expr :: proc( checker: ^Checker, span: source.Span, - diagnostic: int, + diagnostic: source.Diagnostic_Id, recovery_type := types.INVALID, -) -> int { +) -> hir.Expr_Id { return add_hir_expr( checker, hir.Expr { kind = .Invalid, span = span, type = recovery_type, - target = -1, - left = -1, - right = -1, + target = hir.INVALID_REF, + left = hir.INVALID_EXPR, + right = hir.INVALID_EXPR, diagnostic = diagnostic, }, ) } -add_unique :: proc(values: ^[dynamic]int, value: int) { +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 @@ -771,11 +869,11 @@ find_build_local :: proc(locals: []Build_Local, name: symbol.Id) -> (Build_Local coerce_expr :: proc( checker: ^Checker, - expr_id: int, + expr_id: hir.Expr_Id, expected: types.Type, span: source.Span, -) -> int { - if expr_id < 0 { +) -> hir.Expr_Id { + if expr_id == hir.INVALID_EXPR { return expr_id } actual := checker.module.exprs[expr_id].type @@ -790,9 +888,9 @@ coerce_expr :: proc( span = span, type = expected, left = expr_id, - target = -1, - right = -1, - diagnostic = -1, + target = hir.INVALID_REF, + right = hir.INVALID_EXPR, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) } @@ -811,7 +909,7 @@ build_constant_expr :: proc( expr: ast.Expr, constant: Constant, expected: types.Type, -) -> int { +) -> hir.Expr_Id { recovery_type := types.I64 if types.is_signed(expected) { recovery_type = expected @@ -847,35 +945,35 @@ build_constant_expr :: proc( span = expr.span, type = result_type, integer = value, - target = -1, - left = -1, - right = -1, - diagnostic = -1, + target = hir.INVALID_REF, + left = hir.INVALID_EXPR, + right = hir.INVALID_EXPR, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) } Build_Expr_Frame :: struct { - expr: int, + expr: ast.Expr_Id, expected: types.Type, stage: u8, - left: int, + left: hir.Expr_Id, arg_index: int, - built_args: []int, + built_args: []hir.Expr_Id, arg_types: []types.Type, - template: int, + template: ast.Function_Id, } build_expr :: proc( checker: ^Checker, - expr_id: int, + expr_id: ast.Expr_Id, locals: []Build_Local, - global_reads: ^[dynamic]int, - calls: ^[dynamic]int, + global_reads: ^[dynamic]hir.Global_Id, + calls: ^[dynamic]hir.Function_Id, expected := types.INVALID, - pkg := 0, - file := 0, -) -> int { + pkg := ast.Package_Id(0), + file := ast.File_Id(0), +) -> hir.Expr_Id { stack := checker.build_stack clear_dynamic_array(&stack) defer { @@ -886,13 +984,13 @@ build_expr :: proc( clear_dynamic_array(&stack) checker.build_stack = stack } - append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=-1}) - last := -1 + 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 < 0 || frame.expr >= len(checker.ast_module.exprs) { + 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) @@ -911,25 +1009,26 @@ build_expr :: proc( last = invalid_hir_expr(checker, expr.span, expr.diagnostic) _ = pop(&stack) case .Name: - last = -1 + 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=local.id, - left=-1, right=-1, diagnostic=-1, + 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 < 0 { + 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 >= 0 { - add_unique(global_reads, global) + } 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=global, left=-1, right=-1, diagnostic=-1, + 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) @@ -939,7 +1038,7 @@ build_expr :: proc( _ = pop(&stack) case .Add: stack[frame_index].stage = 1 - append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=-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 { @@ -949,13 +1048,13 @@ build_expr :: proc( continue } template := find_template(checker, expr.name, target_pkg) - if template < 0 { + 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] >= 0 { + if checker.template_diagnostics[template] != source.INVALID_DIAGNOSTIC { last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template]) _ = pop(&stack) continue @@ -974,7 +1073,7 @@ build_expr :: proc( continue } stack[frame_index].template = template - stack[frame_index].built_args = make([]int, len(expr.args), checker.allocator) + 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 { @@ -982,7 +1081,7 @@ build_expr :: proc( if arg_expected.kind != .Concrete { arg_expected = types.INVALID } - append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=-1}) + append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=ast.INVALID_FUNCTION}) } } continue @@ -990,7 +1089,7 @@ build_expr :: proc( 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=-1}) + append(&stack, Build_Expr_Frame{expr=expr.right, expected=types.INVALID, template=ast.INVALID_FUNCTION}) continue } if frame.stage == 2 { @@ -1005,7 +1104,7 @@ build_expr :: proc( 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=-1, diagnostic=-1, + target = hir.INVALID_REF, diagnostic = source.INVALID_DIAGNOSTIC, }) } _ = pop(&stack) @@ -1022,7 +1121,7 @@ build_expr :: proc( if arg_expected.kind != .Concrete { arg_expected = types.INVALID } - append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=-1}) + append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=ast.INVALID_FUNCTION}) continue } } @@ -1037,7 +1136,9 @@ build_expr :: proc( checker.module.exprs[stack[frame_index].built_args[index]].span, ) } - add_unique(calls, spec) + 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( @@ -1051,8 +1152,8 @@ build_expr :: proc( last = invalid_hir_expr(checker, expr.span, id) } else { last = add_hir_expr(checker, hir.Expr{ - kind=.Call, span=expr.span, type=result, target=spec, - left=-1, right=-1, args=stack[frame_index].built_args, diagnostic=-1, + 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 } @@ -1062,8 +1163,8 @@ build_expr :: proc( return last } -make_link_name :: proc(checker: ^Checker, spec_id: int) -> string { - spec := checker.specs[spec_id] +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) @@ -1083,15 +1184,12 @@ make_link_name :: proc(checker: ^Checker, spec_id: int) -> string { 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] +build_function :: proc(checker: ^Checker, id: Spec_Id) { + spec := checker.specs[id] function := checker.ast_module.functions[spec.template] - signature_diagnostic := -1 + signature_diagnostic := source.INVALID_DIAGNOSTIC if spec.result.kind != .Void && !types.is_concrete_integer(spec.result) { - checker.specs[spec_id].result = types.I64 + checker.specs[id].result = types.I64 spec.result = types.I64 signature_diagnostic = source.addf( checker.diagnostics, @@ -1111,24 +1209,23 @@ build_function :: proc(checker: ^Checker, spec_id: int) { break } } - hir_id := len(checker.module.functions) - checker.specs[spec_id].hir_id = hir_id + 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]int + params: [dynamic]hir.Local_Id params.allocator = checker.allocator - body: [dynamic]int + body: [dynamic]hir.Stmt_Id body.allocator = checker.allocator - global_reads: [dynamic]int + global_reads: [dynamic]hir.Global_Id global_reads.allocator = checker.allocator - calls: [dynamic]int + calls: [dynamic]hir.Function_Id calls.allocator = checker.allocator for param, index in function.params { - local_id := len(hir_locals) + local_id := hir.local_id(len(hir_locals)) param_type := types.INVALID if index < len(spec.args) { param_type = spec.args[index] @@ -1138,13 +1235,15 @@ build_function :: proc(checker: ^Checker, spec_id: int) { append(¶ms, local_id) } - problematic := signature_diagnostic >= 0 || checker.template_diagnostics[spec.template] >= 0 + 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, spec_id), + 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, @@ -1164,15 +1263,15 @@ build_function :: proc(checker: ^Checker, spec_id: int) { } has_return := false - if signature_diagnostic >= 0 { - append(&body, len(checker.module.statements)) + 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 = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = signature_diagnostic, }, ) @@ -1216,21 +1315,21 @@ build_function :: proc(checker: ^Checker, spec_id: int) { "duplicate local '%s'", symbol_text(checker, statement.name), ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) problematic = true continue } - local_id := len(hir_locals) + local_id := hir.local_id(len(hir_locals)) append( &hir_locals, hir.Local { @@ -1248,7 +1347,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) { id = local_id, }, ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { @@ -1256,7 +1355,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) { span = statement.span, local = local_id, expr = value, - diagnostic = -1, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) problematic = problematic || checker.module.exprs[value].kind == .Invalid @@ -1269,28 +1368,28 @@ build_function :: proc(checker: ^Checker, spec_id: int) { statement.span, "cannot assign a void expression to '_'", ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) problematic = true } else { - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Sink, span = statement.span, expr = value, - local = -1, - diagnostic = -1, + local = hir.INVALID_LOCAL, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) } @@ -1304,14 +1403,14 @@ build_function :: proc(checker: ^Checker, spec_id: int) { "cannot assign unresolved local '%s'", symbol_text(checker, statement.name), ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) @@ -1325,14 +1424,14 @@ build_function :: proc(checker: ^Checker, spec_id: int) { "cannot assign immutable local '%s'", symbol_text(checker, statement.name), ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) @@ -1350,7 +1449,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) { function.file, ) value = coerce_expr(checker, value, local.type, statement.span) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { @@ -1358,41 +1457,41 @@ build_function :: proc(checker: ^Checker, spec_id: int) { span = statement.span, expr = value, local = local.id, - diagnostic = -1, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) problematic = problematic || checker.module.exprs[value].kind == .Invalid case .Return: has_return = true - if statement.expr < 0 { + 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, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) problematic = true } else { - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Return, span = statement.span, - expr = -1, - local = -1, - diagnostic = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) } @@ -1404,14 +1503,14 @@ build_function :: proc(checker: ^Checker, spec_id: int) { statement.span, "void function cannot return a value", ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) @@ -1429,15 +1528,15 @@ build_function :: proc(checker: ^Checker, spec_id: int) { function.file, ) value = coerce_expr(checker, value, spec.result, statement.span) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Return, span = statement.span, expr = value, - local = -1, - diagnostic = -1, + local = hir.INVALID_LOCAL, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) problematic = problematic || checker.module.exprs[value].kind == .Invalid @@ -1449,40 +1548,40 @@ build_function :: proc(checker: ^Checker, spec_id: int) { statement.span, "non-void expression result must be consumed or assigned to '_'", ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = id, }, ) problematic = true } else { - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Expression, span = statement.span, expr = value, - local = -1, - diagnostic = -1, + local = hir.INVALID_LOCAL, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) } case .Invalid: - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = statement.span, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic, }, ) @@ -1497,19 +1596,20 @@ build_function :: proc(checker: ^Checker, spec_id: int) { "function '%s' does not return a value", symbol_text(checker, function.name), ) - append(&body, len(checker.module.statements)) + append(&body, hir.stmt_id(len(checker.module.statements))) append( &checker.module.statements, - hir.Stmt{kind = .Trap, span = function.span, expr = -1, local = -1, diagnostic = id}, + 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, spec_id), + 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, @@ -1521,34 +1621,34 @@ build_function :: proc(checker: ^Checker, spec_id: int) { direct_global_reads = global_reads, calls = calls[:], problematic = problematic, - diagnostic = -1, + diagnostic = source.INVALID_DIAGNOSTIC, }, ) delete(locals) } -expr_problematic :: proc(checker: ^Checker, expr_id: int) -> bool { +expr_problematic :: proc(checker: ^Checker, expr_id: hir.Expr_Id) -> bool { module := &checker.module - stack := checker.expr_stack + stack := checker.hir_expr_stack clear_dynamic_array(&stack) defer { clear_dynamic_array(&stack) - checker.expr_stack = stack + checker.hir_expr_stack = stack } append(&stack, expr_id) for len(stack) > 0 { id := pop(&stack) - if id < 0 || id >= len(module.exprs) { + 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 >= 0 { + if expr.left != hir.INVALID_EXPR { append(&stack, expr.left) } - if expr.right >= 0 { + if expr.right != hir.INVALID_EXPR { append(&stack, expr.right) } append(&stack, ..expr.args) @@ -1557,10 +1657,10 @@ expr_problematic :: proc(checker: ^Checker, expr_id: int) -> bool { } build_globals :: proc(checker: ^Checker) { - for global, global_id in checker.ast_module.globals { - dependencies: [dynamic]int + for global, global_index in checker.ast_module.globals { + dependencies: [dynamic]hir.Global_Id dependencies.allocator = checker.allocator - calls: [dynamic]int + calls: [dynamic]hir.Function_Id calls.allocator = checker.allocator declared := type_from_syntax(global.type) expected := types.INVALID @@ -1568,14 +1668,14 @@ build_globals :: proc(checker: ^Checker) { expected = declared } expr := build_expr(checker, global.expr, nil, &dependencies, &calls, expected, global.pkg, global.file) - global_type := checker.global_types[global_id] + 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 := -1 + diagnostic := source.INVALID_DIAGNOSTIC if !types.is_concrete_integer(global_type) { diagnostic = source.addf( checker.diagnostics, @@ -1602,11 +1702,12 @@ build_globals :: proc(checker: ^Checker) { ) expr = invalid_hir_expr(checker, global.span, diagnostic) } - is_static := checker.module.exprs[expr].kind == .Integer && diagnostic < 0 + 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 { @@ -1634,13 +1735,11 @@ propagate_problems :: proc(checker: ^Checker) { 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 - } + if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) && + checker.module.functions[call].problematic { + function.problematic = true + changed = true + break } } } @@ -1649,8 +1748,8 @@ propagate_problems :: proc(checker: ^Checker) { continue } for dependency in global.dependencies { - if dependency >= 0 && - dependency < len(checker.module.globals) && + if dependency != hir.INVALID_GLOBAL && + int(dependency) < len(checker.module.globals) && checker.module.globals[dependency].problematic { global.problematic = true changed = true @@ -1661,28 +1760,18 @@ propagate_problems :: proc(checker: ^Checker) { 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 - } + if call != hir.INVALID_FUNCTION && int(call) < len(checker.module.functions) && + checker.module.functions[call].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: ^[dynamic]int, value: int) -> bool { +append_unique_global :: proc(values: ^[dynamic]hir.Global_Id, value: hir.Global_Id) -> bool { for existing in values^ { if existing == value { return false @@ -1698,15 +1787,11 @@ propagate_global_reads :: proc(checker: ^Checker) { changed = false for &function in checker.module.functions { for call in function.calls { - if call < 0 || call >= len(checker.specs) { + if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) { 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) { + for global_id in checker.module.functions[call].direct_global_reads { + if append_unique_global(&function.direct_global_reads, global_id) { changed = true } } @@ -1715,26 +1800,22 @@ propagate_global_reads :: proc(checker: ^Checker) { } for &global in checker.module.globals { for call in global.calls { - if call < 0 || call >= len(checker.specs) { + if call == hir.INVALID_FUNCTION || int(call) >= len(checker.module.functions) { 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) + for dependency in checker.module.functions[call].direct_global_reads { + _ = append_unique_global(&global.dependencies, dependency) } } } } Cycle_Frame :: struct { - global: int, + global: hir.Global_Id, next_dependency: int, } -detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []u8) { +detect_global_cycles_visit :: proc(checker: ^Checker, global_id: hir.Global_Id, states: []u8) { if states[global_id] == 2 { return } @@ -1762,7 +1843,7 @@ detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: [] } dependency := dependencies[frame.next_dependency] frame.next_dependency += 1 - if dependency < 0 || dependency >= len(states) { + if dependency == hir.INVALID_GLOBAL || int(dependency) >= len(states) { continue } if states[dependency] == 1 { @@ -1789,12 +1870,13 @@ detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: [] synthesize_trap_main :: proc(checker: ^Checker) { id := source.add(checker.diagnostics, source.Span{}, "missing or unusable main function") - statement_id := len(checker.module.statements) + 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 = -1, local = -1, diagnostic = id}, + hir.Stmt{kind = .Trap, span = source.Span{}, expr = hir.INVALID_EXPR, local = hir.INVALID_LOCAL, diagnostic = id}, ) - body := make([]int, 1, checker.allocator) + body := make([]hir.Stmt_Id, 1, checker.allocator) body[0] = statement_id append( &checker.module.functions, @@ -1813,7 +1895,7 @@ synthesize_trap_main :: proc(checker: ^Checker) { ) } -replace_main_with_trap :: proc(checker: ^Checker, diagnostic: int) { +replace_main_with_trap :: proc(checker: ^Checker, diagnostic: source.Diagnostic_Id) { for &function in checker.module.functions { if !function.is_main { continue @@ -1827,18 +1909,18 @@ replace_main_with_trap :: proc(checker: ^Checker, diagnostic: int) { function.linkage = .External function.problematic = true function.diagnostic = diagnostic - statement_id := len(checker.module.statements) + statement_id := hir.stmt_id(len(checker.module.statements)) append( &checker.module.statements, hir.Stmt { kind = .Trap, span = source.Span{}, - expr = -1, - local = -1, + expr = hir.INVALID_EXPR, + local = hir.INVALID_LOCAL, diagnostic = diagnostic, }, ) - function.body = make([]int, 1, checker.allocator) + function.body = make([]hir.Stmt_Id, 1, checker.allocator) function.body[0] = statement_id return } @@ -1862,16 +1944,17 @@ check :: proc( } checker.specs.allocator = allocator checker.constant_stack.allocator = allocator - checker.expr_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([]int, len(ast_module.functions), allocator) + checker.template_diagnostics = make([]source.Diagnostic_Id, len(ast_module.functions), allocator) for &diagnostic in checker.template_diagnostics { - diagnostic = -1 + diagnostic = source.INVALID_DIAGNOSTIC } defer { for spec in checker.specs { @@ -1885,7 +1968,8 @@ check :: proc( delete(checker.constants, allocator) delete(checker.template_diagnostics, allocator) delete(checker.constant_stack) - delete(checker.expr_stack) + delete(checker.ast_expr_stack) + delete(checker.hir_expr_stack) delete(checker.infer_stack) delete(checker.build_stack) delete(checker.cycle_stack) @@ -1913,11 +1997,13 @@ check :: proc( 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) + for index in 0.. Expr_Id { + assert(index >= 0 && u64(index) < u64(INVALID_EXPR)) + return Expr_Id(index) +} + +stmt_id :: proc(index: int) -> Stmt_Id { + assert(index >= 0 && u64(index) < u64(INVALID_STMT)) + return Stmt_Id(index) +} + +local_id :: proc(index: int) -> Local_Id { + assert(index >= 0 && u64(index) < u64(INVALID_LOCAL)) + return Local_Id(index) +} + +function_id :: proc(index: int) -> Function_Id { + assert(index >= 0 && u64(index) < u64(INVALID_FUNCTION)) + return Function_Id(index) +} + +global_id :: proc(index: int) -> Global_Id { + assert(index >= 0 && u64(index) < u64(INVALID_GLOBAL)) + return Global_Id(index) +} + +index :: proc(id: $T, invalid: T, count: int) -> (int, bool) { + value := int(id) + return value, id != invalid && value < count +} + +local_ref :: proc(id: Local_Id) -> Ref {return Ref(id)} +function_ref :: proc(id: Function_Id) -> Ref {return Ref(id)} +global_ref :: proc(id: Global_Id) -> Ref {return Ref(id)} +as_local :: proc(ref: Ref) -> Local_Id {return Local_Id(ref)} +as_function :: proc(ref: Ref) -> Function_Id {return Function_Id(ref)} +as_global :: proc(ref: Ref) -> Global_Id {return Global_Id(ref)} + +Calling_Convention :: enum u8 { Brolang, C, } -Implementation :: enum { +Implementation :: enum u8 { Definition, Declaration, } -Linkage :: enum { +Linkage :: enum u8 { Internal, External, } -Expr_Kind :: enum { +Expr_Kind :: enum u8 { Invalid, Integer, Local, @@ -33,15 +82,15 @@ Expr_Kind :: enum { } Expr :: struct { - kind: Expr_Kind, span: source.Span, type: types.Type, integer: i64, - target: int, - left: int, - right: int, - args: []int, - diagnostic: int, + args: []Expr_Id, + target: Ref, + left: Expr_Id, + right: Expr_Id, + diagnostic: source.Diagnostic_Id, + kind: Expr_Kind, } Local :: struct { @@ -51,7 +100,7 @@ Local :: struct { parameter: bool, } -Stmt_Kind :: enum { +Stmt_Kind :: enum u8 { Declaration, Assignment, Return, @@ -63,9 +112,9 @@ Stmt_Kind :: enum { Stmt :: struct { kind: Stmt_Kind, span: source.Span, - local: int, - expr: int, - diagnostic: int, + local: Local_Id, + expr: Expr_Id, + diagnostic: source.Diagnostic_Id, } Function :: struct { @@ -75,27 +124,27 @@ Function :: struct { implementation: Implementation, linkage: Linkage, is_main: bool, - params: []int, + params: []Local_Id, result: types.Type, locals: []Local, - body: []int, - direct_global_reads: [dynamic]int, - calls: []int, + body: []Stmt_Id, + direct_global_reads: [dynamic]Global_Id, + calls: []Function_Id, problematic: bool, - diagnostic: int, + diagnostic: source.Diagnostic_Id, } Global :: struct { name: symbol.Id, type: types.Type, - expr: int, + expr: Expr_Id, static_value: i64, is_static: bool, - dependencies: [dynamic]int, - calls: []int, + dependencies: [dynamic]Global_Id, + calls: []Function_Id, direct_problem: bool, problematic: bool, - diagnostic: int, + diagnostic: source.Diagnostic_Id, } Module :: struct { diff --git a/compiler/ir/ir.odin b/compiler/ir/ir.odin index a05b921..0f39ad4 100644 --- a/compiler/ir/ir.odin +++ b/compiler/ir/ir.odin @@ -5,24 +5,66 @@ import "../symbol" import "../types" import "core:mem" -INVALID_ID :: -1 +Instruction_Id :: distinct u32 +Local_Id :: distinct u32 +Function_Id :: distinct u32 +Global_Id :: distinct u32 +Ref :: distinct u32 -Calling_Convention :: enum { +INVALID_INSTRUCTION :: Instruction_Id(0xffff_ffff) +INVALID_LOCAL :: Local_Id(0xffff_ffff) +INVALID_FUNCTION :: Function_Id(0xffff_ffff) +INVALID_GLOBAL :: Global_Id(0xffff_ffff) +INVALID_REF :: Ref(0xffff_ffff) + +instruction_id :: proc(index: int) -> Instruction_Id { + assert(index >= 0 && u64(index) < u64(INVALID_INSTRUCTION)) + return Instruction_Id(index) +} + +local_id :: proc(index: int) -> Local_Id { + assert(index >= 0 && u64(index) < u64(INVALID_LOCAL)) + return Local_Id(index) +} + +function_id :: proc(index: int) -> Function_Id { + assert(index >= 0 && u64(index) < u64(INVALID_FUNCTION)) + return Function_Id(index) +} + +global_id :: proc(index: int) -> Global_Id { + assert(index >= 0 && u64(index) < u64(INVALID_GLOBAL)) + return Global_Id(index) +} + +index :: proc(id: $T, invalid: T, count: int) -> (int, bool) { + value := int(id) + return value, id != invalid && value < count +} + +local_ref :: proc(id: Local_Id) -> Ref {return Ref(id)} +function_ref :: proc(id: Function_Id) -> Ref {return Ref(id)} +global_ref :: proc(id: Global_Id) -> Ref {return Ref(id)} +as_local :: proc(ref: Ref) -> Local_Id {return Local_Id(ref)} +as_function :: proc(ref: Ref) -> Function_Id {return Function_Id(ref)} +as_global :: proc(ref: Ref) -> Global_Id {return Global_Id(ref)} + +Calling_Convention :: enum u8 { Brolang, C, } -Implementation :: enum { +Implementation :: enum u8 { Definition, Declaration, } -Linkage :: enum { +Linkage :: enum u8 { Internal, External, } -Opcode :: enum { +Opcode :: enum u8 { Param, Const, Load_Global, @@ -38,15 +80,15 @@ Opcode :: enum { } Instruction :: struct { - op: Opcode, span: source.Span, type: types.Type, integer: i64, - target: int, - a: int, - b: int, - args: []int, - diagnostic: int, + args: []Instruction_Id, + target: Ref, + a: Instruction_Id, + b: Instruction_Id, + diagnostic: source.Diagnostic_Id, + op: Opcode, } Function :: struct { @@ -68,7 +110,7 @@ Global :: struct { static_value: i64, initializer: []Instruction, problematic: bool, - diagnostic: int, + diagnostic: source.Diagnostic_Id, } Module :: struct { diff --git a/compiler/lexer/lexer.odin b/compiler/lexer/lexer.odin index 6f8d35d..6f2b564 100644 --- a/compiler/lexer/lexer.odin +++ b/compiler/lexer/lexer.odin @@ -34,11 +34,11 @@ append_token :: proc( kind: token.Kind, start, end: int, id := symbol.INVALID, - diagnostic := -1, + diagnostic := source.INVALID_DIAGNOSTIC, ) { append(&stream.items, token.Token{ kind=kind, - span=source.Span{file=source_file.id, start=start, end=end}, + span=source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(end)}, symbol=id, diagnostic=diagnostic, }) @@ -74,7 +74,7 @@ lex :: proc( cursor += 1 append_token(&stream, source_file, .Colon_Colon, start, cursor) } else { - id := source.add(diagnostics, source.Span{file=source_file.id, start=start, end=cursor}, "expected a second ':'") + id := source.add(diagnostics, source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(cursor)}, "expected a second ':'") append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id) } case '=': @@ -111,7 +111,7 @@ lex :: proc( if cursor >= len(bytes) || (bytes[cursor] != '\\' && bytes[cursor] != '"') { source.add( diagnostics, - source.Span{file=source_file.id, start=max(cursor-1, start), end=min(cursor+1, len(bytes))}, + source.Span{file=source_file.id, start=source.Offset(max(cursor-1, start)), end=source.Offset(min(cursor+1, len(bytes)))}, "import strings only support '\\\\' and '\\\"' escapes", ) valid = false @@ -127,7 +127,7 @@ lex :: proc( } else { id := source.add( diagnostics, - source.Span{file=source_file.id, start=start, end=cursor}, + source.Span{file=source_file.id, start=source.Offset(start), end=source.Offset(cursor)}, "unterminated import string", ) append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id) @@ -135,7 +135,7 @@ lex :: proc( case ';': id := source.add( diagnostics, - source.Span{file=source_file.id, start=cursor, end=cursor+1}, + source.Span{file=source_file.id, start=source.Offset(cursor), end=source.Offset(cursor+1)}, "semicolons are invalid; terminate statements with a newline", ) append_token(&stream, source_file, .Invalid, cursor, cursor+1, diagnostic=id) @@ -162,7 +162,7 @@ lex :: proc( } else { id := source.addf( diagnostics, - source.Span{file=source_file.id, start=cursor, end=cursor+1}, + source.Span{file=source_file.id, start=source.Offset(cursor), end=source.Offset(cursor+1)}, "invalid source byte 0x%02x", value, ) diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin index 6192ab4..6400600 100644 --- a/compiler/llvm/llvm.odin +++ b/compiler/llvm/llvm.odin @@ -49,11 +49,11 @@ sentinel :: proc(value_type: types.Type) -> i64 { } } -valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: int) -> bool { - return instruction_id >= 0 && instruction_id < len(instructions) +valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Instruction_Id) -> bool { + return instruction_id != ir.INVALID_INSTRUCTION && int(instruction_id) < len(instructions) } -valid_value :: proc(instructions: []ir.Instruction, value_id: int, expected: types.Type) -> bool { +valid_value :: proc(instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) -> bool { if !valid_instruction(instructions, value_id) || !types.is_concrete_integer(expected) || !types.equal(instructions[value_id].type, expected) { @@ -68,7 +68,7 @@ valid_value :: proc(instructions: []ir.Instruction, value_id: int, expected: typ return false } -write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int, expected: types.Type) { +write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) { if !valid_value(instructions, value_id, expected) { fmt.sbprintf(builder, "%d", sentinel(expected)) return @@ -88,8 +88,8 @@ register_message :: proc(emitter: ^Emitter, text: string) -> int { return id } -diagnostic_message :: proc(emitter: ^Emitter, diagnostic: int, span: source.Span, fallback: string) -> int { - if diagnostic >= 0 && diagnostic < len(emitter.diagnostics.items) { +diagnostic_message :: proc(emitter: ^Emitter, diagnostic: source.Diagnostic_Id, span: source.Span, fallback: string) -> int { + if _, ok := source.diagnostic_index(diagnostic, len(emitter.diagnostics.items)); ok { message := source.format(emitter.diagnostics, diagnostic, emitter.allocator) id := register_message(emitter, message) delete(message, emitter.allocator) @@ -137,7 +137,7 @@ emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: } } -emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []int, param_types: []types.Type) { +emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []ir.Instruction_Id, param_types: []types.Type) { for arg, index in args { if index > 0 { strings.write_string(builder, ", ") @@ -152,60 +152,62 @@ emit_instruction_stream :: proc( instructions: []ir.Instruction, function: ir.Function, global_initializer := false, -) -> int { - return_value := -1 +) -> ir.Instruction_Id { + return_value := ir.INVALID_INSTRUCTION after_return := false - for instruction, instruction_id in instructions { + for instruction, instruction_index in instructions { + instruction_id := ir.instruction_id(instruction_index) if after_return { - fmt.sbprintf(&emitter.builder, "recover_after_return_%d:\n", instruction_id) + fmt.sbprintf(&emitter.builder, "recover_after_return_%d:\n", instruction_index) after_return = false } switch instruction.op { case .Param, .Const: case .Load_Global: - if instruction.target < 0 || instruction.target >= len(emitter.module.globals) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference") + global_id := ir.as_global(instruction.target) + if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference") continue } - global := emitter.module.globals[instruction.target] + global := emitter.module.globals[global_id] if !types.equal(instruction.type, global.type) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference type") + emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference type") continue } if global.is_static { fmt.sbprintf( &emitter.builder, " %%v%d = load %s, ptr @bro.g.%d\n", - instruction_id, + instruction_index, llvm_type(global.type), - instruction.target, + global_id, ) } else { fmt.sbprintf( &emitter.builder, " %%v%d = call %s @bro.get.%d()\n", - instruction_id, + instruction_index, llvm_type(global.type), - instruction.target, + global_id, ) } case .Alloca: if !types.is_concrete_integer(instruction.type) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid allocation type") + emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type") continue } - fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_id, llvm_type(instruction.type)) + fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type)) case .Load: if !valid_instruction(instructions, instruction.a) || instructions[instruction.a].op != .Alloca || !types.equal(instructions[instruction.a].type, instruction.type) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid load slot") + emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot") continue } fmt.sbprintf( &emitter.builder, " %%v%d = load %s, ptr %%v%d\n", - instruction_id, + instruction_index, llvm_type(instruction.type), instruction.a, ) @@ -214,7 +216,7 @@ emit_instruction_stream :: proc( instructions[instruction.a].op != .Alloca || !types.equal(instructions[instruction.a].type, instruction.type) || !valid_value(instructions, instruction.b, instruction.type) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid store operand") + emit_recovery_value(emitter, instruction_index, instruction, "invalid store operand") continue } fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type)) @@ -225,50 +227,51 @@ emit_instruction_stream :: proc( !types.is_concrete_integer(instructions[instruction.a].type) || !types.is_concrete_integer(instruction.type) || instructions[instruction.a].type.bits >= instruction.type.bits { - emit_recovery_value(emitter, instruction_id, instruction, "invalid widening operand") + emit_recovery_value(emitter, instruction_index, instruction, "invalid widening operand") continue } from_type := instructions[instruction.a].type - fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_id, llvm_type(from_type)) + fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_index, llvm_type(from_type)) write_operand(&emitter.builder, instructions, instruction.a, from_type) fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type)) case .Add_Checked: if !valid_value(instructions, instruction.a, instruction.type) || !valid_value(instructions, instruction.b, instruction.type) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid addition operand") + emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand") continue } type_name := llvm_type(instruction.type) - fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_id) + fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type) fmt.sbprintf(&emitter.builder, ", %s ", type_name) write_operand(&emitter.builder, instructions, instruction.b, instruction.type) fmt.sbprintf(&emitter.builder, ")\n") - fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_id) + fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") - fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_id) - fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_id) + fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_index) + fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") - fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_id) + fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_index) fmt.sbprintf( &emitter.builder, " br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n", - instruction_id, - instruction_id, - instruction_id, + instruction_index, + instruction_index, + instruction_index, ) - fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_id) - message := diagnostic_message(emitter, -1, instruction.span, "signed integer addition overflow") + fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index) + message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer addition overflow") emit_trap_call(emitter, message) - fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_id) + fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) case .Call: - if instruction.target < 0 || instruction.target >= len(emitter.module.functions) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid function specialization") + function_id := ir.as_function(instruction.target) + if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid function specialization") continue } - target := emitter.module.functions[instruction.target] + target := emitter.module.functions[function_id] valid_args := len(instruction.args) == len(target.param_types) if valid_args { for arg, index in instruction.args { @@ -283,11 +286,11 @@ emit_instruction_stream :: proc( target_result = types.I32 } if !valid_args || !types.equal(instruction.type, target_result) { - emit_recovery_value(emitter, instruction_id, instruction, "invalid function call operands") + emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands") continue } if instruction.type.kind != .Void { - fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_id) + fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) } else { strings.write_string(&emitter.builder, " ") } diff --git a/compiler/loader/loader.odin b/compiler/loader/loader.odin index d4c69f8..92e1dfb 100644 --- a/compiler/loader/loader.odin +++ b/compiler/loader/loader.odin @@ -39,20 +39,20 @@ is_identifier :: proc(value: string) -> bool { return true } -find_package :: proc(state: ^State, path: string) -> int { +find_package :: proc(state: ^State, path: string) -> ast.Package_Id { for pkg, id in state.module.packages { if pkg.path == path { - return id + return ast.package_id(id) } } - return -1 + return ast.INVALID_PACKAGE } -add_placeholder :: proc(state: ^State, path: string) -> int { - if existing := find_package(state, path); existing >= 0 { +add_placeholder :: proc(state: ^State, path: string) -> ast.Package_Id { + if existing := find_package(state, path); existing != ast.INVALID_PACKAGE { return existing } - id := len(state.module.packages) + id := ast.package_id(len(state.module.packages)) append(&state.module.packages, ast.Package{ path=strings.clone(path, state.allocator), name=symbol.intern(state.symbols, filepath.base(path)), @@ -103,7 +103,7 @@ resolve_import_path :: proc(state: ^State, importing_path, import_path: string) return joined, false } -load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> int { +load_package :: proc(state: ^State, path: string, import_span: source.Span, is_root := false) -> ast.Package_Id { canonical, ok := filepath.abs(path, state.allocator) if !ok || !os.is_dir(path) { if is_root { @@ -111,7 +111,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r if len(canonical) > 0 { delete(canonical, state.allocator) } - return -1 + return ast.INVALID_PACKAGE } placeholder := path if len(canonical) > 0 { @@ -124,12 +124,12 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r } return id } - if existing := find_package(state, canonical); existing >= 0 { + if existing := find_package(state, canonical); existing != ast.INVALID_PACKAGE { delete(canonical, state.allocator) return existing } - pkg_id := len(state.module.packages) + pkg_id := ast.package_id(len(state.module.packages)) append(&state.module.packages, ast.Package{ path=canonical, name=symbol.intern(state.symbols, filepath.base(canonical)), @@ -152,13 +152,24 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r } for file_info in files { + if file_info.size < 0 || !source.fits_source_length(u64(file_info.size)) { + source.addf(state.diagnostics, import_span, "source file '%s' exceeds the 4 GiB source limit", file_info.fullpath) + state.root_failed = true + continue + } bytes, read_ok := os.read_entire_file(file_info.fullpath, state.sources.allocator) if !read_ok { state.root_failed = true continue } + if !source.fits_source_length(u64(len(bytes))) { + source.addf(state.diagnostics, import_span, "source file '%s' exceeds the 4 GiB source limit", file_info.fullpath) + delete(bytes, state.sources.allocator) + state.root_failed = true + continue + } source_id := source.add_source_owned(state.sources, file_info.fullpath, bytes) - file_id := len(state.module.files) + file_id := ast.file_id(len(state.module.files)) append(&state.module.files, ast.File{source=source_id, pkg=pkg_id}) stream := lexer.lex(&state.sources.items[source_id], state.diagnostics, state.symbols, state.token_allocator) parser.parse_into(&stream, &state.sources.items[source_id], state.diagnostics, state.module, pkg_id, file_id) @@ -169,7 +180,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r import_count := len(state.module.imports) for import_id in 0..= 0 { + if import_item.pkg != pkg_id || import_item.target != ast.INVALID_PACKAGE { continue } if filepath.is_abs(import_item.path) { @@ -181,7 +192,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r target_path, target_ok := resolve_import_path(state, canonical, import_item.path) target := load_package(state, target_path, import_item.span) state.module.imports[import_id].target = target - if !target_ok || target < 0 || !state.module.packages[target].available { + if !target_ok || target == ast.INVALID_PACKAGE || !state.module.packages[target].available { state.module.imports[import_id].valid = false } delete(target_path, state.allocator) @@ -189,7 +200,7 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r return pkg_id } -declaration_conflicts :: proc(module: ^ast.Module, pkg: int, name: symbol.Id) -> bool { +declaration_conflicts :: proc(module: ^ast.Module, pkg: ast.Package_Id, name: symbol.Id) -> bool { for function in module.functions { if function.pkg == pkg && function.name == name { return true @@ -205,7 +216,7 @@ declaration_conflicts :: proc(module: ^ast.Module, pkg: int, name: symbol.Id) -> validate_imports :: proc(state: ^State) { for import_item, import_id in state.module.imports { - if !symbol.is_valid(import_item.alias) && import_item.target >= 0 { + if !symbol.is_valid(import_item.alias) && import_item.target != ast.INVALID_PACKAGE { state.module.imports[import_id].alias = state.module.packages[import_item.target].name } alias := state.module.imports[import_id].alias @@ -260,7 +271,7 @@ load :: proc( allocator=allocator, } root := load_package(&state, root_path, source.Span{}, true) - if root != 0 && root >= 0 { + if root != ast.Package_Id(0) && root != ast.INVALID_PACKAGE { state.root_failed = true } validate_imports(&state) diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index bc41aa6..2022e86 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -10,20 +10,20 @@ import "core:mem" State :: struct { hir_module: ^hir.Module, instructions: [dynamic]ir.Instruction, - local_values: []int, - local_slots: []int, + local_values: []ir.Instruction_Id, + local_slots: []ir.Instruction_Id, expr_stack: [dynamic]Lower_Expr_Frame, allocator: mem.Allocator, } -append_instruction :: proc(state: ^State, instruction: ir.Instruction) -> int { - id := len(state.instructions) +append_instruction :: proc(state: ^State, instruction: ir.Instruction) -> ir.Instruction_Id { + id := ir.instruction_id(len(state.instructions)) append(&state.instructions, instruction) return id } -clone_args :: proc(values: []int, allocator: mem.Allocator) -> []int { - result := make([]int, len(values), allocator) +clone_args :: proc(values: []ir.Instruction_Id, allocator: mem.Allocator) -> []ir.Instruction_Id { + result := make([]ir.Instruction_Id, len(values), allocator) copy(result, values) return result } @@ -37,14 +37,19 @@ sentinel :: proc(value_type: types.Type) -> i64 { } } -append_recovery_value :: proc(state: ^State, span: source.Span, value_type: types.Type, diagnostic := -1) -> int { +append_recovery_value :: proc( + state: ^State, + span: source.Span, + value_type: types.Type, + diagnostic := source.INVALID_DIAGNOSTIC, +) -> ir.Instruction_Id { append_instruction(state, ir.Instruction{ op=.Trap, span=span, type=types.VOID, - target=-1, - a=-1, - b=-1, + target=ir.INVALID_REF, + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, diagnostic=diagnostic, }) fallback := value_type @@ -56,22 +61,22 @@ append_recovery_value :: proc(state: ^State, span: source.Span, value_type: type span=span, type=fallback, integer=sentinel(fallback), - target=-1, - a=-1, - b=-1, - diagnostic=-1, + target=ir.INVALID_REF, + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) } Lower_Expr_Frame :: struct { - expr: int, + expr: hir.Expr_Id, stage: u8, - left: int, + left: ir.Instruction_Id, arg_index: int, - args: []int, + args: []ir.Instruction_Id, } -lower_expr :: proc(state: ^State, expr_id: int) -> int { +lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { stack := state.expr_stack clear_dynamic_array(&stack) defer { @@ -82,11 +87,11 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int { state.expr_stack = stack } append(&stack, Lower_Expr_Frame{expr=expr_id}) - last := -1 + last := ir.INVALID_INSTRUCTION for len(stack) > 0 { frame_index := len(stack)-1 frame := stack[frame_index] - if frame.expr < 0 || frame.expr >= len(state.hir_module.exprs) { + if frame.expr == hir.INVALID_EXPR || int(frame.expr) >= len(state.hir_module.exprs) { last = append_recovery_value(state, source.Span{}, types.I64) _ = pop(&stack) continue @@ -100,31 +105,33 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int { case .Integer: last = append_instruction(state, ir.Instruction{ op=.Const, span=expr.span, type=expr.type, integer=expr.integer, - target=-1, a=-1, b=-1, diagnostic=-1, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) _ = pop(&stack) case .Local: - last = -1 - if expr.target >= 0 && expr.target < len(state.local_slots) && state.local_slots[expr.target] >= 0 { + last = ir.INVALID_INSTRUCTION + local := hir.as_local(expr.target) + if local != hir.INVALID_LOCAL && int(local) < len(state.local_slots) && state.local_slots[local] != ir.INVALID_INSTRUCTION { last = append_instruction(state, ir.Instruction{ - op=.Load, span=expr.span, type=expr.type, target=-1, - a=state.local_slots[expr.target], b=-1, diagnostic=-1, + op=.Load, span=expr.span, type=expr.type, target=ir.INVALID_REF, + a=state.local_slots[local], b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) - } else if expr.target >= 0 && expr.target < len(state.local_values) && - state.local_values[expr.target] >= 0 { - last = state.local_values[expr.target] + } else if local != hir.INVALID_LOCAL && int(local) < len(state.local_values) && + state.local_values[local] != ir.INVALID_INSTRUCTION { + last = state.local_values[local] } - if last < 0 { + if last == ir.INVALID_INSTRUCTION { last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) } _ = pop(&stack) case .Global: - if expr.target < 0 || expr.target >= len(state.hir_module.globals) { + global := hir.as_global(expr.target) + if global == hir.INVALID_GLOBAL || int(global) >= len(state.hir_module.globals) { last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) } else { last = append_instruction(state, ir.Instruction{ - op=.Load_Global, span=expr.span, type=expr.type, target=expr.target, - a=-1, b=-1, diagnostic=-1, + op=.Load_Global, span=expr.span, type=expr.type, target=ir.global_ref(ir.Global_Id(global)), + a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) } _ = pop(&stack) @@ -135,12 +142,13 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int { stack[frame_index].stage = 2 append(&stack, Lower_Expr_Frame{expr=expr.left}) case .Call: - if expr.target < 0 || expr.target >= len(state.hir_module.functions) { + function := hir.as_function(expr.target) + if function == hir.INVALID_FUNCTION || int(function) >= len(state.hir_module.functions) { last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) _ = pop(&stack) continue } - stack[frame_index].args = make([]int, len(expr.args), state.allocator) + stack[frame_index].args = make([]ir.Instruction_Id, len(expr.args), state.allocator) stack[frame_index].stage = 4 if len(expr.args) > 0 { append(&stack, Lower_Expr_Frame{expr=expr.args[0]}) @@ -150,8 +158,8 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int { } if frame.stage == 1 { last = append_instruction(state, ir.Instruction{ - op=.Widen, span=expr.span, type=expr.type, target=-1, - a=last, b=-1, diagnostic=-1, + op=.Widen, span=expr.span, type=expr.type, target=ir.INVALID_REF, + a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) _ = pop(&stack) continue @@ -164,8 +172,8 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int { } if frame.stage == 3 { last = append_instruction(state, ir.Instruction{ - op=.Add_Checked, span=expr.span, type=expr.type, target=-1, - a=frame.left, b=last, diagnostic=-1, + op=.Add_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF, + a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC, }) _ = pop(&stack) continue @@ -180,8 +188,8 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int { } } last = append_instruction(state, ir.Instruction{ - op=.Call, span=expr.span, type=expr.type, target=expr.target, - a=-1, b=-1, args=stack[frame_index].args, diagnostic=-1, + op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(hir.as_function(expr.target))), + a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC, }) stack[frame_index].args = nil _ = pop(&stack) @@ -194,8 +202,8 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m state := State{ hir_module=hir_module, allocator=allocator, - local_values=make([]int, len(function.locals), allocator), - local_slots=make([]int, len(function.locals), allocator), + local_values=make([]ir.Instruction_Id, len(function.locals), allocator), + local_slots=make([]ir.Instruction_Id, len(function.locals), allocator), } state.instructions.allocator = allocator state.expr_stack.allocator = allocator @@ -205,17 +213,17 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m delete(state.expr_stack) } for _, index in state.local_values { - state.local_values[index] = -1 - state.local_slots[index] = -1 + state.local_values[index] = ir.INVALID_INSTRUCTION + state.local_slots[index] = ir.INVALID_INSTRUCTION } for local_id in function.params { param := append_instruction(&state, ir.Instruction{ op=.Param, type=function.locals[local_id].type, - target=local_id, - a=-1, - b=-1, - diagnostic=-1, + target=ir.local_ref(ir.Local_Id(local_id)), + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) state.local_values[local_id] = param } @@ -225,10 +233,10 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m switch statement.kind { case .Declaration: value := lower_expr(&state, statement.expr) - if statement.local < 0 || statement.local >= len(function.locals) { + if statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.locals) { append_instruction(&state, ir.Instruction{ op=.Trap, span=statement.span, type=types.VOID, - target=-1, a=-1, b=-1, diagnostic=statement.diagnostic, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic, }) continue } @@ -238,34 +246,34 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m op=.Alloca, span=statement.span, type=local.type, - target=statement.local, - a=-1, - b=-1, - diagnostic=-1, + target=ir.local_ref(ir.Local_Id(statement.local)), + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) state.local_slots[statement.local] = slot append_instruction(&state, ir.Instruction{ op=.Store, span=statement.span, type=local.type, - target=-1, + target=ir.INVALID_REF, a=slot, b=value, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) } else { state.local_values[statement.local] = value } case .Assignment: value := lower_expr(&state, statement.expr) - slot := -1 - if statement.local >= 0 && statement.local < len(state.local_slots) { + slot := ir.INVALID_INSTRUCTION + if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) { slot = state.local_slots[statement.local] } - if slot < 0 || statement.local < 0 || statement.local >= len(function.locals) { + if slot == ir.INVALID_INSTRUCTION || statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.locals) { append_instruction(&state, ir.Instruction{ op=.Trap, span=statement.span, type=types.VOID, - target=-1, a=-1, b=-1, diagnostic=statement.diagnostic, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic, }) continue } @@ -273,21 +281,21 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m op=.Store, span=statement.span, type=function.locals[statement.local].type, - target=-1, + target=ir.INVALID_REF, a=slot, b=value, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) case .Return: - if statement.expr < 0 { + if statement.expr == hir.INVALID_EXPR { append_instruction(&state, ir.Instruction{ op=.Return_Void, span=statement.span, type=types.VOID, - target=-1, - a=-1, - b=-1, - diagnostic=-1, + target=ir.INVALID_REF, + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) } else { value := lower_expr(&state, statement.expr) @@ -295,10 +303,10 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m op=.Return, span=statement.span, type=function.result, - target=-1, + target=ir.INVALID_REF, a=value, - b=-1, - diagnostic=-1, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) } case .Expression, .Sink: @@ -308,9 +316,9 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m op=.Trap, span=statement.span, type=types.VOID, - target=-1, - a=-1, - b=-1, + target=ir.INVALID_REF, + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic, }) } @@ -319,18 +327,18 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m (state.instructions[len(state.instructions)-1].op != .Return && state.instructions[len(state.instructions)-1].op != .Return_Void) { if function.result.kind == .Void { - append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=-1, a=-1, b=-1, diagnostic=-1}) + append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}) } else { value := append_instruction(&state, ir.Instruction{ op=.Const, type=function.result, integer=sentinel(function.result), - target=-1, - a=-1, - b=-1, - diagnostic=-1, + target=ir.INVALID_REF, + a=ir.INVALID_INSTRUCTION, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) - append_instruction(&state, ir.Instruction{op=.Return, type=function.result, target=-1, a=value, b=-1, diagnostic=-1}) + append_instruction(&state, ir.Instruction{op=.Return, type=function.result, target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC}) } } return state.instructions[:] @@ -345,10 +353,10 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al append_instruction(&state, ir.Instruction{ op=.Return, type=global.type, - target=-1, + target=ir.INVALID_REF, a=value, - b=-1, - diagnostic=-1, + b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, }) return state.instructions[:] } @@ -356,6 +364,7 @@ lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, al lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module { module := ir.init_module(allocator) for global in hir_module.globals { + _ = ir.global_id(len(module.globals)) append(&module.globals, ir.Global{ name=global.name, type=global.type, @@ -371,6 +380,7 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod for local_id, index in function.params { param_types[index] = function.locals[local_id].type } + _ = ir.function_id(len(module.functions)) append(&module.functions, ir.Function{ link_name=fmt.aprintf("%s", function.link_name, allocator=allocator), calling_convention=.C if function.calling_convention == .C else .Brolang, diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 161ab7e..aa12fe3 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -13,8 +13,8 @@ Parser :: struct { source_file: ^source.Source, diagnostics: ^source.Diagnostics, module: ast.Module, - pkg: int, - file: int, + pkg: ast.Package_Id, + file: ast.File_Id, cursor: int, delimiter_depth: int, } @@ -22,10 +22,10 @@ Parser :: struct { MAX_EXPRESSION_NESTING :: 256 token_text :: proc(parser: ^Parser, tok: token.Token) -> string { - if tok.span.start < 0 || tok.span.end < tok.span.start || tok.span.end > len(parser.source_file.text) { + if tok.span.end < tok.span.start || int(tok.span.end) > len(parser.source_file.text) { return "" } - return parser.source_file.text[tok.span.start:tok.span.end] + return parser.source_file.text[int(tok.span.start):int(tok.span.end)] } span_from :: proc(first, last: source.Span) -> source.Span { @@ -61,19 +61,19 @@ skip_newlines :: proc(parser: ^Parser) { } } -add_expr :: proc(parser: ^Parser, expr: ast.Expr) -> int { - id := len(parser.module.exprs) +add_expr :: proc(parser: ^Parser, expr: ast.Expr) -> ast.Expr_Id { + id := ast.expr_id(len(parser.module.exprs)) append(&parser.module.exprs, expr) return id } -invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> int { +invalid_expr :: proc(parser: ^Parser, span: source.Span, message: string) -> ast.Expr_Id { id := source.add(parser.diagnostics, span, message) return add_expr(parser, ast.Expr{ kind=.Invalid, span=span, - left=ast.INVALID_ID, - right=ast.INVALID_ID, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, diagnostic=id, }) } @@ -131,7 +131,7 @@ skip_parenthesized :: proc(parser: ^Parser) -> source.Span { return span_from(start.span, end.span) } -parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> int { +parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> ast.Expr_Id { if nesting >= MAX_EXPRESSION_NESTING { span := skip_parenthesized(parser) return invalid_expr(parser, span, "expression nesting exceeds 256 levels") @@ -139,7 +139,7 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok left_paren := advance(parser) parser.delimiter_depth += 1 defer parser.delimiter_depth -= 1 - args: [dynamic]int + args: [dynamic]ast.Expr_Id args.allocator = parser.module.allocator skip_newlines(parser) for current(parser).kind != .Right_Paren && current(parser).kind != .Eof { @@ -162,13 +162,13 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok qualifier=qualifier, name=name.symbol, args=args[:], - left=ast.INVALID_ID, - right=ast.INVALID_ID, - diagnostic=-1, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, }) } -parse_primary :: proc(parser: ^Parser, nesting: int) -> int { +parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id { tok := current(parser) #partial switch tok.kind { case .Integer: @@ -181,9 +181,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> int { kind=.Integer, span=tok.span, integer=value, - left=ast.INVALID_ID, - right=ast.INVALID_ID, - diagnostic=-1, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, }) case .Identifier: first := advance(parser) @@ -204,9 +204,9 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> int { span=span_from(first.span, name.span), qualifier=qualifier, name=name.symbol, - left=ast.INVALID_ID, - right=ast.INVALID_ID, - diagnostic=-1, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, }) case .Underscore: advance(parser) @@ -231,8 +231,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> int { return add_expr(parser, ast.Expr{ kind=.Invalid, span=tok.span, - left=ast.INVALID_ID, - right=ast.INVALID_ID, + left=ast.INVALID_EXPR, + right=ast.INVALID_EXPR, diagnostic=tok.diagnostic, }) } @@ -250,7 +250,7 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) { return 0, 0, false } -parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int) -> int { +parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int) -> ast.Expr_Id { if nesting > MAX_EXPRESSION_NESTING { tok := current(parser) if tok.kind != .Newline && tok.kind != .Right_Brace && tok.kind != .Eof { @@ -277,7 +277,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int span=span_from(left_expr.span, right_expr.span), left=left, right=right, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) if parser.delimiter_depth > 0 { skip_newlines(parser) @@ -286,17 +286,17 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int return left } -parse_expression :: proc(parser: ^Parser) -> int { +parse_expression :: proc(parser: ^Parser) -> ast.Expr_Id { return parse_expression_bp(parser, 0, 0) } -finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> int { +finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> source.Diagnostic_Id { if current(parser).kind == .Newline { skip_newlines(parser) - return -1 + return source.INVALID_DIAGNOSTIC } if current(parser).kind == .Eof || allow_closing_brace && current(parser).kind == .Right_Brace { - return -1 + return source.INVALID_DIAGNOSTIC } diagnostic := source.add( parser.diagnostics, @@ -312,33 +312,33 @@ finish_statement :: proc(parser: ^Parser, allow_closing_brace := false) -> int { return diagnostic } -parse_return :: proc(parser: ^Parser) -> int { +parse_return :: proc(parser: ^Parser) -> ast.Stmt_Id { start := advance(parser) skip_newlines(parser) if current(parser).kind == .Underscore { end := advance(parser) - id := len(parser.module.statements) + id := ast.stmt_id(len(parser.module.statements)) append(&parser.module.statements, ast.Stmt{ kind=.Return, span=span_from(start.span, end.span), name=end.symbol, - expr=ast.INVALID_ID, - diagnostic=-1, + expr=ast.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, }) return id } expr := parse_expression(parser) - id := len(parser.module.statements) + id := ast.stmt_id(len(parser.module.statements)) append(&parser.module.statements, ast.Stmt{ kind=.Return, span=span_from(start.span, parser.module.exprs[expr].span), expr=expr, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) return id } -parse_statement :: proc(parser: ^Parser) -> int { +parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id { if current(parser).kind == .Keyword_Return { return parse_return(parser) } @@ -363,7 +363,7 @@ parse_statement :: proc(parser: ^Parser) -> int { kind = .Declaration immutable = operator.kind == .Colon_Colon } - id := len(parser.module.statements) + id := ast.stmt_id(len(parser.module.statements)) append(&parser.module.statements, ast.Stmt{ kind=kind, span=span_from(name.span, parser.module.exprs[expr].span), @@ -371,7 +371,7 @@ parse_statement :: proc(parser: ^Parser) -> int { type=type_syntax, immutable=immutable, expr=expr, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) return id } @@ -379,12 +379,12 @@ parse_statement :: proc(parser: ^Parser) -> int { } expr := parse_expression(parser) - id := len(parser.module.statements) + id := ast.stmt_id(len(parser.module.statements)) append(&parser.module.statements, ast.Stmt{ kind=.Expression, span=parser.module.exprs[expr].span, expr=expr, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) return id } @@ -446,6 +446,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) { if !ended_by_newline && current(parser).kind != .Eof { _ = finish_statement(parser) } + _ = ast.function_id(len(parser.module.functions)) append(&parser.module.functions, ast.Function{ span=span_from(name.span, end.span), name=name.symbol, @@ -455,23 +456,23 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) { has_body=false, params=params, result=result, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) return } advance(parser) - body: [dynamic]int + body: [dynamic]ast.Stmt_Id body.allocator = parser.module.allocator skip_newlines(parser) for current(parser).kind != .Right_Brace && current(parser).kind != .Eof { append(&body, parse_statement(parser)) - if diagnostic := finish_statement(parser, true); diagnostic >= 0 { - statement_id := len(parser.module.statements) + if diagnostic := finish_statement(parser, true); diagnostic != source.INVALID_DIAGNOSTIC { + statement_id := ast.stmt_id(len(parser.module.statements)) append(&parser.module.statements, ast.Stmt{ kind=.Invalid, span=current(parser).span, - expr=ast.INVALID_ID, + expr=ast.INVALID_EXPR, diagnostic=diagnostic, }) append(&body, statement_id) @@ -482,6 +483,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) { source.add(parser.diagnostics, current(parser).span, "expected '}' after function body") end = func_token } + _ = ast.function_id(len(parser.module.functions)) append(&parser.module.functions, ast.Function{ span=span_from(name.span, end.span), name=name.symbol, @@ -492,7 +494,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) { params=params, result=result, body=body[:], - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) } @@ -522,28 +524,30 @@ parse_import :: proc(parser: ^Parser, alias: token.Token, start: token.Token) { if path_token.kind != .Newline && path_token.kind != .Eof { advance(parser) } + _ = ast.import_id(len(parser.module.imports)) append(&parser.module.imports, ast.Import{ span=start.span, alias=alias.symbol, pkg=parser.pkg, file=parser.file, - target=-1, + target=ast.INVALID_PACKAGE, valid=false, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) _ = finish_statement(parser) return } advance(parser) + _ = ast.import_id(len(parser.module.imports)) append(&parser.module.imports, ast.Import{ span=span_from(start.span, path_token.span), alias=alias.symbol, path=decode_import_path(parser, path_token), pkg=parser.pkg, file=parser.file, - target=-1, + target=ast.INVALID_PACKAGE, valid=true, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) _ = finish_statement(parser) } @@ -606,6 +610,7 @@ parse_top_level :: proc(parser: ^Parser) { } expr := parse_expression(parser) + _ = ast.global_id(len(parser.module.globals)) append(&parser.module.globals, ast.Global{ span=span_from(name.span, parser.module.exprs[expr].span), name=name.symbol, @@ -614,7 +619,7 @@ parse_top_level :: proc(parser: ^Parser) { type=type_syntax, immutable=operator.kind == .Colon_Colon, expr=expr, - diagnostic=-1, + diagnostic=source.INVALID_DIAGNOSTIC, }) _ = finish_statement(parser) } @@ -644,7 +649,8 @@ parse_into :: proc( source_file: ^source.Source, diagnostics: ^source.Diagnostics, module: ^ast.Module, - pkg, file: int, + pkg: ast.Package_Id, + file: ast.File_Id, ) { parser := Parser{ tokens=stream, diff --git a/compiler/source/source.odin b/compiler/source/source.odin index be7fc0d..25f7ab0 100644 --- a/compiler/source/source.odin +++ b/compiler/source/source.odin @@ -3,17 +3,48 @@ package source import "core:fmt" import "core:mem" +Source_Id :: distinct u32 +Diagnostic_Id :: distinct u32 +Offset :: distinct u32 + +INVALID_SOURCE :: Source_Id(0xffff_ffff) +INVALID_DIAGNOSTIC :: Diagnostic_Id(0xffff_ffff) + +source_id :: proc(index: int) -> Source_Id { + assert(index >= 0 && u64(index) < u64(INVALID_SOURCE)) + return Source_Id(index) +} + +diagnostic_id :: proc(index: int) -> Diagnostic_Id { + assert(index >= 0 && u64(index) < u64(INVALID_DIAGNOSTIC)) + return Diagnostic_Id(index) +} + +source_index :: proc(id: Source_Id, count: int) -> (int, bool) { + index := int(id) + return index, id != INVALID_SOURCE && index < count +} + +diagnostic_index :: proc(id: Diagnostic_Id, count: int) -> (int, bool) { + index := int(id) + return index, id != INVALID_DIAGNOSTIC && index < count +} + +fits_source_length :: proc(length: u64) -> bool { + return length <= u64(max(Offset)) +} + Span :: struct { - file: int, - start: int, - end: int, + file: Source_Id, + start: Offset, + end: Offset, } Source :: struct { - id: int, + id: Source_Id, path: string, text: string, - line_starts: []int, + line_starts: []Offset, } Store :: struct { @@ -30,7 +61,7 @@ Diagnostics :: struct { source: ^Source, store: ^Store, items: [dynamic]Diagnostic, - index: map[Diagnostic_Key]int, + index: map[Diagnostic_Key]Diagnostic_Id, allocator: mem.Allocator, } @@ -55,20 +86,22 @@ destroy_store :: proc(store: ^Store) { delete(store.items) } -make_line_starts :: proc(text: string, allocator: mem.Allocator) -> []int { - result: [dynamic]int +make_line_starts :: proc(text: string, allocator: mem.Allocator) -> []Offset { + assert(fits_source_length(u64(len(text)))) + result: [dynamic]Offset result.allocator = allocator append(&result, 0) for value, offset in transmute([]byte)text { if value == '\n' { - append(&result, offset+1) + append(&result, Offset(offset+1)) } } return result[:] } -add_source_owned :: proc(store: ^Store, path: string, text: []byte) -> int { - id := len(store.items) +add_source_owned :: proc(store: ^Store, path: string, text: []byte) -> Source_Id { + assert(fits_source_length(u64(len(text)))) + id := source_id(len(store.items)) owned_text := string(text) append(&store.items, Source{ id=id, @@ -79,7 +112,7 @@ add_source_owned :: proc(store: ^Store, path: string, text: []byte) -> int { return id } -add_source :: proc(store: ^Store, path, text: string) -> int { +add_source :: proc(store: ^Store, path, text: string) -> Source_Id { owned := make([]byte, len(text), store.allocator) copy(owned, transmute([]byte)text) return add_source_owned(store, path, owned) @@ -111,51 +144,51 @@ destroy_diagnostics :: proc(diagnostics: ^Diagnostics) { delete(diagnostics.items) } -add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int { +add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> Diagnostic_Id { key := Diagnostic_Key{span=span, message=message} if id, ok := diagnostics.index[key]; ok { return id } - id := len(diagnostics.items) + id := diagnostic_id(len(diagnostics.items)) cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator) append(&diagnostics.items, Diagnostic{span=span, message=cloned}) diagnostics.index[Diagnostic_Key{span=span, message=cloned}] = id return id } -addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int { +addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> Diagnostic_Id { message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator) key := Diagnostic_Key{span=span, message=message} if id, ok := diagnostics.index[key]; ok { delete(message, diagnostics.allocator) return id } - id := len(diagnostics.items) + id := diagnostic_id(len(diagnostics.items)) append(&diagnostics.items, Diagnostic{span=span, message=message}) diagnostics.index[Diagnostic_Key{span=span, message=message}] = id return id } -line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) { +line_and_column :: proc(source_file: ^Source, offset: Offset) -> (line, column: int) { if len(source_file.line_starts) > 0 { - limit := min(max(offset, 0), len(source_file.text)) + limit := min(int(offset), len(source_file.text)) low := 0 high := len(source_file.line_starts) for low < high { middle := low + (high-low)/2 - if source_file.line_starts[middle] <= limit { + if int(source_file.line_starts[middle]) <= limit { low = middle+1 } else { high = middle } } line = max(low, 1) - column = limit-source_file.line_starts[line-1]+1 + column = limit-int(source_file.line_starts[line-1])+1 return } line = 1 column = 1 - limit := min(max(offset, 0), len(source_file.text)) + limit := min(int(offset), len(source_file.text)) for byte_value in transmute([]byte)source_file.text[:limit] { if byte_value == '\n' { line += 1 @@ -168,14 +201,17 @@ line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int } source_for_span :: proc(diagnostics: ^Diagnostics, span: Span) -> ^Source { - if diagnostics.store != nil && span.file >= 0 && span.file < len(diagnostics.store.items) { - return &diagnostics.store.items[span.file] + if diagnostics.store != nil { + if index, ok := source_index(span.file, len(diagnostics.store.items)); ok { + return &diagnostics.store.items[index] + } } return diagnostics.source } -format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocator) -> string { - if id < 0 || id >= len(diagnostics.items) { +format :: proc(diagnostics: ^Diagnostics, id: Diagnostic_Id, allocator := context.allocator) -> string { + index, ok := diagnostic_index(id, len(diagnostics.items)) + if !ok { path := "" if diagnostics.source != nil { path = diagnostics.source.path @@ -184,7 +220,7 @@ format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocato } return fmt.aprintf("%s: compiler recovery error", path, allocator=allocator) } - diagnostic := diagnostics.items[id] + diagnostic := diagnostics.items[index] source_file := source_for_span(diagnostics, diagnostic.span) if source_file == nil { return fmt.aprintf(": error: %s", diagnostic.message, allocator=allocator) @@ -202,7 +238,7 @@ format :: proc(diagnostics: ^Diagnostics, id: int, allocator := context.allocato print_all :: proc(diagnostics: ^Diagnostics) { for _, id in diagnostics.items { - message := format(diagnostics, id) + message := format(diagnostics, diagnostic_id(id)) fmt.eprintln(message) delete(message) } diff --git a/compiler/token/token.odin b/compiler/token/token.odin index cd9b39f..065be4f 100644 --- a/compiler/token/token.odin +++ b/compiler/token/token.odin @@ -3,7 +3,7 @@ package token import "../source" import "../symbol" -Kind :: enum { +Kind :: enum u8 { Invalid, Eof, Newline, @@ -32,10 +32,10 @@ Kind :: enum { } Token :: struct { - kind: Kind, - span: source.Span, - symbol: symbol.Id, - diagnostic: int, + span: source.Span, + symbol: symbol.Id, + diagnostic: source.Diagnostic_Id, + kind: Kind, } Stream :: struct { diff --git a/compiler_tests.odin b/compiler_tests.odin index 50ab51f..b03c777 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -82,6 +82,52 @@ main :: func() void { _ = value } testing.expect_value(t, module.statements[module.functions[0].body[0]].name, sink_symbol) } +@(test) +compact_ids_reserve_invalid_values_and_preserve_layout :: proc(t: ^testing.T) { + testing.expect_value(t, size_of(source.Span), 12) + testing.expect_value(t, size_of(token.Token), 24) + + source_index, source_ok := source.source_index(source.Source_Id(0), 1) + testing.expect_value(t, source_index, 0) + testing.expect(t, source_ok) + _, source_invalid := source.source_index(source.INVALID_SOURCE, 1) + testing.expect(t, !source_invalid) + _, source_out_of_bounds := source.source_index(source.Source_Id(1), 1) + testing.expect(t, !source_out_of_bounds) + diagnostic_index, diagnostic_ok := source.diagnostic_index(source.Diagnostic_Id(0), 1) + testing.expect_value(t, diagnostic_index, 0) + testing.expect(t, diagnostic_ok) + _, diagnostic_invalid := source.diagnostic_index(source.INVALID_DIAGNOSTIC, 1) + testing.expect(t, !diagnostic_invalid) + + expr_index, expr_ok := ast.index(ast.Expr_Id(0), ast.INVALID_EXPR, 1) + testing.expect_value(t, expr_index, 0) + testing.expect(t, expr_ok) + _, expr_invalid := ast.index(ast.INVALID_EXPR, ast.INVALID_EXPR, 1) + testing.expect(t, !expr_invalid) + + function_index, function_ok := hir.index(hir.Function_Id(0), hir.INVALID_FUNCTION, 1) + testing.expect_value(t, function_index, 0) + testing.expect(t, function_ok) + _, function_invalid := hir.index(hir.INVALID_FUNCTION, hir.INVALID_FUNCTION, 1) + testing.expect(t, !function_invalid) + + instruction_index, instruction_ok := ir.index(ir.Instruction_Id(0), ir.INVALID_INSTRUCTION, 1) + testing.expect_value(t, instruction_index, 0) + testing.expect(t, instruction_ok) + _, instruction_invalid := ir.index(ir.INVALID_INSTRUCTION, ir.INVALID_INSTRUCTION, 1) + testing.expect(t, !instruction_invalid) + + spec_index, spec_ok := checker.spec_index(checker.Spec_Id(0), 1) + testing.expect_value(t, spec_index, 0) + testing.expect(t, spec_ok) + _, spec_invalid := checker.spec_index(checker.INVALID_SPEC, 1) + testing.expect(t, !spec_invalid) + + testing.expect(t, source.fits_source_length(u64(0xffff_ffff))) + testing.expect(t, !source.fits_source_length(u64(0x1_0000_0000))) +} + @(test) lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) { source_file := source.Source{path="test.bro", text="# comment\nmain :: func() void {}\n"} @@ -196,7 +242,7 @@ main :: func() void {} c_symbol := symbol.intern(&symbols, "c") for tok in stream.items { - if tok.span.start < len(text) && text[tok.span.start:tok.span.end] == "c" { + if int(tok.span.start) < len(text) && text[int(tok.span.start):int(tok.span.end)] == "c" { testing.expect_value(t, tok.kind, token.Kind.Identifier) testing.expect_value(t, tok.symbol, c_symbol) } @@ -415,8 +461,8 @@ multi_source_diagnostics_report_the_originating_file :: proc(t: ^testing.T) { testing.expect(t, loaded) found := false - for _, diagnostic_id in diagnostics.items { - message := source.format(&diagnostics, diagnostic_id) + for _, diagnostic_index in diagnostics.items { + message := source.format(&diagnostics, source.diagnostic_id(diagnostic_index)) if strings.contains(message, "/b.bro:2:9:") && strings.contains(message, "unknown package alias 'math'") { found = true @@ -788,7 +834,7 @@ main :: func() void { testing.expect(t, done_id >= 0) testing.expect(t, main_id >= 0) testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].kind, hir.Stmt_Kind.Return) - testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].expr, -1) + testing.expect_value(t, hir_module.statements[hir_module.functions[done_id].body[0]].expr, hir.INVALID_EXPR) main := hir_module.functions[main_id] testing.expect_value(t, hir_module.statements[main.body[0]].kind, hir.Stmt_Kind.Expression) testing.expect_value(t, hir_module.statements[main.body[1]].kind, hir.Stmt_Kind.Sink) @@ -1243,14 +1289,59 @@ maximum_signed_i64_literal_parses_exactly :: proc(t: ^testing.T) { testing.expect_value(t, module.exprs[module.globals[0].expr].integer, i64(9223372036854775807)) } +@(test) +malformed_hir_references_lower_to_valid_trapped_llvm :: proc(t: ^testing.T) { + hir_module := hir.init_module() + defer hir.destroy_module(&hir_module) + append(&hir_module.exprs, hir.Expr{ + kind=.Local, + type=types.I8, + target=hir.local_ref(hir.INVALID_LOCAL), + left=hir.INVALID_EXPR, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + append(&hir_module.statements, hir.Stmt{ + kind=.Expression, + expr=hir.Expr_Id(0), + local=hir.INVALID_LOCAL, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + body := make([]hir.Stmt_Id, 1) + body[0] = hir.Stmt_Id(0) + append(&hir_module.functions, hir.Function{ + name=symbol.INVALID, + link_name=strings.clone("main"), + calling_convention=.C, + implementation=.Definition, + linkage=.External, + is_main=true, + result=types.VOID, + body=body, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + source_file := source.Source{path="test.bro", text=""} + diagnostics := source.init_diagnostics(&source_file) + defer source.destroy_diagnostics(&diagnostics) + symbols := symbol.init_table() + defer symbol.destroy_table(&symbols) + text := llvm.emit(&ir_module, &diagnostics, &symbols) + defer delete(text) + + testing.expect(t, strings.contains(text, "call void @bro.trap")) + testing.expect(t, !strings.contains(text, "%v-1")) +} + @(test) malformed_ir_emits_traps_and_typed_sentinels :: proc(t: ^testing.T) { module := ir.init_module() defer ir.destroy_module(&module) instructions := make([]ir.Instruction, 3) - instructions[0] = ir.Instruction{op=.Store, type=types.I8, a=-1, b=-1, diagnostic=-1} - instructions[1] = ir.Instruction{op=.Add_Checked, type=types.I32, a=-1, b=-1, diagnostic=-1} - instructions[2] = ir.Instruction{op=.Return, type=types.I32, a=1, b=-1, diagnostic=-1} + instructions[0] = ir.Instruction{op=.Store, type=types.I8, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC} + instructions[1] = ir.Instruction{op=.Add_Checked, type=types.I32, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC} + instructions[2] = ir.Instruction{op=.Return, type=types.I32, a=ir.Instruction_Id(1), b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC} append(&module.functions, ir.Function{ link_name=strings.clone("main"), calling_convention=.C, @@ -1335,14 +1426,14 @@ deep_global_cycle_detection_uses_iterative_dfs :: proc(t: ^testing.T) { for id in 0..