harden compiler parsing, recovery, and deep-expression handling
This commit is contained in:
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
- for global initialization cycles, report also starting and ending lines
|
- for global initialization cycles, report also starting and ending lines
|
||||||
|
|
||||||
|
# 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`
|
||||||
|
|
||||||
# milestones
|
# milestones
|
||||||
|
|
||||||
1. interop type foundation
|
1. interop type foundation
|
||||||
|
|||||||
@@ -25,3 +25,7 @@ Results captured on 2026-06-10 with Odin `dev-2026-02:b942f72cb`:
|
|||||||
| Diagnostics | 0 | 0 |
|
| Diagnostics | 0 | 0 |
|
||||||
|
|
||||||
The measured run reduced token size by 14.3% and peak tracked memory by 9.8%.
|
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.
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ build_command :: proc(
|
|||||||
command: [dynamic]string
|
command: [dynamic]string
|
||||||
command.allocator = allocator
|
command.allocator = allocator
|
||||||
append_owned(&command, "/usr/bin/env", allocator)
|
append_owned(&command, "/usr/bin/env", allocator)
|
||||||
append_owned(&command, "ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache", allocator)
|
|
||||||
append_owned(&command, "ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache", allocator)
|
|
||||||
append_owned(&command, "zig", allocator)
|
append_owned(&command, "zig", allocator)
|
||||||
append_owned(&command, "cc", allocator)
|
append_owned(&command, "cc", allocator)
|
||||||
append_owned(&command, "-Wno-override-module", allocator)
|
append_owned(&command, "-Wno-override-module", allocator)
|
||||||
|
|||||||
+490
-319
@@ -60,6 +60,11 @@ Checker :: struct {
|
|||||||
global_types: []types.Type,
|
global_types: []types.Type,
|
||||||
constants: []Constant,
|
constants: []Constant,
|
||||||
template_diagnostics: []int,
|
template_diagnostics: []int,
|
||||||
|
constant_stack: [dynamic]Constant_Frame,
|
||||||
|
expr_stack: [dynamic]int,
|
||||||
|
infer_stack: [dynamic]Infer_Frame,
|
||||||
|
build_stack: [dynamic]Build_Expr_Frame,
|
||||||
|
cycle_stack: [dynamic]Cycle_Frame,
|
||||||
main_symbol: symbol.Id,
|
main_symbol: symbol.Id,
|
||||||
sink_symbol: symbol.Id,
|
sink_symbol: symbol.Id,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
@@ -69,38 +74,75 @@ symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
|
|||||||
return symbol.resolve(checker.symbols, id)
|
return symbol.resolve(checker.symbols, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Constant_Frame :: struct {
|
||||||
|
expr: int,
|
||||||
|
stage: u8,
|
||||||
|
}
|
||||||
|
|
||||||
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
|
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
|
||||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||||
return Constant{kind = .Not_Constant}
|
return Constant{kind = .Not_Constant}
|
||||||
}
|
}
|
||||||
if checker.constants[expr_id].kind != .Unknown {
|
stack := checker.constant_stack
|
||||||
return checker.constants[expr_id]
|
clear_dynamic_array(&stack)
|
||||||
|
defer {
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
checker.constant_stack = stack
|
||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[expr_id]
|
append(&stack, Constant_Frame{expr=expr_id})
|
||||||
result: Constant
|
|
||||||
switch expr.kind {
|
for len(stack) > 0 {
|
||||||
case .Integer:
|
frame_index := len(stack)-1
|
||||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
frame := stack[frame_index]
|
||||||
case .Add:
|
if checker.constants[frame.expr].kind != .Unknown {
|
||||||
left := eval_constant(checker, expr.left)
|
_ = pop(&stack)
|
||||||
right := eval_constant(checker, expr.right)
|
continue
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
|
if expr.kind != .Add {
|
||||||
|
result := Constant{kind = .Not_Constant}
|
||||||
|
if expr.kind == .Integer {
|
||||||
|
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||||
|
}
|
||||||
|
checker.constants[frame.expr] = result
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 0 {
|
||||||
|
stack[frame_index].stage = 1
|
||||||
|
if expr.left >= 0 && expr.left < len(checker.ast_module.exprs) &&
|
||||||
|
checker.constants[expr.left].kind == .Unknown {
|
||||||
|
append(&stack, Constant_Frame{expr=expr.left})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 1 {
|
||||||
|
stack[frame_index].stage = 2
|
||||||
|
if expr.right >= 0 && expr.right < len(checker.ast_module.exprs) &&
|
||||||
|
checker.constants[expr.right].kind == .Unknown {
|
||||||
|
append(&stack, Constant_Frame{expr=expr.right})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
left := Constant{kind = .Not_Constant}
|
||||||
|
right := Constant{kind = .Not_Constant}
|
||||||
|
if expr.left >= 0 && expr.left < len(checker.constants) {
|
||||||
|
left = checker.constants[expr.left]
|
||||||
|
}
|
||||||
|
if expr.right >= 0 && expr.right < len(checker.constants) {
|
||||||
|
right = checker.constants[expr.right]
|
||||||
|
}
|
||||||
|
result := Constant{kind = .Not_Constant}
|
||||||
if left.kind == .Overflow || right.kind == .Overflow {
|
if left.kind == .Overflow || right.kind == .Overflow {
|
||||||
result = Constant{kind = .Overflow}
|
result = Constant{kind = .Overflow}
|
||||||
} else if left.kind != .Value || right.kind != .Value {
|
} else if left.kind == .Value && right.kind == .Value {
|
||||||
result = Constant{kind = .Not_Constant}
|
|
||||||
} else {
|
|
||||||
value, overflow := intrinsics.overflow_add(left.value, right.value)
|
value, overflow := intrinsics.overflow_add(left.value, right.value)
|
||||||
if overflow {
|
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||||
result = Constant{kind = .Overflow}
|
|
||||||
} else {
|
|
||||||
result = Constant{kind = .Value, value = value}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case .Invalid, .Name, .Call:
|
checker.constants[frame.expr] = result
|
||||||
result = Constant{kind = .Not_Constant}
|
_ = pop(&stack)
|
||||||
}
|
}
|
||||||
checker.constants[expr_id] = result
|
return checker.constants[expr_id]
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fits_signed_type :: proc(value: i128, target: types.Type) -> bool {
|
fits_signed_type :: proc(value: i128, target: types.Type) -> bool {
|
||||||
@@ -264,26 +306,29 @@ contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
|
mark_expr_imports_used :: proc(checker: ^Checker, expr_id, file: int) {
|
||||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
stack := checker.expr_stack
|
||||||
return
|
clear_dynamic_array(&stack)
|
||||||
|
defer {
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
checker.expr_stack = stack
|
||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[expr_id]
|
append(&stack, expr_id)
|
||||||
switch expr.kind {
|
for len(stack) > 0 {
|
||||||
case .Name:
|
id := pop(&stack)
|
||||||
if symbol.is_valid(expr.qualifier) {
|
if id < 0 || id >= len(checker.ast_module.exprs) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[id]
|
||||||
|
if (expr.kind == .Name || expr.kind == .Call) && symbol.is_valid(expr.qualifier) {
|
||||||
_ = find_import(checker, file, expr.qualifier, true)
|
_ = find_import(checker, file, expr.qualifier, true)
|
||||||
}
|
}
|
||||||
case .Call:
|
switch expr.kind {
|
||||||
if symbol.is_valid(expr.qualifier) {
|
case .Call:
|
||||||
_ = find_import(checker, file, expr.qualifier, true)
|
append(&stack, ..expr.args)
|
||||||
|
case .Add:
|
||||||
|
append(&stack, expr.left, expr.right)
|
||||||
|
case .Invalid, .Integer, .Name:
|
||||||
}
|
}
|
||||||
for arg in expr.args {
|
|
||||||
mark_expr_imports_used(checker, arg, file)
|
|
||||||
}
|
|
||||||
case .Add:
|
|
||||||
mark_expr_imports_used(checker, expr.left, file)
|
|
||||||
mark_expr_imports_used(checker, expr.right, file)
|
|
||||||
case .Invalid, .Integer:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,80 +490,138 @@ ensure_spec :: proc(checker: ^Checker, template: int, actual_args: []types.Type)
|
|||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Infer_Frame :: struct {
|
||||||
|
expr: int,
|
||||||
|
stage: u8,
|
||||||
|
left: types.Type,
|
||||||
|
arg_index: int,
|
||||||
|
args: []types.Type,
|
||||||
|
template: int,
|
||||||
|
}
|
||||||
|
|
||||||
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type {
|
infer_expr :: proc(checker: ^Checker, expr_id: int, locals: []Infer_Local, pkg := 0, file := 0) -> types.Type {
|
||||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
stack := checker.infer_stack
|
||||||
return types.INVALID
|
clear_dynamic_array(&stack)
|
||||||
|
defer {
|
||||||
|
for frame in stack {
|
||||||
|
delete(frame.args, checker.allocator)
|
||||||
|
}
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
checker.infer_stack = stack
|
||||||
}
|
}
|
||||||
constant := eval_constant(checker, expr_id)
|
append(&stack, Infer_Frame{expr=expr_id, template=-1})
|
||||||
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
last := types.INVALID
|
||||||
return types.I64
|
|
||||||
|
for len(stack) > 0 {
|
||||||
|
frame_index := len(stack)-1
|
||||||
|
frame := stack[frame_index]
|
||||||
|
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
|
||||||
|
last = types.INVALID
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
|
if frame.stage == 0 {
|
||||||
|
constant := eval_constant(checker, frame.expr)
|
||||||
|
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
||||||
|
last = types.I64
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if constant.kind == .Value {
|
||||||
|
last = types.smallest_signed_for_literal(i64(constant.value))
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch expr.kind {
|
||||||
|
case .Invalid:
|
||||||
|
last = types.INVALID
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Integer:
|
||||||
|
last = types.smallest_signed_for_literal(expr.integer)
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Name:
|
||||||
|
last = types.INVALID
|
||||||
|
if !symbol.is_valid(expr.qualifier) {
|
||||||
|
last = find_infer_local(locals, expr.name)
|
||||||
|
}
|
||||||
|
if !types.is_valid(last) {
|
||||||
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||||
|
if available {
|
||||||
|
global := find_global(checker, expr.name, target_pkg)
|
||||||
|
if global >= 0 {
|
||||||
|
last = checker.global_types[global]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Add:
|
||||||
|
stack[frame_index].stage = 1
|
||||||
|
append(&stack, Infer_Frame{expr=expr.left, template=-1})
|
||||||
|
case .Call:
|
||||||
|
target_pkg, available := expr_package(checker, expr, pkg, file)
|
||||||
|
template := -1
|
||||||
|
if available {
|
||||||
|
template = find_template(checker, expr.name, target_pkg)
|
||||||
|
}
|
||||||
|
if template < 0 {
|
||||||
|
last = types.INVALID
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if checker.template_diagnostics[template] >= 0 {
|
||||||
|
declared := type_from_syntax(checker.ast_module.functions[template].result)
|
||||||
|
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack[frame_index].template = template
|
||||||
|
stack[frame_index].args = make([]types.Type, len(expr.args), checker.allocator)
|
||||||
|
stack[frame_index].stage = 3
|
||||||
|
if len(expr.args) > 0 {
|
||||||
|
append(&stack, Infer_Frame{expr=expr.args[0], template=-1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 1 {
|
||||||
|
stack[frame_index].left = last
|
||||||
|
stack[frame_index].stage = 2
|
||||||
|
append(&stack, Infer_Frame{expr=expr.right, template=-1})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 2 {
|
||||||
|
last = types.widest(frame.left, last)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 3 {
|
||||||
|
if frame.arg_index < len(expr.args) {
|
||||||
|
stack[frame_index].args[frame.arg_index] = last
|
||||||
|
stack[frame_index].arg_index += 1
|
||||||
|
if frame.arg_index+1 < len(expr.args) {
|
||||||
|
append(&stack, Infer_Frame{expr=expr.args[frame.arg_index+1], template=-1})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function := checker.ast_module.functions[frame.template]
|
||||||
|
if can_specialize(function, stack[frame_index].args) {
|
||||||
|
spec := ensure_spec(checker, frame.template, stack[frame_index].args)
|
||||||
|
last = checker.specs[spec].result
|
||||||
|
} else {
|
||||||
|
declared := type_from_syntax(function.result)
|
||||||
|
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
|
||||||
|
last = types.I32
|
||||||
|
} else {
|
||||||
|
last = declared if declared.kind == .Concrete || declared.kind == .Void else types.INVALID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(stack[frame_index].args, checker.allocator)
|
||||||
|
stack[frame_index].args = nil
|
||||||
|
_ = pop(&stack)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if constant.kind == .Value {
|
return last
|
||||||
return types.smallest_signed_for_literal(i64(constant.value))
|
|
||||||
}
|
|
||||||
expr := checker.ast_module.exprs[expr_id]
|
|
||||||
switch expr.kind {
|
|
||||||
case .Invalid:
|
|
||||||
return types.INVALID
|
|
||||||
case .Integer:
|
|
||||||
return types.smallest_signed_for_literal(expr.integer)
|
|
||||||
case .Name:
|
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
|
||||||
local_type := find_infer_local(locals, expr.name)
|
|
||||||
if types.is_valid(local_type) {
|
|
||||||
return local_type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
||||||
if !available {
|
|
||||||
return types.INVALID
|
|
||||||
}
|
|
||||||
global := find_global(checker, expr.name, target_pkg)
|
|
||||||
if global >= 0 {
|
|
||||||
return checker.global_types[global]
|
|
||||||
}
|
|
||||||
return types.INVALID
|
|
||||||
case .Add:
|
|
||||||
left := infer_expr(checker, expr.left, locals, pkg, file)
|
|
||||||
right := infer_expr(checker, expr.right, locals, pkg, file)
|
|
||||||
return types.widest(left, right)
|
|
||||||
case .Call:
|
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file)
|
|
||||||
if !available {
|
|
||||||
return types.INVALID
|
|
||||||
}
|
|
||||||
template := find_template(checker, expr.name, target_pkg)
|
|
||||||
if template < 0 {
|
|
||||||
return types.INVALID
|
|
||||||
}
|
|
||||||
if checker.template_diagnostics[template] >= 0 {
|
|
||||||
declared := type_from_syntax(checker.ast_module.functions[template].result)
|
|
||||||
if declared.kind == .Concrete || declared.kind == .Void {
|
|
||||||
return declared
|
|
||||||
}
|
|
||||||
return types.INVALID
|
|
||||||
}
|
|
||||||
args := make([]types.Type, len(expr.args), checker.allocator)
|
|
||||||
for arg, index in expr.args {
|
|
||||||
args[index] = infer_expr(checker, arg, locals, pkg, file)
|
|
||||||
}
|
|
||||||
function := checker.ast_module.functions[template]
|
|
||||||
if !can_specialize(function, args) {
|
|
||||||
delete(args, checker.allocator)
|
|
||||||
declared := type_from_syntax(function.result)
|
|
||||||
if function.pkg == 0 && function.name == checker.main_symbol && function.result == .Int {
|
|
||||||
return types.I32
|
|
||||||
}
|
|
||||||
if declared.kind == .Concrete || declared.kind == .Void {
|
|
||||||
return declared
|
|
||||||
}
|
|
||||||
return types.INVALID
|
|
||||||
}
|
|
||||||
spec := ensure_spec(checker, template, args)
|
|
||||||
delete(args, checker.allocator)
|
|
||||||
return checker.specs[spec].result
|
|
||||||
}
|
|
||||||
return types.INVALID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
|
infer_spec_result :: proc(checker: ^Checker, spec_id: int) -> types.Type {
|
||||||
@@ -752,6 +855,17 @@ build_constant_expr :: proc(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Build_Expr_Frame :: struct {
|
||||||
|
expr: int,
|
||||||
|
expected: types.Type,
|
||||||
|
stage: u8,
|
||||||
|
left: int,
|
||||||
|
arg_index: int,
|
||||||
|
built_args: []int,
|
||||||
|
arg_types: []types.Type,
|
||||||
|
template: int,
|
||||||
|
}
|
||||||
|
|
||||||
build_expr :: proc(
|
build_expr :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr_id: int,
|
expr_id: int,
|
||||||
@@ -762,173 +876,190 @@ build_expr :: proc(
|
|||||||
pkg := 0,
|
pkg := 0,
|
||||||
file := 0,
|
file := 0,
|
||||||
) -> int {
|
) -> int {
|
||||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
stack := checker.build_stack
|
||||||
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
clear_dynamic_array(&stack)
|
||||||
return invalid_hir_expr(checker, source.Span{}, id)
|
defer {
|
||||||
|
for frame in stack {
|
||||||
|
delete(frame.built_args, checker.allocator)
|
||||||
|
delete(frame.arg_types, checker.allocator)
|
||||||
|
}
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
checker.build_stack = stack
|
||||||
}
|
}
|
||||||
expr := checker.ast_module.exprs[expr_id]
|
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=-1})
|
||||||
constant := eval_constant(checker, expr_id)
|
last := -1
|
||||||
if constant.kind == .Value || constant.kind == .Overflow {
|
|
||||||
return build_constant_expr(checker, expr, constant, expected)
|
for len(stack) > 0 {
|
||||||
}
|
frame_index := len(stack)-1
|
||||||
switch expr.kind {
|
frame := stack[frame_index]
|
||||||
case .Invalid:
|
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
|
||||||
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
||||||
case .Integer:
|
last = invalid_hir_expr(checker, source.Span{}, id)
|
||||||
unreachable()
|
_ = pop(&stack)
|
||||||
case .Name:
|
continue
|
||||||
if !symbol.is_valid(expr.qualifier) {
|
}
|
||||||
if local, ok := find_build_local(locals, expr.name); ok {
|
expr := checker.ast_module.exprs[frame.expr]
|
||||||
return add_hir_expr(
|
if frame.stage == 0 {
|
||||||
|
constant := eval_constant(checker, frame.expr)
|
||||||
|
if constant.kind == .Value || constant.kind == .Overflow {
|
||||||
|
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch expr.kind {
|
||||||
|
case .Invalid, .Integer:
|
||||||
|
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Name:
|
||||||
|
last = -1
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last < 0 {
|
||||||
|
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)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Add:
|
||||||
|
stack[frame_index].stage = 1
|
||||||
|
append(&stack, Build_Expr_Frame{expr=expr.left, expected=types.INVALID, template=-1})
|
||||||
|
case .Call:
|
||||||
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||||
|
if !available {
|
||||||
|
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
template := find_template(checker, expr.name, target_pkg)
|
||||||
|
if template < 0 {
|
||||||
|
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 {
|
||||||
|
last = invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
expr.span,
|
||||||
|
"function '%s' expects %d arguments, got %d",
|
||||||
|
symbol_text(checker, expr.name),
|
||||||
|
len(checker.ast_module.functions[template].params),
|
||||||
|
len(expr.args),
|
||||||
|
)
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stack[frame_index].template = template
|
||||||
|
stack[frame_index].built_args = make([]int, len(expr.args), checker.allocator)
|
||||||
|
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
|
||||||
|
stack[frame_index].stage = 3
|
||||||
|
if len(expr.args) > 0 {
|
||||||
|
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[0].type)
|
||||||
|
if arg_expected.kind != .Concrete {
|
||||||
|
arg_expected = types.INVALID
|
||||||
|
}
|
||||||
|
append(&stack, Build_Expr_Frame{expr=expr.args[0], expected=arg_expected, template=-1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 1 {
|
||||||
|
stack[frame_index].left = last
|
||||||
|
stack[frame_index].stage = 2
|
||||||
|
append(&stack, Build_Expr_Frame{expr=expr.right, expected=types.INVALID, template=-1})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 2 {
|
||||||
|
left := frame.left
|
||||||
|
right := last
|
||||||
|
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
|
||||||
|
if !types.is_signed(result) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "addition requires compatible signed integers")
|
||||||
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
|
} else {
|
||||||
|
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||||
|
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||||
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Add, span=expr.span, type=result, left=left, right=right,
|
||||||
|
target=-1, diagnostic=-1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 3 {
|
||||||
|
if frame.arg_index < len(expr.args) {
|
||||||
|
stack[frame_index].built_args[frame.arg_index] = last
|
||||||
|
stack[frame_index].arg_types[frame.arg_index] = checker.module.exprs[last].type
|
||||||
|
stack[frame_index].arg_index += 1
|
||||||
|
if frame.arg_index+1 < len(expr.args) {
|
||||||
|
next := frame.arg_index+1
|
||||||
|
arg_expected := type_from_syntax(checker.ast_module.functions[frame.template].params[next].type)
|
||||||
|
if arg_expected.kind != .Concrete {
|
||||||
|
arg_expected = types.INVALID
|
||||||
|
}
|
||||||
|
append(&stack, Build_Expr_Frame{expr=expr.args[next], expected=arg_expected, template=-1})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spec := ensure_spec(checker, frame.template, stack[frame_index].arg_types)
|
||||||
|
delete(stack[frame_index].arg_types, checker.allocator)
|
||||||
|
stack[frame_index].arg_types = nil
|
||||||
|
for _, index in stack[frame_index].built_args {
|
||||||
|
stack[frame_index].built_args[index] = coerce_expr(
|
||||||
checker,
|
checker,
|
||||||
hir.Expr {
|
stack[frame_index].built_args[index],
|
||||||
kind = .Local,
|
checker.specs[spec].args[index],
|
||||||
span = expr.span,
|
checker.module.exprs[stack[frame_index].built_args[index]].span,
|
||||||
type = local.type,
|
|
||||||
target = local.id,
|
|
||||||
left = -1,
|
|
||||||
right = -1,
|
|
||||||
diagnostic = -1,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
add_unique(calls, spec)
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
result := checker.specs[spec].result
|
||||||
if !available {
|
if !types.is_valid(result) {
|
||||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
id := source.addf(
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
checker.diagnostics,
|
||||||
}
|
expr.span,
|
||||||
global := find_global(checker, expr.name, target_pkg)
|
"could not resolve result type for specialization of '%s'",
|
||||||
if global >= 0 {
|
symbol_text(checker, expr.name),
|
||||||
add_unique(global_reads, global)
|
)
|
||||||
return add_hir_expr(
|
delete(stack[frame_index].built_args, checker.allocator)
|
||||||
checker,
|
stack[frame_index].built_args = nil
|
||||||
hir.Expr {
|
last = invalid_hir_expr(checker, expr.span, id)
|
||||||
kind = .Global,
|
} else {
|
||||||
span = expr.span,
|
last = add_hir_expr(checker, hir.Expr{
|
||||||
type = checker.global_types[global],
|
kind=.Call, span=expr.span, type=result, target=spec,
|
||||||
target = global,
|
left=-1, right=-1, args=stack[frame_index].built_args, diagnostic=-1,
|
||||||
left = -1,
|
})
|
||||||
right = -1,
|
stack[frame_index].built_args = nil
|
||||||
diagnostic = -1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
|
||||||
case .Add:
|
|
||||||
left := build_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
||||||
right := build_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
|
||||||
left_type := checker.module.exprs[left].type
|
|
||||||
right_type := checker.module.exprs[right].type
|
|
||||||
result := types.widest(left_type, right_type)
|
|
||||||
if !types.is_signed(result) {
|
|
||||||
id := source.add(
|
|
||||||
checker.diagnostics,
|
|
||||||
expr.span,
|
|
||||||
"addition requires compatible signed integers",
|
|
||||||
)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
|
||||||
}
|
|
||||||
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
|
||||||
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
|
||||||
return add_hir_expr(
|
|
||||||
checker,
|
|
||||||
hir.Expr {
|
|
||||||
kind = .Add,
|
|
||||||
span = expr.span,
|
|
||||||
type = result,
|
|
||||||
left = left,
|
|
||||||
right = right,
|
|
||||||
target = -1,
|
|
||||||
diagnostic = -1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
case .Call:
|
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
|
||||||
if !available {
|
|
||||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
|
||||||
}
|
|
||||||
template := find_template(checker, expr.name, target_pkg)
|
|
||||||
if template < 0 {
|
|
||||||
id := add_call_resolution_diagnostic(checker, expr, target_pkg)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
|
||||||
}
|
|
||||||
if checker.template_diagnostics[template] >= 0 {
|
|
||||||
return invalid_hir_expr(checker, expr.span, checker.template_diagnostics[template])
|
|
||||||
}
|
|
||||||
if len(expr.args) != len(checker.ast_module.functions[template].params) {
|
|
||||||
id := source.addf(
|
|
||||||
checker.diagnostics,
|
|
||||||
expr.span,
|
|
||||||
"function '%s' expects %d arguments, got %d",
|
|
||||||
symbol_text(checker, expr.name),
|
|
||||||
len(checker.ast_module.functions[template].params),
|
|
||||||
len(expr.args),
|
|
||||||
)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
|
||||||
}
|
|
||||||
built_args := make([]int, len(expr.args), checker.allocator)
|
|
||||||
arg_types := make([]types.Type, len(expr.args), checker.allocator)
|
|
||||||
for arg, index in expr.args {
|
|
||||||
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[index].type)
|
|
||||||
if arg_expected.kind != .Concrete {
|
|
||||||
arg_expected = types.INVALID
|
|
||||||
}
|
}
|
||||||
built_args[index] = build_expr(
|
_ = pop(&stack)
|
||||||
checker,
|
|
||||||
arg,
|
|
||||||
locals,
|
|
||||||
global_reads,
|
|
||||||
calls,
|
|
||||||
arg_expected,
|
|
||||||
pkg,
|
|
||||||
file,
|
|
||||||
)
|
|
||||||
arg_types[index] = checker.module.exprs[built_args[index]].type
|
|
||||||
}
|
}
|
||||||
spec := ensure_spec(checker, template, arg_types)
|
|
||||||
delete(arg_types, checker.allocator)
|
|
||||||
for _, index in built_args {
|
|
||||||
built_args[index] = coerce_expr(
|
|
||||||
checker,
|
|
||||||
built_args[index],
|
|
||||||
checker.specs[spec].args[index],
|
|
||||||
checker.module.exprs[built_args[index]].span,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
add_unique(calls, spec)
|
|
||||||
result := checker.specs[spec].result
|
|
||||||
if !types.is_valid(result) {
|
|
||||||
id := source.addf(
|
|
||||||
checker.diagnostics,
|
|
||||||
expr.span,
|
|
||||||
"could not resolve result type for specialization of '%s'",
|
|
||||||
symbol_text(checker, expr.name),
|
|
||||||
)
|
|
||||||
delete(built_args, checker.allocator)
|
|
||||||
return invalid_hir_expr(checker, expr.span, id)
|
|
||||||
}
|
|
||||||
return add_hir_expr(
|
|
||||||
checker,
|
|
||||||
hir.Expr {
|
|
||||||
kind = .Call,
|
|
||||||
span = expr.span,
|
|
||||||
type = result,
|
|
||||||
target = spec,
|
|
||||||
left = -1,
|
|
||||||
right = -1,
|
|
||||||
args = built_args,
|
|
||||||
diagnostic = -1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return invalid_hir_expr(
|
return last
|
||||||
checker,
|
|
||||||
expr.span,
|
|
||||||
source.add(checker.diagnostics, expr.span, "invalid expression"),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
|
make_link_name :: proc(checker: ^Checker, spec_id: int) -> string {
|
||||||
@@ -1022,7 +1153,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
result = spec.result,
|
result = spec.result,
|
||||||
locals = hir_locals[:],
|
locals = hir_locals[:],
|
||||||
body = body[:],
|
body = body[:],
|
||||||
direct_global_reads = global_reads[:],
|
direct_global_reads = global_reads,
|
||||||
calls = calls[:],
|
calls = calls[:],
|
||||||
problematic = problematic,
|
problematic = problematic,
|
||||||
diagnostic = checker.template_diagnostics[spec.template],
|
diagnostic = checker.template_diagnostics[spec.template],
|
||||||
@@ -1387,7 +1518,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
result = spec.result,
|
result = spec.result,
|
||||||
locals = hir_locals[:],
|
locals = hir_locals[:],
|
||||||
body = body[:],
|
body = body[:],
|
||||||
direct_global_reads = global_reads[:],
|
direct_global_reads = global_reads,
|
||||||
calls = calls[:],
|
calls = calls[:],
|
||||||
problematic = problematic,
|
problematic = problematic,
|
||||||
diagnostic = -1,
|
diagnostic = -1,
|
||||||
@@ -1396,24 +1527,31 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
|||||||
delete(locals)
|
delete(locals)
|
||||||
}
|
}
|
||||||
|
|
||||||
expr_problematic :: proc(module: ^hir.Module, expr_id: int) -> bool {
|
expr_problematic :: proc(checker: ^Checker, expr_id: int) -> bool {
|
||||||
if expr_id < 0 || expr_id >= len(module.exprs) {
|
module := &checker.module
|
||||||
return true
|
stack := checker.expr_stack
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
defer {
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
checker.expr_stack = stack
|
||||||
}
|
}
|
||||||
expr := module.exprs[expr_id]
|
append(&stack, expr_id)
|
||||||
if expr.kind == .Invalid {
|
for len(stack) > 0 {
|
||||||
return true
|
id := pop(&stack)
|
||||||
}
|
if id < 0 || id >= len(module.exprs) {
|
||||||
if expr.left >= 0 && expr_problematic(module, expr.left) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if expr.right >= 0 && expr_problematic(module, expr.right) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for arg in expr.args {
|
|
||||||
if expr_problematic(module, arg) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
expr := module.exprs[id]
|
||||||
|
if expr.kind == .Invalid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if expr.left >= 0 {
|
||||||
|
append(&stack, expr.left)
|
||||||
|
}
|
||||||
|
if expr.right >= 0 {
|
||||||
|
append(&stack, expr.right)
|
||||||
|
}
|
||||||
|
append(&stack, ..expr.args)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -1477,10 +1615,10 @@ build_globals :: proc(checker: ^Checker) {
|
|||||||
expr = expr,
|
expr = expr,
|
||||||
static_value = static_value,
|
static_value = static_value,
|
||||||
is_static = is_static,
|
is_static = is_static,
|
||||||
dependencies = dependencies[:],
|
dependencies = dependencies,
|
||||||
calls = calls[:],
|
calls = calls[:],
|
||||||
direct_problem = expr_problematic(&checker.module, expr),
|
direct_problem = expr_problematic(checker, expr),
|
||||||
problematic = expr_problematic(&checker.module, expr),
|
problematic = expr_problematic(checker, expr),
|
||||||
diagnostic = diagnostic,
|
diagnostic = diagnostic,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -1544,17 +1682,13 @@ resolve_call_targets :: proc(checker: ^Checker) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
append_unique_slice :: proc(values: ^[]int, value: int, allocator: mem.Allocator) -> bool {
|
append_unique_slice :: proc(values: ^[dynamic]int, value: int) -> bool {
|
||||||
for existing in values^ {
|
for existing in values^ {
|
||||||
if existing == value {
|
if existing == value {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
replacement := make([]int, len(values^) + 1, allocator)
|
append(values, value)
|
||||||
copy(replacement, values^)
|
|
||||||
replacement[len(values^)] = value
|
|
||||||
delete(values^, allocator)
|
|
||||||
values^ = replacement
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1572,11 +1706,7 @@ propagate_global_reads :: proc(checker: ^Checker) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for global_id in checker.module.functions[callee].direct_global_reads {
|
for global_id in checker.module.functions[callee].direct_global_reads {
|
||||||
if append_unique_slice(
|
if append_unique_slice(&function.direct_global_reads, global_id) {
|
||||||
&function.direct_global_reads,
|
|
||||||
global_id,
|
|
||||||
checker.allocator,
|
|
||||||
) {
|
|
||||||
changed = true
|
changed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1593,37 +1723,68 @@ propagate_global_reads :: proc(checker: ^Checker) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for dependency in checker.module.functions[function_id].direct_global_reads {
|
for dependency in checker.module.functions[function_id].direct_global_reads {
|
||||||
_ = append_unique_slice(&global.dependencies, dependency, checker.allocator)
|
_ = append_unique_slice(&global.dependencies, dependency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cycle_Frame :: struct {
|
||||||
|
global: int,
|
||||||
|
next_dependency: int,
|
||||||
|
}
|
||||||
|
|
||||||
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []u8) {
|
detect_global_cycles_visit :: proc(checker: ^Checker, global_id: int, states: []u8) {
|
||||||
if states[global_id] == 2 {
|
if states[global_id] == 2 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if states[global_id] == 1 {
|
stack := checker.cycle_stack
|
||||||
id := source.addf(
|
clear_dynamic_array(&stack)
|
||||||
checker.diagnostics,
|
defer {
|
||||||
checker.ast_module.globals[global_id].span,
|
clear_dynamic_array(&stack)
|
||||||
"global initialization cycle involving '%s'",
|
checker.cycle_stack = stack
|
||||||
symbol_text(checker, checker.module.globals[global_id].name),
|
|
||||||
)
|
|
||||||
checker.module.globals[global_id].diagnostic = id
|
|
||||||
checker.module.globals[global_id].problematic = true
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
states[global_id] = 1
|
append(&stack, Cycle_Frame{global=global_id})
|
||||||
for dependency in checker.module.globals[global_id].dependencies {
|
for len(stack) > 0 {
|
||||||
if dependency >= 0 && dependency < len(states) {
|
frame_index := len(stack)-1
|
||||||
detect_global_cycles_visit(checker, dependency, states)
|
frame := &stack[frame_index]
|
||||||
if checker.module.globals[dependency].problematic {
|
if states[frame.global] == 0 {
|
||||||
checker.module.globals[global_id].problematic = true
|
states[frame.global] = 1
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
dependencies := checker.module.globals[frame.global].dependencies
|
||||||
|
if frame.next_dependency >= len(dependencies) {
|
||||||
|
states[frame.global] = 2
|
||||||
|
if checker.module.globals[frame.global].problematic && frame_index > 0 {
|
||||||
|
checker.module.globals[stack[frame_index-1].global].problematic = true
|
||||||
|
}
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dependency := dependencies[frame.next_dependency]
|
||||||
|
frame.next_dependency += 1
|
||||||
|
if dependency < 0 || dependency >= len(states) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if states[dependency] == 1 {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics,
|
||||||
|
checker.ast_module.globals[dependency].span,
|
||||||
|
"global initialization cycle involving '%s'",
|
||||||
|
symbol_text(checker, checker.module.globals[dependency].name),
|
||||||
|
)
|
||||||
|
checker.module.globals[dependency].diagnostic = id
|
||||||
|
checker.module.globals[dependency].problematic = true
|
||||||
|
checker.module.globals[frame.global].problematic = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if states[dependency] == 2 {
|
||||||
|
if checker.module.globals[dependency].problematic {
|
||||||
|
checker.module.globals[frame.global].problematic = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
append(&stack, Cycle_Frame{global=dependency})
|
||||||
}
|
}
|
||||||
states[global_id] = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synthesize_trap_main :: proc(checker: ^Checker) {
|
synthesize_trap_main :: proc(checker: ^Checker) {
|
||||||
@@ -1700,6 +1861,11 @@ check :: proc(
|
|||||||
allocator = allocator,
|
allocator = allocator,
|
||||||
}
|
}
|
||||||
checker.specs.allocator = allocator
|
checker.specs.allocator = allocator
|
||||||
|
checker.constant_stack.allocator = allocator
|
||||||
|
checker.expr_stack.allocator = allocator
|
||||||
|
checker.infer_stack.allocator = allocator
|
||||||
|
checker.build_stack.allocator = allocator
|
||||||
|
checker.cycle_stack.allocator = allocator
|
||||||
build_symbol_indexes(&checker)
|
build_symbol_indexes(&checker)
|
||||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||||
@@ -1718,6 +1884,11 @@ check :: proc(
|
|||||||
delete(checker.global_types, allocator)
|
delete(checker.global_types, allocator)
|
||||||
delete(checker.constants, allocator)
|
delete(checker.constants, allocator)
|
||||||
delete(checker.template_diagnostics, allocator)
|
delete(checker.template_diagnostics, allocator)
|
||||||
|
delete(checker.constant_stack)
|
||||||
|
delete(checker.expr_stack)
|
||||||
|
delete(checker.infer_stack)
|
||||||
|
delete(checker.build_stack)
|
||||||
|
delete(checker.cycle_stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
for function, index in ast_module.functions {
|
for function, index in ast_module.functions {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ Function :: struct {
|
|||||||
result: types.Type,
|
result: types.Type,
|
||||||
locals: []Local,
|
locals: []Local,
|
||||||
body: []int,
|
body: []int,
|
||||||
direct_global_reads: []int,
|
direct_global_reads: [dynamic]int,
|
||||||
calls: []int,
|
calls: []int,
|
||||||
problematic: bool,
|
problematic: bool,
|
||||||
diagnostic: int,
|
diagnostic: int,
|
||||||
@@ -91,7 +91,7 @@ Global :: struct {
|
|||||||
expr: int,
|
expr: int,
|
||||||
static_value: i64,
|
static_value: i64,
|
||||||
is_static: bool,
|
is_static: bool,
|
||||||
dependencies: []int,
|
dependencies: [dynamic]int,
|
||||||
calls: []int,
|
calls: []int,
|
||||||
direct_problem: bool,
|
direct_problem: bool,
|
||||||
problematic: bool,
|
problematic: bool,
|
||||||
@@ -125,11 +125,11 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
delete(function.params, module.allocator)
|
delete(function.params, module.allocator)
|
||||||
delete(function.locals, module.allocator)
|
delete(function.locals, module.allocator)
|
||||||
delete(function.body, module.allocator)
|
delete(function.body, module.allocator)
|
||||||
delete(function.direct_global_reads, module.allocator)
|
delete(function.direct_global_reads)
|
||||||
delete(function.calls, module.allocator)
|
delete(function.calls, module.allocator)
|
||||||
}
|
}
|
||||||
for global in module.globals {
|
for global in module.globals {
|
||||||
delete(global.dependencies, module.allocator)
|
delete(global.dependencies)
|
||||||
delete(global.calls, module.allocator)
|
delete(global.calls, module.allocator)
|
||||||
}
|
}
|
||||||
delete(module.exprs)
|
delete(module.exprs)
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ is_identifier_continue :: proc(value: byte) -> bool {
|
|||||||
|
|
||||||
keyword_kind :: proc(text: string) -> token.Kind {
|
keyword_kind :: proc(text: string) -> token.Kind {
|
||||||
switch text {
|
switch text {
|
||||||
case "c": return .Keyword_C
|
|
||||||
case "func": return .Keyword_Func
|
case "func": return .Keyword_Func
|
||||||
case "import": return .Keyword_Import
|
case "import": return .Keyword_Import
|
||||||
case "return": return .Keyword_Return
|
case "return": return .Keyword_Return
|
||||||
|
|||||||
+109
-19
@@ -40,9 +40,37 @@ function_result_type :: proc(function: ir.Function) -> string {
|
|||||||
return llvm_type(function.result)
|
return llvm_type(function.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int) {
|
sentinel :: proc(value_type: types.Type) -> i64 {
|
||||||
if value_id < 0 || value_id >= len(instructions) {
|
switch value_type.bits {
|
||||||
fmt.sbprintf(builder, "-6148914691236517206")
|
case 8: return -86
|
||||||
|
case 16: return -21846
|
||||||
|
case 32: return -1431655766
|
||||||
|
case: return -6148914691236517206
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: int) -> bool {
|
||||||
|
return instruction_id >= 0 && instruction_id < len(instructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_value :: proc(instructions: []ir.Instruction, value_id: int, expected: types.Type) -> bool {
|
||||||
|
if !valid_instruction(instructions, value_id) ||
|
||||||
|
!types.is_concrete_integer(expected) ||
|
||||||
|
!types.equal(instructions[value_id].type, expected) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch instructions[value_id].op {
|
||||||
|
case .Param, .Const, .Load_Global, .Load, .Widen, .Add_Checked, .Call:
|
||||||
|
return true
|
||||||
|
case .Alloca, .Store, .Trap, .Return, .Return_Void:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int, expected: types.Type) {
|
||||||
|
if !valid_value(instructions, value_id, expected) {
|
||||||
|
fmt.sbprintf(builder, "%d", sentinel(expected))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
value := instructions[value_id]
|
value := instructions[value_id]
|
||||||
@@ -95,13 +123,27 @@ emit_trap_call :: proc(emitter: ^Emitter, message_id: int) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []int) {
|
emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: ir.Instruction, fallback: string) {
|
||||||
|
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback)
|
||||||
|
emit_trap_call(emitter, message)
|
||||||
|
if types.is_concrete_integer(instruction.type) {
|
||||||
|
fmt.sbprintf(
|
||||||
|
&emitter.builder,
|
||||||
|
" %%v%d = add %s 0, %d\n",
|
||||||
|
instruction_id,
|
||||||
|
llvm_type(instruction.type),
|
||||||
|
sentinel(instruction.type),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_call_args :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, args: []int, param_types: []types.Type) {
|
||||||
for arg, index in args {
|
for arg, index in args {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
strings.write_string(builder, ", ")
|
strings.write_string(builder, ", ")
|
||||||
}
|
}
|
||||||
fmt.sbprintf(builder, "%s ", llvm_type(instructions[arg].type))
|
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index]))
|
||||||
write_operand(builder, instructions, arg)
|
write_operand(builder, instructions, arg, param_types[index])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,11 +164,14 @@ emit_instruction_stream :: proc(
|
|||||||
case .Param, .Const:
|
case .Param, .Const:
|
||||||
case .Load_Global:
|
case .Load_Global:
|
||||||
if instruction.target < 0 || instruction.target >= len(emitter.module.globals) {
|
if instruction.target < 0 || instruction.target >= len(emitter.module.globals) {
|
||||||
message := diagnostic_message(emitter, -1, instruction.span, "invalid global reference")
|
emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference")
|
||||||
emit_trap_call(emitter, message)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
global := emitter.module.globals[instruction.target]
|
global := emitter.module.globals[instruction.target]
|
||||||
|
if !types.equal(instruction.type, global.type) {
|
||||||
|
emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
if global.is_static {
|
if global.is_static {
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
@@ -145,8 +190,18 @@ emit_instruction_stream :: proc(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case .Alloca:
|
case .Alloca:
|
||||||
|
if !types.is_concrete_integer(instruction.type) {
|
||||||
|
emit_recovery_value(emitter, instruction_id, 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_id, llvm_type(instruction.type))
|
||||||
case .Load:
|
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")
|
||||||
|
continue
|
||||||
|
}
|
||||||
fmt.sbprintf(
|
fmt.sbprintf(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
" %%v%d = load %s, ptr %%v%d\n",
|
" %%v%d = load %s, ptr %%v%d\n",
|
||||||
@@ -155,22 +210,41 @@ emit_instruction_stream :: proc(
|
|||||||
instruction.a,
|
instruction.a,
|
||||||
)
|
)
|
||||||
case .Store:
|
case .Store:
|
||||||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
|
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")
|
||||||
|
continue
|
||||||
|
}
|
||||||
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type))
|
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type))
|
||||||
write_operand(&emitter.builder, instructions, instruction.b)
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
|
||||||
fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a)
|
fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a)
|
||||||
case .Widen:
|
case .Widen:
|
||||||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
|
!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")
|
||||||
|
continue
|
||||||
|
}
|
||||||
from_type := instructions[instruction.a].type
|
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_id, llvm_type(from_type))
|
||||||
write_operand(&emitter.builder, instructions, instruction.a)
|
write_operand(&emitter.builder, instructions, instruction.a, from_type)
|
||||||
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type))
|
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type))
|
||||||
case .Add_Checked:
|
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")
|
||||||
|
continue
|
||||||
|
}
|
||||||
type_name := llvm_type(instruction.type)
|
type_name := llvm_type(instruction.type)
|
||||||
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_id)
|
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_id)
|
||||||
strings.write_string(&emitter.builder, "{ ")
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name)
|
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)
|
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
|
||||||
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
|
||||||
write_operand(&emitter.builder, instructions, instruction.b)
|
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
|
||||||
fmt.sbprintf(&emitter.builder, ")\n")
|
fmt.sbprintf(&emitter.builder, ")\n")
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_id)
|
fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_id)
|
||||||
strings.write_string(&emitter.builder, "{ ")
|
strings.write_string(&emitter.builder, "{ ")
|
||||||
@@ -191,11 +265,27 @@ emit_instruction_stream :: proc(
|
|||||||
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_id)
|
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_id)
|
||||||
case .Call:
|
case .Call:
|
||||||
if instruction.target < 0 || instruction.target >= len(emitter.module.functions) {
|
if instruction.target < 0 || instruction.target >= len(emitter.module.functions) {
|
||||||
message := diagnostic_message(emitter, -1, instruction.span, "invalid function specialization")
|
emit_recovery_value(emitter, instruction_id, instruction, "invalid function specialization")
|
||||||
emit_trap_call(emitter, message)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
target := emitter.module.functions[instruction.target]
|
target := emitter.module.functions[instruction.target]
|
||||||
|
valid_args := len(instruction.args) == len(target.param_types)
|
||||||
|
if valid_args {
|
||||||
|
for arg, index in instruction.args {
|
||||||
|
if !valid_value(instructions, arg, target.param_types[index]) {
|
||||||
|
valid_args = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target_result := target.result
|
||||||
|
if target.is_main {
|
||||||
|
target_result = types.I32
|
||||||
|
}
|
||||||
|
if !valid_args || !types.equal(instruction.type, target_result) {
|
||||||
|
emit_recovery_value(emitter, instruction_id, instruction, "invalid function call operands")
|
||||||
|
continue
|
||||||
|
}
|
||||||
if instruction.type.kind != .Void {
|
if instruction.type.kind != .Void {
|
||||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_id)
|
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_id)
|
||||||
} else {
|
} else {
|
||||||
@@ -206,7 +296,7 @@ emit_instruction_stream :: proc(
|
|||||||
strings.write_string(&emitter.builder, "fastcc ")
|
strings.write_string(&emitter.builder, "fastcc ")
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
|
||||||
emit_call_args(&emitter.builder, instructions, instruction.args)
|
emit_call_args(&emitter.builder, instructions, instruction.args, target.param_types)
|
||||||
strings.write_string(&emitter.builder, ")\n")
|
strings.write_string(&emitter.builder, ")\n")
|
||||||
case .Trap:
|
case .Trap:
|
||||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||||
@@ -217,7 +307,7 @@ emit_instruction_stream :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function))
|
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function))
|
||||||
write_operand(&emitter.builder, instructions, instruction.a)
|
write_operand(&emitter.builder, instructions, instruction.a, function.result)
|
||||||
strings.write_string(&emitter.builder, "\n")
|
strings.write_string(&emitter.builder, "\n")
|
||||||
after_return = true
|
after_return = true
|
||||||
case .Return_Void:
|
case .Return_Void:
|
||||||
@@ -286,11 +376,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
|
|||||||
placeholder_function.result = global.type
|
placeholder_function.result = global.type
|
||||||
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
|
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
|
||||||
fmt.sbprintf(&emitter.builder, " store %s ", type_name)
|
fmt.sbprintf(&emitter.builder, " store %s ", type_name)
|
||||||
write_operand(&emitter.builder, global.initializer, value)
|
write_operand(&emitter.builder, global.initializer, value, global.type)
|
||||||
fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id)
|
fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id)
|
||||||
fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id)
|
fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id)
|
||||||
fmt.sbprintf(&emitter.builder, " ret %s ", type_name)
|
fmt.sbprintf(&emitter.builder, " ret %s ", type_name)
|
||||||
write_operand(&emitter.builder, global.initializer, value)
|
write_operand(&emitter.builder, global.initializer, value, global.type)
|
||||||
strings.write_string(&emitter.builder, "\nready:\n")
|
strings.write_string(&emitter.builder, "\nready:\n")
|
||||||
fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name)
|
fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name)
|
||||||
}
|
}
|
||||||
@@ -383,7 +473,7 @@ emit_declarations :: proc(emitter: ^Emitter) {
|
|||||||
}
|
}
|
||||||
strings.write_string(
|
strings.write_string(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
"\ndefine internal void @bro.trap(ptr %message, i64 %length) {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
"\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,13 +152,12 @@ load_package :: proc(state: ^State, path: string, import_span: source.Span, is_r
|
|||||||
}
|
}
|
||||||
|
|
||||||
for file_info in files {
|
for file_info in files {
|
||||||
bytes, read_ok := os.read_entire_file(file_info.fullpath)
|
bytes, read_ok := os.read_entire_file(file_info.fullpath, state.sources.allocator)
|
||||||
if !read_ok {
|
if !read_ok {
|
||||||
state.root_failed = true
|
state.root_failed = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
source_id := source.add_source(state.sources, file_info.fullpath, string(bytes))
|
source_id := source.add_source_owned(state.sources, file_info.fullpath, bytes)
|
||||||
delete(bytes)
|
|
||||||
file_id := len(state.module.files)
|
file_id := len(state.module.files)
|
||||||
append(&state.module.files, ast.File{source=source_id, pkg=pkg_id})
|
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)
|
stream := lexer.lex(&state.sources.items[source_id], state.diagnostics, state.symbols, state.token_allocator)
|
||||||
|
|||||||
+157
-127
@@ -2,6 +2,7 @@ package lower
|
|||||||
|
|
||||||
import "../hir"
|
import "../hir"
|
||||||
import "../ir"
|
import "../ir"
|
||||||
|
import "../source"
|
||||||
import "../types"
|
import "../types"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
@@ -11,6 +12,7 @@ State :: struct {
|
|||||||
instructions: [dynamic]ir.Instruction,
|
instructions: [dynamic]ir.Instruction,
|
||||||
local_values: []int,
|
local_values: []int,
|
||||||
local_slots: []int,
|
local_slots: []int,
|
||||||
|
expr_stack: [dynamic]Lower_Expr_Frame,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,142 +37,25 @@ sentinel :: proc(value_type: types.Type) -> i64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lower_expr :: proc(state: ^State, expr_id: int) -> int {
|
append_recovery_value :: proc(state: ^State, span: source.Span, value_type: types.Type, diagnostic := -1) -> int {
|
||||||
if expr_id < 0 || expr_id >= len(state.hir_module.exprs) {
|
|
||||||
trap := append_instruction(state, ir.Instruction{
|
|
||||||
op=.Trap,
|
|
||||||
type=types.VOID,
|
|
||||||
target=-1,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
_ = trap
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Const,
|
|
||||||
type=types.I64,
|
|
||||||
integer=sentinel(types.I64),
|
|
||||||
target=-1,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
expr := state.hir_module.exprs[expr_id]
|
|
||||||
switch expr.kind {
|
|
||||||
case .Invalid:
|
|
||||||
append_instruction(state, ir.Instruction{
|
|
||||||
op=.Trap,
|
|
||||||
span=expr.span,
|
|
||||||
type=types.VOID,
|
|
||||||
target=-1,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=expr.diagnostic,
|
|
||||||
})
|
|
||||||
fallback := expr.type
|
|
||||||
if !types.is_concrete_integer(fallback) {
|
|
||||||
fallback = types.I64
|
|
||||||
}
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Const,
|
|
||||||
span=expr.span,
|
|
||||||
type=fallback,
|
|
||||||
integer=sentinel(fallback),
|
|
||||||
target=-1,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
case .Integer:
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Const,
|
|
||||||
span=expr.span,
|
|
||||||
type=expr.type,
|
|
||||||
integer=expr.integer,
|
|
||||||
target=-1,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
case .Local:
|
|
||||||
if expr.target >= 0 && expr.target < len(state.local_slots) && state.local_slots[expr.target] >= 0 {
|
|
||||||
return 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if expr.target >= 0 && expr.target < len(state.local_values) {
|
|
||||||
return state.local_values[expr.target]
|
|
||||||
}
|
|
||||||
case .Global:
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Load_Global,
|
|
||||||
span=expr.span,
|
|
||||||
type=expr.type,
|
|
||||||
target=expr.target,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
case .Widen:
|
|
||||||
value := lower_expr(state, expr.left)
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Widen,
|
|
||||||
span=expr.span,
|
|
||||||
type=expr.type,
|
|
||||||
target=-1,
|
|
||||||
a=value,
|
|
||||||
b=-1,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
case .Add:
|
|
||||||
left := lower_expr(state, expr.left)
|
|
||||||
right := lower_expr(state, expr.right)
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Add_Checked,
|
|
||||||
span=expr.span,
|
|
||||||
type=expr.type,
|
|
||||||
target=-1,
|
|
||||||
a=left,
|
|
||||||
b=right,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
case .Call:
|
|
||||||
args := make([]int, len(expr.args), state.allocator)
|
|
||||||
for arg, index in expr.args {
|
|
||||||
args[index] = lower_expr(state, arg)
|
|
||||||
}
|
|
||||||
return append_instruction(state, ir.Instruction{
|
|
||||||
op=.Call,
|
|
||||||
span=expr.span,
|
|
||||||
type=expr.type,
|
|
||||||
target=expr.target,
|
|
||||||
a=-1,
|
|
||||||
b=-1,
|
|
||||||
args=args,
|
|
||||||
diagnostic=-1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Trap,
|
op=.Trap,
|
||||||
span=expr.span,
|
span=span,
|
||||||
type=types.VOID,
|
type=types.VOID,
|
||||||
target=-1,
|
target=-1,
|
||||||
a=-1,
|
a=-1,
|
||||||
b=-1,
|
b=-1,
|
||||||
diagnostic=expr.diagnostic,
|
diagnostic=diagnostic,
|
||||||
})
|
})
|
||||||
|
fallback := value_type
|
||||||
|
if !types.is_concrete_integer(fallback) {
|
||||||
|
fallback = types.I64
|
||||||
|
}
|
||||||
return append_instruction(state, ir.Instruction{
|
return append_instruction(state, ir.Instruction{
|
||||||
op=.Const,
|
op=.Const,
|
||||||
span=expr.span,
|
span=span,
|
||||||
type=types.I64,
|
type=fallback,
|
||||||
integer=sentinel(types.I64),
|
integer=sentinel(fallback),
|
||||||
target=-1,
|
target=-1,
|
||||||
a=-1,
|
a=-1,
|
||||||
b=-1,
|
b=-1,
|
||||||
@@ -178,6 +63,133 @@ lower_expr :: proc(state: ^State, expr_id: int) -> int {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lower_Expr_Frame :: struct {
|
||||||
|
expr: int,
|
||||||
|
stage: u8,
|
||||||
|
left: int,
|
||||||
|
arg_index: int,
|
||||||
|
args: []int,
|
||||||
|
}
|
||||||
|
|
||||||
|
lower_expr :: proc(state: ^State, expr_id: int) -> int {
|
||||||
|
stack := state.expr_stack
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
defer {
|
||||||
|
for frame in stack {
|
||||||
|
delete(frame.args, state.allocator)
|
||||||
|
}
|
||||||
|
clear_dynamic_array(&stack)
|
||||||
|
state.expr_stack = stack
|
||||||
|
}
|
||||||
|
append(&stack, Lower_Expr_Frame{expr=expr_id})
|
||||||
|
last := -1
|
||||||
|
for len(stack) > 0 {
|
||||||
|
frame_index := len(stack)-1
|
||||||
|
frame := stack[frame_index]
|
||||||
|
if frame.expr < 0 || frame.expr >= len(state.hir_module.exprs) {
|
||||||
|
last = append_recovery_value(state, source.Span{}, types.I64)
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expr := state.hir_module.exprs[frame.expr]
|
||||||
|
if frame.stage == 0 {
|
||||||
|
switch expr.kind {
|
||||||
|
case .Invalid:
|
||||||
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||||
|
_ = pop(&stack)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Local:
|
||||||
|
last = -1
|
||||||
|
if expr.target >= 0 && expr.target < len(state.local_slots) && state.local_slots[expr.target] >= 0 {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
} else if expr.target >= 0 && expr.target < len(state.local_values) &&
|
||||||
|
state.local_values[expr.target] >= 0 {
|
||||||
|
last = state.local_values[expr.target]
|
||||||
|
}
|
||||||
|
if last < 0 {
|
||||||
|
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) {
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ = pop(&stack)
|
||||||
|
case .Widen:
|
||||||
|
stack[frame_index].stage = 1
|
||||||
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
|
case .Add:
|
||||||
|
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) {
|
||||||
|
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].stage = 4
|
||||||
|
if len(expr.args) > 0 {
|
||||||
|
append(&stack, Lower_Expr_Frame{expr=expr.args[0]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 2 {
|
||||||
|
stack[frame_index].left = last
|
||||||
|
stack[frame_index].stage = 3
|
||||||
|
append(&stack, Lower_Expr_Frame{expr=expr.right})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
_ = pop(&stack)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if frame.stage == 4 {
|
||||||
|
if frame.arg_index < len(expr.args) {
|
||||||
|
stack[frame_index].args[frame.arg_index] = last
|
||||||
|
stack[frame_index].arg_index += 1
|
||||||
|
if frame.arg_index+1 < len(expr.args) {
|
||||||
|
append(&stack, Lower_Expr_Frame{expr=expr.args[frame.arg_index+1]})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
stack[frame_index].args = nil
|
||||||
|
_ = pop(&stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction {
|
lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction {
|
||||||
state := State{
|
state := State{
|
||||||
hir_module=hir_module,
|
hir_module=hir_module,
|
||||||
@@ -186,9 +198,11 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
|||||||
local_slots=make([]int, len(function.locals), allocator),
|
local_slots=make([]int, len(function.locals), allocator),
|
||||||
}
|
}
|
||||||
state.instructions.allocator = allocator
|
state.instructions.allocator = allocator
|
||||||
|
state.expr_stack.allocator = allocator
|
||||||
defer {
|
defer {
|
||||||
delete(state.local_values, allocator)
|
delete(state.local_values, allocator)
|
||||||
delete(state.local_slots, allocator)
|
delete(state.local_slots, allocator)
|
||||||
|
delete(state.expr_stack)
|
||||||
}
|
}
|
||||||
for _, index in state.local_values {
|
for _, index in state.local_values {
|
||||||
state.local_values[index] = -1
|
state.local_values[index] = -1
|
||||||
@@ -211,6 +225,13 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
|||||||
switch statement.kind {
|
switch statement.kind {
|
||||||
case .Declaration:
|
case .Declaration:
|
||||||
value := lower_expr(&state, statement.expr)
|
value := lower_expr(&state, statement.expr)
|
||||||
|
if statement.local < 0 || 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,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
local := function.locals[statement.local]
|
local := function.locals[statement.local]
|
||||||
if local.mutable {
|
if local.mutable {
|
||||||
slot := append_instruction(&state, ir.Instruction{
|
slot := append_instruction(&state, ir.Instruction{
|
||||||
@@ -241,6 +262,13 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
|||||||
if statement.local >= 0 && statement.local < len(state.local_slots) {
|
if statement.local >= 0 && statement.local < len(state.local_slots) {
|
||||||
slot = state.local_slots[statement.local]
|
slot = state.local_slots[statement.local]
|
||||||
}
|
}
|
||||||
|
if slot < 0 || statement.local < 0 || 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,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
append_instruction(&state, ir.Instruction{
|
append_instruction(&state, ir.Instruction{
|
||||||
op=.Store,
|
op=.Store,
|
||||||
span=statement.span,
|
span=statement.span,
|
||||||
@@ -311,6 +339,8 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
|||||||
lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, allocator: mem.Allocator) -> []ir.Instruction {
|
lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, allocator: mem.Allocator) -> []ir.Instruction {
|
||||||
state := State{hir_module=hir_module, allocator=allocator}
|
state := State{hir_module=hir_module, allocator=allocator}
|
||||||
state.instructions.allocator = allocator
|
state.instructions.allocator = allocator
|
||||||
|
state.expr_stack.allocator = allocator
|
||||||
|
defer delete(state.expr_stack)
|
||||||
value := lower_expr(&state, global.expr)
|
value := lower_expr(&state, global.expr)
|
||||||
append_instruction(&state, ir.Instruction{
|
append_instruction(&state, ir.Instruction{
|
||||||
op=.Return,
|
op=.Return,
|
||||||
|
|||||||
+69
-10
@@ -19,6 +19,8 @@ Parser :: struct {
|
|||||||
delimiter_depth: int,
|
delimiter_depth: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MAX_EXPRESSION_NESTING :: 256
|
||||||
|
|
||||||
token_text :: proc(parser: ^Parser, tok: token.Token) -> string {
|
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.start < 0 || tok.span.end < tok.span.start || tok.span.end > len(parser.source_file.text) {
|
||||||
return ""
|
return ""
|
||||||
@@ -110,7 +112,30 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
return .Invalid
|
return .Invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token) -> int {
|
skip_parenthesized :: proc(parser: ^Parser) -> source.Span {
|
||||||
|
start := current(parser)
|
||||||
|
depth := 0
|
||||||
|
end := start
|
||||||
|
for current(parser).kind != .Eof {
|
||||||
|
tok := advance(parser)
|
||||||
|
end = tok
|
||||||
|
if tok.kind == .Left_Paren {
|
||||||
|
depth += 1
|
||||||
|
} else if tok.kind == .Right_Paren {
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return span_from(start.span, end.span)
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Token, nesting: int) -> int {
|
||||||
|
if nesting >= MAX_EXPRESSION_NESTING {
|
||||||
|
span := skip_parenthesized(parser)
|
||||||
|
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
|
||||||
|
}
|
||||||
left_paren := advance(parser)
|
left_paren := advance(parser)
|
||||||
parser.delimiter_depth += 1
|
parser.delimiter_depth += 1
|
||||||
defer parser.delimiter_depth -= 1
|
defer parser.delimiter_depth -= 1
|
||||||
@@ -118,7 +143,7 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
|
|||||||
args.allocator = parser.module.allocator
|
args.allocator = parser.module.allocator
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
|
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
|
||||||
append(&args, parse_expression(parser))
|
append(&args, parse_expression_bp(parser, 0, nesting+1))
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if _, ok := allow(parser, .Comma); ok {
|
if _, ok := allow(parser, .Comma); ok {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
@@ -143,7 +168,7 @@ parse_call :: proc(parser: ^Parser, qualifier: symbol.Id, first, name: token.Tok
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_primary :: proc(parser: ^Parser) -> int {
|
parse_primary :: proc(parser: ^Parser, nesting: int) -> int {
|
||||||
tok := current(parser)
|
tok := current(parser)
|
||||||
#partial switch tok.kind {
|
#partial switch tok.kind {
|
||||||
case .Integer:
|
case .Integer:
|
||||||
@@ -172,7 +197,7 @@ parse_primary :: proc(parser: ^Parser) -> int {
|
|||||||
name = advance(parser)
|
name = advance(parser)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Left_Paren {
|
if current(parser).kind == .Left_Paren {
|
||||||
return parse_call(parser, qualifier, first, name)
|
return parse_call(parser, qualifier, first, name, nesting)
|
||||||
}
|
}
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
kind=.Name,
|
kind=.Name,
|
||||||
@@ -187,11 +212,15 @@ parse_primary :: proc(parser: ^Parser) -> int {
|
|||||||
advance(parser)
|
advance(parser)
|
||||||
return invalid_expr(parser, tok.span, "'_' is a write-only sink and cannot be read")
|
return invalid_expr(parser, tok.span, "'_' is a write-only sink and cannot be read")
|
||||||
case .Left_Paren:
|
case .Left_Paren:
|
||||||
|
if nesting >= MAX_EXPRESSION_NESTING {
|
||||||
|
span := skip_parenthesized(parser)
|
||||||
|
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
|
||||||
|
}
|
||||||
advance(parser)
|
advance(parser)
|
||||||
parser.delimiter_depth += 1
|
parser.delimiter_depth += 1
|
||||||
defer parser.delimiter_depth -= 1
|
defer parser.delimiter_depth -= 1
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
expr := parse_expression(parser)
|
expr := parse_expression_bp(parser, 0, nesting+1)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
if _, ok := allow(parser, .Right_Paren); !ok {
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected ')'")
|
source.add(parser.diagnostics, current(parser).span, "expected ')'")
|
||||||
@@ -213,15 +242,34 @@ parse_primary :: proc(parser: ^Parser) -> int {
|
|||||||
return invalid_expr(parser, tok.span, "expected an expression")
|
return invalid_expr(parser, tok.span, "expected an expression")
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_expression :: proc(parser: ^Parser) -> int {
|
infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
||||||
left := parse_primary(parser)
|
#partial switch kind {
|
||||||
|
case .Plus:
|
||||||
|
return 10, 11, true
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int) -> int {
|
||||||
|
if nesting > MAX_EXPRESSION_NESTING {
|
||||||
|
tok := current(parser)
|
||||||
|
if tok.kind != .Newline && tok.kind != .Right_Brace && tok.kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
return invalid_expr(parser, tok.span, "expression nesting exceeds 256 levels")
|
||||||
|
}
|
||||||
|
left := parse_primary(parser, nesting)
|
||||||
if parser.delimiter_depth > 0 {
|
if parser.delimiter_depth > 0 {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
}
|
}
|
||||||
for current(parser).kind == .Plus {
|
for {
|
||||||
|
left_power, right_power, ok := infix_binding_power(current(parser).kind)
|
||||||
|
if !ok || left_power < minimum_binding_power {
|
||||||
|
break
|
||||||
|
}
|
||||||
advance(parser)
|
advance(parser)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
right := parse_primary(parser)
|
right := parse_expression_bp(parser, right_power, nesting+1)
|
||||||
left_expr := parser.module.exprs[left]
|
left_expr := parser.module.exprs[left]
|
||||||
right_expr := parser.module.exprs[right]
|
right_expr := parser.module.exprs[right]
|
||||||
left = add_expr(parser, ast.Expr{
|
left = add_expr(parser, ast.Expr{
|
||||||
@@ -238,6 +286,10 @@ parse_expression :: proc(parser: ^Parser) -> int {
|
|||||||
return left
|
return left
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_expression :: proc(parser: ^Parser) -> int {
|
||||||
|
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) -> int {
|
||||||
if current(parser).kind == .Newline {
|
if current(parser).kind == .Newline {
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
@@ -536,10 +588,17 @@ parse_top_level :: proc(parser: ^Parser) {
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
|
||||||
c_abi := false
|
c_abi := false
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_C {
|
if operator.kind == .Colon_Colon &&
|
||||||
|
current(parser).kind == .Identifier &&
|
||||||
|
token_text(parser, current(parser)) == "c" {
|
||||||
|
saved := parser.cursor
|
||||||
c_abi = true
|
c_abi = true
|
||||||
advance(parser)
|
advance(parser)
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
if current(parser).kind != .Keyword_Func {
|
||||||
|
c_abi = false
|
||||||
|
parser.cursor = saved
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Func {
|
if operator.kind == .Colon_Colon && current(parser).kind == .Keyword_Func {
|
||||||
parse_function(parser, name, c_abi)
|
parse_function(parser, name, c_abi)
|
||||||
|
|||||||
+62
-15
@@ -10,9 +10,10 @@ Span :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Source :: struct {
|
Source :: struct {
|
||||||
id: int,
|
id: int,
|
||||||
path: string,
|
path: string,
|
||||||
text: string,
|
text: string,
|
||||||
|
line_starts: []int,
|
||||||
}
|
}
|
||||||
|
|
||||||
Store :: struct {
|
Store :: struct {
|
||||||
@@ -29,9 +30,15 @@ Diagnostics :: struct {
|
|||||||
source: ^Source,
|
source: ^Source,
|
||||||
store: ^Store,
|
store: ^Store,
|
||||||
items: [dynamic]Diagnostic,
|
items: [dynamic]Diagnostic,
|
||||||
|
index: map[Diagnostic_Key]int,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Diagnostic_Key :: struct {
|
||||||
|
span: Span,
|
||||||
|
message: string,
|
||||||
|
}
|
||||||
|
|
||||||
init_store :: proc(allocator := context.allocator) -> Store {
|
init_store :: proc(allocator := context.allocator) -> Store {
|
||||||
result: Store
|
result: Store
|
||||||
result.allocator = allocator
|
result.allocator = allocator
|
||||||
@@ -43,25 +50,47 @@ destroy_store :: proc(store: ^Store) {
|
|||||||
for item in store.items {
|
for item in store.items {
|
||||||
delete(item.path, store.allocator)
|
delete(item.path, store.allocator)
|
||||||
delete(item.text, store.allocator)
|
delete(item.text, store.allocator)
|
||||||
|
delete(item.line_starts, store.allocator)
|
||||||
}
|
}
|
||||||
delete(store.items)
|
delete(store.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
add_source :: proc(store: ^Store, path, text: string) -> int {
|
make_line_starts :: proc(text: string, allocator: mem.Allocator) -> []int {
|
||||||
|
result: [dynamic]int
|
||||||
|
result.allocator = allocator
|
||||||
|
append(&result, 0)
|
||||||
|
for value, offset in transmute([]byte)text {
|
||||||
|
if value == '\n' {
|
||||||
|
append(&result, offset+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
add_source_owned :: proc(store: ^Store, path: string, text: []byte) -> int {
|
||||||
id := len(store.items)
|
id := len(store.items)
|
||||||
|
owned_text := string(text)
|
||||||
append(&store.items, Source{
|
append(&store.items, Source{
|
||||||
id=id,
|
id=id,
|
||||||
path=fmt.aprintf("%s", path, allocator=store.allocator),
|
path=fmt.aprintf("%s", path, allocator=store.allocator),
|
||||||
text=fmt.aprintf("%s", text, allocator=store.allocator),
|
text=owned_text,
|
||||||
|
line_starts=make_line_starts(owned_text, store.allocator),
|
||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_source :: proc(store: ^Store, path, text: string) -> int {
|
||||||
|
owned := make([]byte, len(text), store.allocator)
|
||||||
|
copy(owned, transmute([]byte)text)
|
||||||
|
return add_source_owned(store, path, owned)
|
||||||
|
}
|
||||||
|
|
||||||
init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -> Diagnostics {
|
init_diagnostics :: proc(source_file: ^Source, allocator := context.allocator) -> Diagnostics {
|
||||||
result: Diagnostics
|
result: Diagnostics
|
||||||
result.source = source_file
|
result.source = source_file
|
||||||
result.allocator = allocator
|
result.allocator = allocator
|
||||||
result.items.allocator = allocator
|
result.items.allocator = allocator
|
||||||
|
result.index.allocator = allocator
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +99,12 @@ init_store_diagnostics :: proc(store: ^Store, allocator := context.allocator) ->
|
|||||||
result.store = store
|
result.store = store
|
||||||
result.allocator = allocator
|
result.allocator = allocator
|
||||||
result.items.allocator = allocator
|
result.items.allocator = allocator
|
||||||
|
result.index.allocator = allocator
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
||||||
|
delete(diagnostics.index)
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
delete(diagnostic.message, diagnostics.allocator)
|
delete(diagnostic.message, diagnostics.allocator)
|
||||||
}
|
}
|
||||||
@@ -81,34 +112,50 @@ destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int {
|
add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int {
|
||||||
for diagnostic, id in diagnostics.items {
|
key := Diagnostic_Key{span=span, message=message}
|
||||||
if diagnostic.span == span && diagnostic.message == message {
|
if id, ok := diagnostics.index[key]; ok {
|
||||||
return id
|
return id
|
||||||
}
|
|
||||||
}
|
}
|
||||||
id := len(diagnostics.items)
|
id := len(diagnostics.items)
|
||||||
cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator)
|
cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator)
|
||||||
append(&diagnostics.items, Diagnostic{span=span, message=cloned})
|
append(&diagnostics.items, Diagnostic{span=span, message=cloned})
|
||||||
|
diagnostics.index[Diagnostic_Key{span=span, message=cloned}] = id
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int {
|
addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int {
|
||||||
message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator)
|
message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator)
|
||||||
for diagnostic, id in diagnostics.items {
|
key := Diagnostic_Key{span=span, message=message}
|
||||||
if diagnostic.span == span && diagnostic.message == message {
|
if id, ok := diagnostics.index[key]; ok {
|
||||||
delete(message, diagnostics.allocator)
|
delete(message, diagnostics.allocator)
|
||||||
return id
|
return id
|
||||||
}
|
|
||||||
}
|
}
|
||||||
id := len(diagnostics.items)
|
id := len(diagnostics.items)
|
||||||
append(&diagnostics.items, Diagnostic{span=span, message=message})
|
append(&diagnostics.items, Diagnostic{span=span, message=message})
|
||||||
|
diagnostics.index[Diagnostic_Key{span=span, message=message}] = id
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) {
|
line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) {
|
||||||
|
if len(source_file.line_starts) > 0 {
|
||||||
|
limit := min(max(offset, 0), 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 {
|
||||||
|
low = middle+1
|
||||||
|
} else {
|
||||||
|
high = middle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line = max(low, 1)
|
||||||
|
column = limit-source_file.line_starts[line-1]+1
|
||||||
|
return
|
||||||
|
}
|
||||||
line = 1
|
line = 1
|
||||||
column = 1
|
column = 1
|
||||||
limit := min(offset, len(source_file.text))
|
limit := min(max(offset, 0), len(source_file.text))
|
||||||
for byte_value in transmute([]byte)source_file.text[:limit] {
|
for byte_value in transmute([]byte)source_file.text[:limit] {
|
||||||
if byte_value == '\n' {
|
if byte_value == '\n' {
|
||||||
line += 1
|
line += 1
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Kind :: enum {
|
|||||||
Left_Brace,
|
Left_Brace,
|
||||||
Right_Brace,
|
Right_Brace,
|
||||||
Comma,
|
Comma,
|
||||||
Keyword_C,
|
|
||||||
Keyword_Func,
|
Keyword_Func,
|
||||||
Keyword_Import,
|
Keyword_Import,
|
||||||
Keyword_Return,
|
Keyword_Return,
|
||||||
|
|||||||
+255
-2
@@ -176,6 +176,113 @@ main :: func() void {}
|
|||||||
testing.expect(t, module.functions[3].has_body)
|
testing.expect(t, module.functions[3].has_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
|
||||||
|
text := `c :: 5
|
||||||
|
x :: c
|
||||||
|
foreign :: c func() i32
|
||||||
|
broken :: c 5
|
||||||
|
main :: func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
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" {
|
||||||
|
testing.expect_value(t, tok.kind, token.Kind.Identifier)
|
||||||
|
testing.expect_value(t, tok.symbol, c_symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(module.globals), 3)
|
||||||
|
testing.expect_value(t, module.exprs[module.globals[1].expr].name, c_symbol)
|
||||||
|
testing.expect_value(t, module.exprs[module.globals[2].expr].name, c_symbol)
|
||||||
|
testing.expect(t, module.functions[0].c_abi)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 1)
|
||||||
|
testing.expect(t, strings.contains(diagnostics.items[0].message, "followed by a newline"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
pratt_parser_preserves_left_associative_addition_shape :: proc(t: ^testing.T) {
|
||||||
|
source_file := source.Source{path="test.bro", text="value :: 1 + 2 + 3\nmain :: func() void {}\n"}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
root := module.exprs[module.globals[0].expr]
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, root.kind, ast.Expr_Kind.Add)
|
||||||
|
testing.expect_value(t, module.exprs[root.left].kind, ast.Expr_Kind.Add)
|
||||||
|
testing.expect_value(t, module.exprs[root.right].integer, i64(3))
|
||||||
|
}
|
||||||
|
|
||||||
|
nested_expression_source :: proc(call: bool, depth: int) -> string {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
if call {
|
||||||
|
strings.write_string(&builder, "identity :: func(value i32) i32 { return value }\nvalue :: ")
|
||||||
|
for _ in 0..<depth {
|
||||||
|
strings.write_string(&builder, "identity(")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strings.write_string(&builder, "value :: ")
|
||||||
|
for _ in 0..<depth {
|
||||||
|
strings.write_byte(&builder, '(')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strings.write_byte(&builder, '1')
|
||||||
|
for _ in 0..<depth {
|
||||||
|
strings.write_byte(&builder, ')')
|
||||||
|
}
|
||||||
|
strings.write_string(&builder, "\nmain :: func() void {}\n")
|
||||||
|
return strings.clone(strings.to_string(builder))
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_nesting_result :: proc(text: string) -> (count: int, found_budget: bool) {
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found_budget = found_budget || strings.contains(diagnostic.message, "expression nesting exceeds 256 levels")
|
||||||
|
}
|
||||||
|
return len(diagnostics.items), found_budget
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
parser_enforces_explicit_expression_nesting_budget :: proc(t: ^testing.T) {
|
||||||
|
modes := [?]bool{false, true}
|
||||||
|
for call in modes {
|
||||||
|
at_limit := nested_expression_source(call, parser.MAX_EXPRESSION_NESTING)
|
||||||
|
defer delete(at_limit)
|
||||||
|
count, found := parse_nesting_result(at_limit)
|
||||||
|
testing.expect_value(t, count, 0)
|
||||||
|
testing.expect(t, !found)
|
||||||
|
|
||||||
|
over_limit := nested_expression_source(call, parser.MAX_EXPRESSION_NESTING+1)
|
||||||
|
defer delete(over_limit)
|
||||||
|
_, found = parse_nesting_result(over_limit)
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) {
|
cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||||
options, valid := parse_cli_args([]string{
|
options, valid := parse_cli_args([]string{
|
||||||
@@ -1078,8 +1185,6 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
|||||||
|
|
||||||
expected := []string{
|
expected := []string{
|
||||||
"/usr/bin/env",
|
"/usr/bin/env",
|
||||||
"ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache",
|
|
||||||
"ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache",
|
|
||||||
"zig",
|
"zig",
|
||||||
"cc",
|
"cc",
|
||||||
"-Wno-override-module",
|
"-Wno-override-module",
|
||||||
@@ -1097,6 +1202,154 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
source_store_owns_buffers_indexes_lines_and_deduplicates_diagnostics :: proc(t: ^testing.T) {
|
||||||
|
store := source.init_store()
|
||||||
|
defer source.destroy_store(&store)
|
||||||
|
bytes := make([]byte, len("one\ntwo\n"))
|
||||||
|
copy(bytes, "one\ntwo\n")
|
||||||
|
source_id := source.add_source_owned(&store, "owned.bro", bytes)
|
||||||
|
bytes[0] = 'O'
|
||||||
|
|
||||||
|
diagnostics := source.init_store_diagnostics(&store)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
span := source.Span{file=source_id, start=4, end=7}
|
||||||
|
first := source.add(&diagnostics, span, "same")
|
||||||
|
second := source.addf(&diagnostics, span, "%s", "same")
|
||||||
|
other := source.add(&diagnostics, span, "other")
|
||||||
|
formatted := source.format(&diagnostics, other)
|
||||||
|
defer delete(formatted)
|
||||||
|
|
||||||
|
testing.expect_value(t, store.items[source_id].text, "One\ntwo\n")
|
||||||
|
testing.expect_value(t, len(store.items[source_id].line_starts), 3)
|
||||||
|
testing.expect_value(t, first, second)
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 2)
|
||||||
|
testing.expect(t, strings.contains(formatted, "owned.bro:2:1:"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
maximum_signed_i64_literal_parses_exactly :: proc(t: ^testing.T) {
|
||||||
|
source_file := source.Source{path="test.bro", text="value :: 9223372036854775807\nmain :: func() void {}\n"}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect_value(t, module.exprs[module.globals[0].expr].integer, i64(9223372036854775807))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(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}
|
||||||
|
append(&module.functions, ir.Function{
|
||||||
|
link_name=strings.clone("main"),
|
||||||
|
calling_convention=.C,
|
||||||
|
implementation=.Definition,
|
||||||
|
linkage=.External,
|
||||||
|
is_main=true,
|
||||||
|
result=types.I32,
|
||||||
|
instructions=instructions,
|
||||||
|
})
|
||||||
|
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(&module, &diagnostics, &symbols)
|
||||||
|
defer delete(text)
|
||||||
|
|
||||||
|
testing.expect(t, strings.contains(text, "call void @bro.trap"))
|
||||||
|
testing.expect(t, strings.contains(text, "%v0 = add i8 0, -86"))
|
||||||
|
testing.expect(t, strings.contains(text, "%v1 = add i32 0, -1431655766"))
|
||||||
|
testing.expect(t, strings.contains(text, "@bro.trap(ptr %message, i64 %length) noreturn"))
|
||||||
|
testing.expect(t, !strings.contains(text, "%v-1"))
|
||||||
|
llvm_path := "/tmp/brolang-test-malformed-recovery.ll"
|
||||||
|
output := "/tmp/brolang-test-malformed-recovery"
|
||||||
|
defer _ = os.remove(llvm_path)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.write_entire_file(llvm_path, transmute([]byte)text))
|
||||||
|
testing.expect(t, backend.compile(llvm_path, output))
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
hundred_thousand_term_runtime_addition_uses_iterative_pipeline :: proc(t: ^testing.T) {
|
||||||
|
builder := strings.builder_make()
|
||||||
|
defer strings.builder_destroy(&builder)
|
||||||
|
strings.write_string(&builder, "sum :: func(value i32) i32 { return value")
|
||||||
|
for _ in 0..<100_000 {
|
||||||
|
strings.write_string(&builder, " + 1")
|
||||||
|
}
|
||||||
|
strings.write_string(&builder, " }\nmain :: func() void { _ = sum(0) }\n")
|
||||||
|
source_file := source.Source{path="test.bro", text=strings.to_string(builder)}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
ir_module := lower.lower(&hir_module)
|
||||||
|
defer ir.destroy_module(&ir_module)
|
||||||
|
|
||||||
|
instruction_count := 0
|
||||||
|
for function in ir_module.functions {
|
||||||
|
instruction_count += len(function.instructions)
|
||||||
|
}
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, instruction_count > 100_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
deep_global_cycle_detection_uses_iterative_dfs :: proc(t: ^testing.T) {
|
||||||
|
count := 50_000
|
||||||
|
ast_module := ast.init_module()
|
||||||
|
defer ast.destroy_module(&ast_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)
|
||||||
|
name := symbol.intern(&symbols, "value")
|
||||||
|
state := checker.Checker{
|
||||||
|
ast_module=&ast_module,
|
||||||
|
diagnostics=&diagnostics,
|
||||||
|
symbols=&symbols,
|
||||||
|
module=hir.init_module(),
|
||||||
|
allocator=context.allocator,
|
||||||
|
}
|
||||||
|
defer hir.destroy_module(&state.module)
|
||||||
|
defer delete(state.cycle_stack)
|
||||||
|
|
||||||
|
for id in 0..<count {
|
||||||
|
append(&ast_module.globals, ast.Global{name=name})
|
||||||
|
dependencies: [dynamic]int
|
||||||
|
dependencies.allocator = context.allocator
|
||||||
|
append(&dependencies, (id+1)%count)
|
||||||
|
append(&state.module.globals, hir.Global{name=name, dependencies=dependencies})
|
||||||
|
}
|
||||||
|
states := make([]u8, count)
|
||||||
|
defer delete(states)
|
||||||
|
checker.detect_global_cycles_visit(&state, 0, states)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 1)
|
||||||
|
for global in state.module.globals {
|
||||||
|
testing.expect(t, global.problematic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-mutable"
|
output := "/tmp/brolang-test-mutable"
|
||||||
|
|||||||
Reference in New Issue
Block a user