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
|
||||
|
||||
# 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
|
||||
|
||||
1. interop type foundation
|
||||
|
||||
@@ -25,3 +25,7 @@ Results captured on 2026-06-10 with Odin `dev-2026-02:b942f72cb`:
|
||||
| Diagnostics | 0 | 0 |
|
||||
|
||||
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.allocator = 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, "cc", allocator)
|
||||
append_owned(&command, "-Wno-override-module", allocator)
|
||||
|
||||
+490
-319
@@ -60,6 +60,11 @@ Checker :: struct {
|
||||
global_types: []types.Type,
|
||||
constants: []Constant,
|
||||
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,
|
||||
sink_symbol: symbol.Id,
|
||||
allocator: mem.Allocator,
|
||||
@@ -69,38 +74,75 @@ symbol_text :: proc(checker: ^Checker, id: symbol.Id) -> string {
|
||||
return symbol.resolve(checker.symbols, id)
|
||||
}
|
||||
|
||||
Constant_Frame :: struct {
|
||||
expr: int,
|
||||
stage: u8,
|
||||
}
|
||||
|
||||
eval_constant :: proc(checker: ^Checker, expr_id: int) -> Constant {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
return Constant{kind = .Not_Constant}
|
||||
}
|
||||
if checker.constants[expr_id].kind != .Unknown {
|
||||
return checker.constants[expr_id]
|
||||
stack := checker.constant_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.constant_stack = stack
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
result: Constant
|
||||
switch expr.kind {
|
||||
case .Integer:
|
||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||
case .Add:
|
||||
left := eval_constant(checker, expr.left)
|
||||
right := eval_constant(checker, expr.right)
|
||||
append(&stack, Constant_Frame{expr=expr_id})
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if checker.constants[frame.expr].kind != .Unknown {
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if expr.kind != .Add {
|
||||
result := Constant{kind = .Not_Constant}
|
||||
if expr.kind == .Integer {
|
||||
result = Constant{kind = .Value, value = i128(expr.integer)}
|
||||
}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if frame.stage == 0 {
|
||||
stack[frame_index].stage = 1
|
||||
if expr.left >= 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 {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else if left.kind != .Value || right.kind != .Value {
|
||||
result = Constant{kind = .Not_Constant}
|
||||
} else {
|
||||
} else if left.kind == .Value && right.kind == .Value {
|
||||
value, overflow := intrinsics.overflow_add(left.value, right.value)
|
||||
if overflow {
|
||||
result = Constant{kind = .Overflow}
|
||||
} else {
|
||||
result = Constant{kind = .Value, value = value}
|
||||
}
|
||||
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
|
||||
}
|
||||
case .Invalid, .Name, .Call:
|
||||
result = Constant{kind = .Not_Constant}
|
||||
checker.constants[frame.expr] = result
|
||||
_ = pop(&stack)
|
||||
}
|
||||
checker.constants[expr_id] = result
|
||||
return result
|
||||
return checker.constants[expr_id]
|
||||
}
|
||||
|
||||
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) {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
return
|
||||
stack := checker.expr_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.expr_stack = stack
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
switch expr.kind {
|
||||
case .Name:
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
append(&stack, expr_id)
|
||||
for len(stack) > 0 {
|
||||
id := pop(&stack)
|
||||
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)
|
||||
}
|
||||
case .Call:
|
||||
if symbol.is_valid(expr.qualifier) {
|
||||
_ = find_import(checker, file, expr.qualifier, true)
|
||||
switch expr.kind {
|
||||
case .Call:
|
||||
append(&stack, ..expr.args)
|
||||
case .Add:
|
||||
append(&stack, expr.left, expr.right)
|
||||
case .Invalid, .Integer, .Name:
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
return types.INVALID
|
||||
stack := checker.infer_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
for frame in stack {
|
||||
delete(frame.args, checker.allocator)
|
||||
}
|
||||
clear_dynamic_array(&stack)
|
||||
checker.infer_stack = stack
|
||||
}
|
||||
constant := eval_constant(checker, expr_id)
|
||||
if constant.kind == .Overflow || (constant.kind == .Value && !fits_i64(constant.value)) {
|
||||
return types.I64
|
||||
append(&stack, Infer_Frame{expr=expr_id, template=-1})
|
||||
last := types.INVALID
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
|
||||
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 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
|
||||
return last
|
||||
}
|
||||
|
||||
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(
|
||||
checker: ^Checker,
|
||||
expr_id: int,
|
||||
@@ -762,173 +876,190 @@ build_expr :: proc(
|
||||
pkg := 0,
|
||||
file := 0,
|
||||
) -> int {
|
||||
if expr_id < 0 || expr_id >= len(checker.ast_module.exprs) {
|
||||
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
||||
return invalid_hir_expr(checker, source.Span{}, id)
|
||||
stack := checker.build_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
for frame in stack {
|
||||
delete(frame.built_args, checker.allocator)
|
||||
delete(frame.arg_types, checker.allocator)
|
||||
}
|
||||
clear_dynamic_array(&stack)
|
||||
checker.build_stack = stack
|
||||
}
|
||||
expr := checker.ast_module.exprs[expr_id]
|
||||
constant := eval_constant(checker, expr_id)
|
||||
if constant.kind == .Value || constant.kind == .Overflow {
|
||||
return build_constant_expr(checker, expr, constant, expected)
|
||||
}
|
||||
switch expr.kind {
|
||||
case .Invalid:
|
||||
return invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
||||
case .Integer:
|
||||
unreachable()
|
||||
case .Name:
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if local, ok := find_build_local(locals, expr.name); ok {
|
||||
return add_hir_expr(
|
||||
append(&stack, Build_Expr_Frame{expr=expr_id, expected=expected, template=-1})
|
||||
last := -1
|
||||
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := stack[frame_index]
|
||||
if frame.expr < 0 || frame.expr >= len(checker.ast_module.exprs) {
|
||||
id := source.add(checker.diagnostics, source.Span{}, "missing expression")
|
||||
last = invalid_hir_expr(checker, source.Span{}, id)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
expr := checker.ast_module.exprs[frame.expr]
|
||||
if frame.stage == 0 {
|
||||
constant := eval_constant(checker, frame.expr)
|
||||
if constant.kind == .Value || constant.kind == .Overflow {
|
||||
last = build_constant_expr(checker, expr, constant, frame.expected)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
switch expr.kind {
|
||||
case .Invalid, .Integer:
|
||||
last = invalid_hir_expr(checker, expr.span, expr.diagnostic)
|
||||
_ = pop(&stack)
|
||||
case .Name:
|
||||
last = -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,
|
||||
hir.Expr {
|
||||
kind = .Local,
|
||||
span = expr.span,
|
||||
type = local.type,
|
||||
target = local.id,
|
||||
left = -1,
|
||||
right = -1,
|
||||
diagnostic = -1,
|
||||
},
|
||||
stack[frame_index].built_args[index],
|
||||
checker.specs[spec].args[index],
|
||||
checker.module.exprs[stack[frame_index].built_args[index]].span,
|
||||
)
|
||||
}
|
||||
}
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
global := find_global(checker, expr.name, target_pkg)
|
||||
if global >= 0 {
|
||||
add_unique(global_reads, global)
|
||||
return add_hir_expr(
|
||||
checker,
|
||||
hir.Expr {
|
||||
kind = .Global,
|
||||
span = expr.span,
|
||||
type = checker.global_types[global],
|
||||
target = global,
|
||||
left = -1,
|
||||
right = -1,
|
||||
diagnostic = -1,
|
||||
},
|
||||
)
|
||||
}
|
||||
id := add_name_resolution_diagnostic(checker, expr, target_pkg)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
case .Add:
|
||||
left := build_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
right := build_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
left_type := checker.module.exprs[left].type
|
||||
right_type := checker.module.exprs[right].type
|
||||
result := types.widest(left_type, right_type)
|
||||
if !types.is_signed(result) {
|
||||
id := source.add(
|
||||
checker.diagnostics,
|
||||
expr.span,
|
||||
"addition requires compatible signed integers",
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
left = coerce_expr(checker, left, result, checker.module.exprs[left].span)
|
||||
right = coerce_expr(checker, right, result, checker.module.exprs[right].span)
|
||||
return add_hir_expr(
|
||||
checker,
|
||||
hir.Expr {
|
||||
kind = .Add,
|
||||
span = expr.span,
|
||||
type = result,
|
||||
left = left,
|
||||
right = right,
|
||||
target = -1,
|
||||
diagnostic = -1,
|
||||
},
|
||||
)
|
||||
case .Call:
|
||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||
if !available {
|
||||
id := add_package_resolution_diagnostic(checker, expr, file)
|
||||
return invalid_hir_expr(checker, expr.span, id)
|
||||
}
|
||||
template := find_template(checker, expr.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
|
||||
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(stack[frame_index].built_args, checker.allocator)
|
||||
stack[frame_index].built_args = nil
|
||||
last = invalid_hir_expr(checker, expr.span, id)
|
||||
} else {
|
||||
last = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Call, span=expr.span, type=result, target=spec,
|
||||
left=-1, right=-1, args=stack[frame_index].built_args, diagnostic=-1,
|
||||
})
|
||||
stack[frame_index].built_args = nil
|
||||
}
|
||||
built_args[index] = build_expr(
|
||||
checker,
|
||||
arg,
|
||||
locals,
|
||||
global_reads,
|
||||
calls,
|
||||
arg_expected,
|
||||
pkg,
|
||||
file,
|
||||
)
|
||||
arg_types[index] = checker.module.exprs[built_args[index]].type
|
||||
_ = pop(&stack)
|
||||
}
|
||||
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(
|
||||
checker,
|
||||
expr.span,
|
||||
source.add(checker.diagnostics, expr.span, "invalid expression"),
|
||||
)
|
||||
return last
|
||||
}
|
||||
|
||||
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,
|
||||
locals = hir_locals[:],
|
||||
body = body[:],
|
||||
direct_global_reads = global_reads[:],
|
||||
direct_global_reads = global_reads,
|
||||
calls = calls[:],
|
||||
problematic = problematic,
|
||||
diagnostic = checker.template_diagnostics[spec.template],
|
||||
@@ -1387,7 +1518,7 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
||||
result = spec.result,
|
||||
locals = hir_locals[:],
|
||||
body = body[:],
|
||||
direct_global_reads = global_reads[:],
|
||||
direct_global_reads = global_reads,
|
||||
calls = calls[:],
|
||||
problematic = problematic,
|
||||
diagnostic = -1,
|
||||
@@ -1396,24 +1527,31 @@ build_function :: proc(checker: ^Checker, spec_id: int) {
|
||||
delete(locals)
|
||||
}
|
||||
|
||||
expr_problematic :: proc(module: ^hir.Module, expr_id: int) -> bool {
|
||||
if expr_id < 0 || expr_id >= len(module.exprs) {
|
||||
return true
|
||||
expr_problematic :: proc(checker: ^Checker, expr_id: int) -> bool {
|
||||
module := &checker.module
|
||||
stack := checker.expr_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.expr_stack = stack
|
||||
}
|
||||
expr := module.exprs[expr_id]
|
||||
if expr.kind == .Invalid {
|
||||
return true
|
||||
}
|
||||
if expr.left >= 0 && expr_problematic(module, expr.left) {
|
||||
return true
|
||||
}
|
||||
if expr.right >= 0 && expr_problematic(module, expr.right) {
|
||||
return true
|
||||
}
|
||||
for arg in expr.args {
|
||||
if expr_problematic(module, arg) {
|
||||
append(&stack, expr_id)
|
||||
for len(stack) > 0 {
|
||||
id := pop(&stack)
|
||||
if id < 0 || id >= len(module.exprs) {
|
||||
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
|
||||
}
|
||||
@@ -1477,10 +1615,10 @@ build_globals :: proc(checker: ^Checker) {
|
||||
expr = expr,
|
||||
static_value = static_value,
|
||||
is_static = is_static,
|
||||
dependencies = dependencies[:],
|
||||
dependencies = dependencies,
|
||||
calls = calls[:],
|
||||
direct_problem = expr_problematic(&checker.module, expr),
|
||||
problematic = expr_problematic(&checker.module, expr),
|
||||
direct_problem = expr_problematic(checker, expr),
|
||||
problematic = expr_problematic(checker, expr),
|
||||
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^ {
|
||||
if existing == value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
replacement := make([]int, len(values^) + 1, allocator)
|
||||
copy(replacement, values^)
|
||||
replacement[len(values^)] = value
|
||||
delete(values^, allocator)
|
||||
values^ = replacement
|
||||
append(values, value)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1572,11 +1706,7 @@ propagate_global_reads :: proc(checker: ^Checker) {
|
||||
continue
|
||||
}
|
||||
for global_id in checker.module.functions[callee].direct_global_reads {
|
||||
if append_unique_slice(
|
||||
&function.direct_global_reads,
|
||||
global_id,
|
||||
checker.allocator,
|
||||
) {
|
||||
if append_unique_slice(&function.direct_global_reads, global_id) {
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
@@ -1593,37 +1723,68 @@ propagate_global_reads :: proc(checker: ^Checker) {
|
||||
continue
|
||||
}
|
||||
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) {
|
||||
if states[global_id] == 2 {
|
||||
return
|
||||
}
|
||||
if states[global_id] == 1 {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
checker.ast_module.globals[global_id].span,
|
||||
"global initialization cycle involving '%s'",
|
||||
symbol_text(checker, checker.module.globals[global_id].name),
|
||||
)
|
||||
checker.module.globals[global_id].diagnostic = id
|
||||
checker.module.globals[global_id].problematic = true
|
||||
return
|
||||
stack := checker.cycle_stack
|
||||
clear_dynamic_array(&stack)
|
||||
defer {
|
||||
clear_dynamic_array(&stack)
|
||||
checker.cycle_stack = stack
|
||||
}
|
||||
states[global_id] = 1
|
||||
for dependency in checker.module.globals[global_id].dependencies {
|
||||
if dependency >= 0 && dependency < len(states) {
|
||||
detect_global_cycles_visit(checker, dependency, states)
|
||||
if checker.module.globals[dependency].problematic {
|
||||
checker.module.globals[global_id].problematic = true
|
||||
}
|
||||
append(&stack, Cycle_Frame{global=global_id})
|
||||
for len(stack) > 0 {
|
||||
frame_index := len(stack)-1
|
||||
frame := &stack[frame_index]
|
||||
if states[frame.global] == 0 {
|
||||
states[frame.global] = 1
|
||||
}
|
||||
dependencies := checker.module.globals[frame.global].dependencies
|
||||
if frame.next_dependency >= len(dependencies) {
|
||||
states[frame.global] = 2
|
||||
if checker.module.globals[frame.global].problematic && frame_index > 0 {
|
||||
checker.module.globals[stack[frame_index-1].global].problematic = true
|
||||
}
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
dependency := dependencies[frame.next_dependency]
|
||||
frame.next_dependency += 1
|
||||
if dependency < 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) {
|
||||
@@ -1700,6 +1861,11 @@ check :: proc(
|
||||
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)
|
||||
checker.global_types = make([]types.Type, len(ast_module.globals), allocator)
|
||||
checker.constants = make([]Constant, len(ast_module.exprs), allocator)
|
||||
@@ -1718,6 +1884,11 @@ check :: proc(
|
||||
delete(checker.global_types, allocator)
|
||||
delete(checker.constants, 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 {
|
||||
|
||||
@@ -79,7 +79,7 @@ Function :: struct {
|
||||
result: types.Type,
|
||||
locals: []Local,
|
||||
body: []int,
|
||||
direct_global_reads: []int,
|
||||
direct_global_reads: [dynamic]int,
|
||||
calls: []int,
|
||||
problematic: bool,
|
||||
diagnostic: int,
|
||||
@@ -91,7 +91,7 @@ Global :: struct {
|
||||
expr: int,
|
||||
static_value: i64,
|
||||
is_static: bool,
|
||||
dependencies: []int,
|
||||
dependencies: [dynamic]int,
|
||||
calls: []int,
|
||||
direct_problem: bool,
|
||||
problematic: bool,
|
||||
@@ -125,11 +125,11 @@ destroy_module :: proc(module: ^Module) {
|
||||
delete(function.params, module.allocator)
|
||||
delete(function.locals, module.allocator)
|
||||
delete(function.body, module.allocator)
|
||||
delete(function.direct_global_reads, module.allocator)
|
||||
delete(function.direct_global_reads)
|
||||
delete(function.calls, module.allocator)
|
||||
}
|
||||
for global in module.globals {
|
||||
delete(global.dependencies, module.allocator)
|
||||
delete(global.dependencies)
|
||||
delete(global.calls, module.allocator)
|
||||
}
|
||||
delete(module.exprs)
|
||||
|
||||
@@ -14,7 +14,6 @@ is_identifier_continue :: proc(value: byte) -> bool {
|
||||
|
||||
keyword_kind :: proc(text: string) -> token.Kind {
|
||||
switch text {
|
||||
case "c": return .Keyword_C
|
||||
case "func": return .Keyword_Func
|
||||
case "import": return .Keyword_Import
|
||||
case "return": return .Keyword_Return
|
||||
|
||||
+109
-19
@@ -40,9 +40,37 @@ function_result_type :: proc(function: ir.Function) -> string {
|
||||
return llvm_type(function.result)
|
||||
}
|
||||
|
||||
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: int) {
|
||||
if value_id < 0 || value_id >= len(instructions) {
|
||||
fmt.sbprintf(builder, "-6148914691236517206")
|
||||
sentinel :: proc(value_type: types.Type) -> i64 {
|
||||
switch value_type.bits {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
if index > 0 {
|
||||
strings.write_string(builder, ", ")
|
||||
}
|
||||
fmt.sbprintf(builder, "%s ", llvm_type(instructions[arg].type))
|
||||
write_operand(builder, instructions, arg)
|
||||
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index]))
|
||||
write_operand(builder, instructions, arg, param_types[index])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +164,14 @@ emit_instruction_stream :: proc(
|
||||
case .Param, .Const:
|
||||
case .Load_Global:
|
||||
if instruction.target < 0 || instruction.target >= len(emitter.module.globals) {
|
||||
message := diagnostic_message(emitter, -1, instruction.span, "invalid global reference")
|
||||
emit_trap_call(emitter, message)
|
||||
emit_recovery_value(emitter, instruction_id, instruction, "invalid global reference")
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
fmt.sbprintf(
|
||||
&emitter.builder,
|
||||
@@ -145,8 +190,18 @@ emit_instruction_stream :: proc(
|
||||
)
|
||||
}
|
||||
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))
|
||||
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(
|
||||
&emitter.builder,
|
||||
" %%v%d = load %s, ptr %%v%d\n",
|
||||
@@ -155,22 +210,41 @@ emit_instruction_stream :: proc(
|
||||
instruction.a,
|
||||
)
|
||||
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))
|
||||
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)
|
||||
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
|
||||
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))
|
||||
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)
|
||||
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_id)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name)
|
||||
write_operand(&emitter.builder, instructions, instruction.a)
|
||||
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
|
||||
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, " %%v%d = extractvalue ", instruction_id)
|
||||
strings.write_string(&emitter.builder, "{ ")
|
||||
@@ -191,11 +265,27 @@ emit_instruction_stream :: proc(
|
||||
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_id)
|
||||
case .Call:
|
||||
if instruction.target < 0 || instruction.target >= len(emitter.module.functions) {
|
||||
message := diagnostic_message(emitter, -1, instruction.span, "invalid function specialization")
|
||||
emit_trap_call(emitter, message)
|
||||
emit_recovery_value(emitter, instruction_id, instruction, "invalid function specialization")
|
||||
continue
|
||||
}
|
||||
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 {
|
||||
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_id)
|
||||
} else {
|
||||
@@ -206,7 +296,7 @@ emit_instruction_stream :: proc(
|
||||
strings.write_string(&emitter.builder, "fastcc ")
|
||||
}
|
||||
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")
|
||||
case .Trap:
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||
@@ -217,7 +307,7 @@ emit_instruction_stream :: proc(
|
||||
continue
|
||||
}
|
||||
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")
|
||||
after_return = true
|
||||
case .Return_Void:
|
||||
@@ -286,11 +376,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
|
||||
placeholder_function.result = global.type
|
||||
value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true)
|
||||
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, " store i8 2, ptr @bro.gstate.%d\n", global_id)
|
||||
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")
|
||||
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(
|
||||
&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 {
|
||||
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 {
|
||||
state.root_failed = true
|
||||
continue
|
||||
}
|
||||
source_id := source.add_source(state.sources, file_info.fullpath, string(bytes))
|
||||
delete(bytes)
|
||||
source_id := source.add_source_owned(state.sources, file_info.fullpath, bytes)
|
||||
file_id := len(state.module.files)
|
||||
append(&state.module.files, ast.File{source=source_id, pkg=pkg_id})
|
||||
stream := lexer.lex(&state.sources.items[source_id], state.diagnostics, state.symbols, state.token_allocator)
|
||||
|
||||
+157
-127
@@ -2,6 +2,7 @@ package lower
|
||||
|
||||
import "../hir"
|
||||
import "../ir"
|
||||
import "../source"
|
||||
import "../types"
|
||||
import "core:fmt"
|
||||
import "core:mem"
|
||||
@@ -11,6 +12,7 @@ State :: struct {
|
||||
instructions: [dynamic]ir.Instruction,
|
||||
local_values: []int,
|
||||
local_slots: []int,
|
||||
expr_stack: [dynamic]Lower_Expr_Frame,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
@@ -35,142 +37,25 @@ sentinel :: proc(value_type: types.Type) -> i64 {
|
||||
}
|
||||
}
|
||||
|
||||
lower_expr :: proc(state: ^State, expr_id: int) -> 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_recovery_value :: proc(state: ^State, span: source.Span, value_type: types.Type, diagnostic := -1) -> int {
|
||||
append_instruction(state, ir.Instruction{
|
||||
op=.Trap,
|
||||
span=expr.span,
|
||||
span=span,
|
||||
type=types.VOID,
|
||||
target=-1,
|
||||
a=-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{
|
||||
op=.Const,
|
||||
span=expr.span,
|
||||
type=types.I64,
|
||||
integer=sentinel(types.I64),
|
||||
span=span,
|
||||
type=fallback,
|
||||
integer=sentinel(fallback),
|
||||
target=-1,
|
||||
a=-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 {
|
||||
state := State{
|
||||
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),
|
||||
}
|
||||
state.instructions.allocator = allocator
|
||||
state.expr_stack.allocator = allocator
|
||||
defer {
|
||||
delete(state.local_values, allocator)
|
||||
delete(state.local_slots, allocator)
|
||||
delete(state.expr_stack)
|
||||
}
|
||||
for _, index in state.local_values {
|
||||
state.local_values[index] = -1
|
||||
@@ -211,6 +225,13 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
||||
switch statement.kind {
|
||||
case .Declaration:
|
||||
value := lower_expr(&state, statement.expr)
|
||||
if statement.local < 0 || statement.local >= len(function.locals) {
|
||||
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]
|
||||
if local.mutable {
|
||||
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) {
|
||||
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{
|
||||
op=.Store,
|
||||
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 {
|
||||
state := State{hir_module=hir_module, allocator=allocator}
|
||||
state.instructions.allocator = allocator
|
||||
state.expr_stack.allocator = allocator
|
||||
defer delete(state.expr_stack)
|
||||
value := lower_expr(&state, global.expr)
|
||||
append_instruction(&state, ir.Instruction{
|
||||
op=.Return,
|
||||
|
||||
+69
-10
@@ -19,6 +19,8 @@ Parser :: struct {
|
||||
delimiter_depth: int,
|
||||
}
|
||||
|
||||
MAX_EXPRESSION_NESTING :: 256
|
||||
|
||||
token_text :: proc(parser: ^Parser, tok: token.Token) -> string {
|
||||
if tok.span.start < 0 || tok.span.end < tok.span.start || tok.span.end > len(parser.source_file.text) {
|
||||
return ""
|
||||
@@ -110,7 +112,30 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
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)
|
||||
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
|
||||
skip_newlines(parser)
|
||||
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)
|
||||
if _, ok := allow(parser, .Comma); ok {
|
||||
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)
|
||||
#partial switch tok.kind {
|
||||
case .Integer:
|
||||
@@ -172,7 +197,7 @@ parse_primary :: proc(parser: ^Parser) -> int {
|
||||
name = advance(parser)
|
||||
}
|
||||
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{
|
||||
kind=.Name,
|
||||
@@ -187,11 +212,15 @@ parse_primary :: proc(parser: ^Parser) -> int {
|
||||
advance(parser)
|
||||
return invalid_expr(parser, tok.span, "'_' is a write-only sink and cannot be read")
|
||||
case .Left_Paren:
|
||||
if nesting >= MAX_EXPRESSION_NESTING {
|
||||
span := skip_parenthesized(parser)
|
||||
return invalid_expr(parser, span, "expression nesting exceeds 256 levels")
|
||||
}
|
||||
advance(parser)
|
||||
parser.delimiter_depth += 1
|
||||
defer parser.delimiter_depth -= 1
|
||||
skip_newlines(parser)
|
||||
expr := parse_expression(parser)
|
||||
expr := parse_expression_bp(parser, 0, nesting+1)
|
||||
skip_newlines(parser)
|
||||
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||
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")
|
||||
}
|
||||
|
||||
parse_expression :: proc(parser: ^Parser) -> int {
|
||||
left := parse_primary(parser)
|
||||
infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
||||
#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 {
|
||||
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)
|
||||
skip_newlines(parser)
|
||||
right := parse_primary(parser)
|
||||
right := parse_expression_bp(parser, right_power, nesting+1)
|
||||
left_expr := parser.module.exprs[left]
|
||||
right_expr := parser.module.exprs[right]
|
||||
left = add_expr(parser, ast.Expr{
|
||||
@@ -238,6 +286,10 @@ parse_expression :: proc(parser: ^Parser) -> int {
|
||||
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 {
|
||||
if current(parser).kind == .Newline {
|
||||
skip_newlines(parser)
|
||||
@@ -536,10 +588,17 @@ parse_top_level :: proc(parser: ^Parser) {
|
||||
skip_newlines(parser)
|
||||
|
||||
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
|
||||
advance(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 {
|
||||
parse_function(parser, name, c_abi)
|
||||
|
||||
+62
-15
@@ -10,9 +10,10 @@ Span :: struct {
|
||||
}
|
||||
|
||||
Source :: struct {
|
||||
id: int,
|
||||
path: string,
|
||||
text: string,
|
||||
id: int,
|
||||
path: string,
|
||||
text: string,
|
||||
line_starts: []int,
|
||||
}
|
||||
|
||||
Store :: struct {
|
||||
@@ -29,9 +30,15 @@ Diagnostics :: struct {
|
||||
source: ^Source,
|
||||
store: ^Store,
|
||||
items: [dynamic]Diagnostic,
|
||||
index: map[Diagnostic_Key]int,
|
||||
allocator: mem.Allocator,
|
||||
}
|
||||
|
||||
Diagnostic_Key :: struct {
|
||||
span: Span,
|
||||
message: string,
|
||||
}
|
||||
|
||||
init_store :: proc(allocator := context.allocator) -> Store {
|
||||
result: Store
|
||||
result.allocator = allocator
|
||||
@@ -43,25 +50,47 @@ destroy_store :: proc(store: ^Store) {
|
||||
for item in store.items {
|
||||
delete(item.path, store.allocator)
|
||||
delete(item.text, store.allocator)
|
||||
delete(item.line_starts, store.allocator)
|
||||
}
|
||||
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)
|
||||
owned_text := string(text)
|
||||
append(&store.items, Source{
|
||||
id=id,
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
result: Diagnostics
|
||||
result.source = source_file
|
||||
result.allocator = allocator
|
||||
result.items.allocator = allocator
|
||||
result.index.allocator = allocator
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -70,10 +99,12 @@ init_store_diagnostics :: proc(store: ^Store, allocator := context.allocator) ->
|
||||
result.store = store
|
||||
result.allocator = allocator
|
||||
result.items.allocator = allocator
|
||||
result.index.allocator = allocator
|
||||
return result
|
||||
}
|
||||
|
||||
destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
||||
delete(diagnostics.index)
|
||||
for diagnostic in diagnostics.items {
|
||||
delete(diagnostic.message, diagnostics.allocator)
|
||||
}
|
||||
@@ -81,34 +112,50 @@ destroy_diagnostics :: proc(diagnostics: ^Diagnostics) {
|
||||
}
|
||||
|
||||
add :: proc(diagnostics: ^Diagnostics, span: Span, message: string) -> int {
|
||||
for diagnostic, id in diagnostics.items {
|
||||
if diagnostic.span == span && diagnostic.message == message {
|
||||
return id
|
||||
}
|
||||
key := Diagnostic_Key{span=span, message=message}
|
||||
if id, ok := diagnostics.index[key]; ok {
|
||||
return id
|
||||
}
|
||||
id := len(diagnostics.items)
|
||||
cloned := fmt.aprintf("%s", message, allocator=diagnostics.allocator)
|
||||
append(&diagnostics.items, Diagnostic{span=span, message=cloned})
|
||||
diagnostics.index[Diagnostic_Key{span=span, message=cloned}] = id
|
||||
return id
|
||||
}
|
||||
|
||||
addf :: proc(diagnostics: ^Diagnostics, span: Span, format: string, args: ..any) -> int {
|
||||
message := fmt.aprintf(format, ..args, allocator=diagnostics.allocator)
|
||||
for diagnostic, id in diagnostics.items {
|
||||
if diagnostic.span == span && diagnostic.message == message {
|
||||
delete(message, diagnostics.allocator)
|
||||
return id
|
||||
}
|
||||
key := Diagnostic_Key{span=span, message=message}
|
||||
if id, ok := diagnostics.index[key]; ok {
|
||||
delete(message, diagnostics.allocator)
|
||||
return id
|
||||
}
|
||||
id := len(diagnostics.items)
|
||||
append(&diagnostics.items, Diagnostic{span=span, message=message})
|
||||
diagnostics.index[Diagnostic_Key{span=span, message=message}] = id
|
||||
return id
|
||||
}
|
||||
|
||||
line_and_column :: proc(source_file: ^Source, offset: int) -> (line, column: int) {
|
||||
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
|
||||
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] {
|
||||
if byte_value == '\n' {
|
||||
line += 1
|
||||
|
||||
@@ -20,7 +20,6 @@ Kind :: enum {
|
||||
Left_Brace,
|
||||
Right_Brace,
|
||||
Comma,
|
||||
Keyword_C,
|
||||
Keyword_Func,
|
||||
Keyword_Import,
|
||||
Keyword_Return,
|
||||
|
||||
+255
-2
@@ -176,6 +176,113 @@ main :: func() void {}
|
||||
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)
|
||||
cli_parses_ordered_link_options_and_rejects_invalid_forms :: proc(t: ^testing.T) {
|
||||
options, valid := parse_cli_args([]string{
|
||||
@@ -1078,8 +1185,6 @@ backend_translates_link_arguments_without_reordering_them :: proc(t: ^testing.T)
|
||||
|
||||
expected := []string{
|
||||
"/usr/bin/env",
|
||||
"ZIG_LOCAL_CACHE_DIR=/tmp/brolang-zig-cache",
|
||||
"ZIG_GLOBAL_CACHE_DIR=/tmp/brolang-zig-global-cache",
|
||||
"zig",
|
||||
"cc",
|
||||
"-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)
|
||||
mutable_local_reassignment_uses_runtime_storage :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-mutable"
|
||||
|
||||
Reference in New Issue
Block a user