more comptime eval

This commit is contained in:
2026-07-03 15:54:28 +02:00
parent e00a4e929a
commit adf142736c
7 changed files with 2266 additions and 481 deletions
+2 -451
View File
@@ -6,7 +6,6 @@ import "../source"
import "../symbol"
import "../target"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:mem"
import "core:slice"
@@ -33,18 +32,6 @@ Spec :: struct {
hir_id: hir.Function_Id,
}
Comptime_Value_Kind :: enum u8 {
Integer,
Type,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
kind: Comptime_Value_Kind,
}
Infer_Local :: struct {
name: symbol.Id,
type: types.Type,
@@ -118,19 +105,6 @@ Build_Ctx :: struct {
loop_floor: int,
}
Constant_Kind :: enum {
Unknown,
Not_Constant,
Value,
Overflow,
Div_By_Zero,
}
Constant :: struct {
kind: Constant_Kind,
value: i128,
}
Function_Index_Entry :: struct {
scope: ast.Package_Id,
name: symbol.Id,
@@ -202,26 +176,6 @@ type_label :: proc(checker: ^Checker, value: types.Type) -> string {
return types.name(value)
}
find_comptime_value :: proc(values: []Comptime_Value, name: symbol.Id) -> (Comptime_Value, bool) {
for index := len(values) - 1; index >= 0; index -= 1 {
if values[index].name == name {
return values[index], true
}
}
return {}, false
}
current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_Value, bool) {
return find_comptime_value(checker.current_comptime_values, name)
}
current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) {
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
return value.type, true
}
return types.INVALID, false
}
is_type_metatype_syntax :: proc(checker: ^Checker, value: ast.Type_Syntax) -> bool {
item, ok := types.node(&checker.module.types, value)
return ok && item.name == u32(checker.type_symbol) && item.qualifier == 0
@@ -260,379 +214,6 @@ comptime_param_count :: proc(function: ast.Function) -> int {
return count
}
comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
if len(left) != len(right) {
return false
}
for value, index in left {
other := right[index]
if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) ||
(value.kind == .Integer && value.value != other.value) {
return false
}
}
return true
}
Constant_Frame :: struct {
expr: ast.Expr_Id,
stage: u8,
}
eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
if expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return Constant{kind = .Not_Constant}
}
stack := checker.constant_stack
clear_dynamic_array(&stack)
defer {
clear_dynamic_array(&stack)
checker.constant_stack = stack
}
append(&stack, Constant_Frame{expr=expr_id})
for len(stack) > 0 {
frame_index := len(stack)-1
frame := stack[frame_index]
if checker.constants[frame.expr].kind != .Unknown {
_ = pop(&stack)
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul &&
expr.kind != .Div && expr.kind != .Negate {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 0 {
stack[frame_index].stage = 1
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.ast_module.exprs) &&
checker.constants[expr.left].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.left})
}
continue
}
if frame.stage == 1 && expr.kind == .Negate {
operand := Constant{kind = .Not_Constant}
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
operand = checker.constants[expr.left]
}
result := Constant{kind = .Not_Constant}
if operand.kind == .Div_By_Zero {
result = Constant{kind = .Div_By_Zero}
} else if operand.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if operand.kind == .Value {
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
result = Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
continue
}
if frame.stage == 1 {
stack[frame_index].stage = 2
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.ast_module.exprs) &&
checker.constants[expr.right].kind == .Unknown {
append(&stack, Constant_Frame{expr=expr.right})
}
continue
}
left := Constant{kind = .Not_Constant}
right := Constant{kind = .Not_Constant}
if expr.left != ast.INVALID_EXPR && int(expr.left) < len(checker.constants) {
left = checker.constants[expr.left]
}
if expr.right != ast.INVALID_EXPR && int(expr.right) < len(checker.constants) {
right = checker.constants[expr.right]
}
result := Constant{kind = .Not_Constant}
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
result = Constant{kind = .Div_By_Zero}
} else if left.kind == .Overflow || right.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if left.kind == .Value && right.kind == .Value {
value: i128
overflow: bool
div_by_zero: bool
#partial switch expr.kind {
case .Sub: value, overflow = intrinsics.overflow_sub(left.value, right.value)
case .Mul: value, overflow = intrinsics.overflow_mul(left.value, right.value)
case .Div:
if right.value == 0 {
div_by_zero = true
} else {
value = left.value / right.value
}
case: value, overflow = intrinsics.overflow_add(left.value, right.value)
}
switch {
case div_by_zero: result = Constant{kind = .Div_By_Zero}
case overflow: result = Constant{kind = .Overflow}
case: result = Constant{kind = .Value, value = value}
}
}
checker.constants[frame.expr] = result
_ = pop(&stack)
}
return checker.constants[expr_id]
}
eval_integer_constant_in_context :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
depth := 0,
values: []Comptime_Value = nil,
) -> Constant {
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return Constant{kind = .Not_Constant}
}
expr := checker.ast_module.exprs[expr_id]
#partial switch expr.kind {
case .Integer:
return Constant{kind = .Value, value = i128(expr.integer)}
case .Bool:
return Constant{kind = .Value, value = i128(expr.integer)}
case .Name:
if !symbol.is_valid(expr.qualifier) {
if value, ok := find_comptime_value(values, expr.name); ok {
if value.kind == .Integer {
return Constant{kind = .Value, value = value.value}
}
}
if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer {
return Constant{kind = .Value, value = value.value}
}
}
}
target_pkg, available := expr_package(checker, expr, pkg, file, false)
if !available {
return Constant{kind = .Not_Constant}
}
global := find_global(checker, expr.name, target_pkg)
if global == ast.INVALID_GLOBAL || int(global) >= len(checker.ast_module.globals) {
return Constant{kind = .Not_Constant}
}
g := checker.ast_module.globals[global]
if g.external || !g.immutable {
return Constant{kind = .Not_Constant}
}
return eval_integer_constant_in_context(checker, g.expr, g.pkg, g.file, depth+1, values)
case .Comptime:
if expr.left != ast.INVALID_EXPR {
return eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
}
result, produced, ok := eval_comptime_statements(checker, expr.body, pkg, file, depth+1, values, true)
if ok && produced {
return result
}
return Constant{kind = .Not_Constant}
case .Negate:
operand := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
if operand.kind == .Value {
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
return Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
}
return operand
case .Not:
operand := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
if operand.kind == .Value {
return Constant{kind = .Value, value = 1 if operand.value == 0 else 0}
}
return operand
case .Add, .Sub, .Mul, .Div:
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1, values)
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
return Constant{kind = .Div_By_Zero}
}
if left.kind == .Overflow || right.kind == .Overflow {
return Constant{kind = .Overflow}
}
if left.kind != .Value || right.kind != .Value {
return Constant{kind = .Not_Constant}
}
value: i128
overflow: bool
#partial switch expr.kind {
case .Sub:
value, overflow = intrinsics.overflow_sub(left.value, right.value)
case .Mul:
value, overflow = intrinsics.overflow_mul(left.value, right.value)
case .Div:
if right.value == 0 {
return Constant{kind = .Div_By_Zero}
}
value = left.value / right.value
case:
value, overflow = intrinsics.overflow_add(left.value, right.value)
}
return Constant{kind = .Overflow} if overflow else Constant{kind = .Value, value = value}
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1, values)
if left.kind != .Value || right.kind != .Value {
if left.kind == .Div_By_Zero || right.kind == .Div_By_Zero {
return Constant{kind = .Div_By_Zero}
}
if left.kind == .Overflow || right.kind == .Overflow {
return Constant{kind = .Overflow}
}
return Constant{kind = .Not_Constant}
}
ok := false
#partial switch expr.kind {
case .Eq: ok = left.value == right.value
case .Ne: ok = left.value != right.value
case .Lt: ok = left.value < right.value
case .Le: ok = left.value <= right.value
case .Gt: ok = left.value > right.value
case .Ge: ok = left.value >= right.value
}
return Constant{kind = .Value, value = 1 if ok else 0}
case .And, .Or:
left := eval_integer_constant_in_context(checker, expr.left, pkg, file, depth+1, values)
if left.kind != .Value {
return left
}
if expr.kind == .And && left.value == 0 {
return Constant{kind = .Value, value = 0}
}
if expr.kind == .Or && left.value != 0 {
return Constant{kind = .Value, value = 1}
}
right := eval_integer_constant_in_context(checker, expr.right, pkg, file, depth+1, values)
if right.kind == .Value {
return Constant{kind = .Value, value = 1 if right.value != 0 else 0}
}
return right
case .Call:
return eval_comptime_call(checker, expr, pkg, file, depth+1, values)
}
return Constant{kind = .Not_Constant}
}
eval_comptime_statements :: proc(
checker: ^Checker,
statements: []ast.Stmt_Id,
pkg: ast.Package_Id,
file: ast.File_Id,
depth: int,
values: []Comptime_Value,
yield_returns: bool,
) -> (Constant, bool, bool) {
env: [dynamic]Comptime_Value
env.allocator = checker.allocator
append(&env, ..values)
defer delete(env)
for statement_id in statements {
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
return Constant{kind=.Not_Constant}, false, false
}
statement := checker.ast_module.statements[statement_id]
#partial switch statement.kind {
case .Declaration:
if statement.expr == ast.INVALID_EXPR || !statement.immutable {
return Constant{kind=.Not_Constant}, false, false
}
value := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
if value.kind != .Value || statement.name == checker.sink_symbol {
return value, false, false
}
append(&env, Comptime_Value{name=statement.name, type=types.I64, value=value.value})
case .Return:
if yield_returns || statement.expr == ast.INVALID_EXPR {
return Constant{kind=.Not_Constant}, false, false
}
value := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
return value, value.kind == .Value, value.kind == .Value
case .Yield:
if !yield_returns || statement.expr == ast.INVALID_EXPR {
return Constant{kind=.Not_Constant}, false, false
}
value := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
return value, value.kind == .Value, value.kind == .Value
case .If:
if len(statement.captures) > 0 || statement.guard != ast.INVALID_EXPR {
return Constant{kind=.Not_Constant}, false, false
}
condition := eval_integer_constant_in_context(checker, statement.expr, pkg, file, depth+1, env[:])
if condition.kind != .Value {
return condition, false, false
}
body := statement.body if condition.value != 0 else statement.else_body
value, produced, ok := eval_comptime_statements(checker, body, pkg, file, depth+1, env[:], yield_returns)
if !ok || produced {
return value, produced, ok
}
case:
return Constant{kind=.Not_Constant}, false, false
}
}
return Constant{kind=.Not_Constant}, false, true
}
eval_comptime_call :: proc(
checker: ^Checker,
expr: ast.Expr,
pkg: ast.Package_Id,
file: ast.File_Id,
depth: int,
values: []Comptime_Value,
) -> Constant {
if expr.left != ast.INVALID_EXPR || depth > 64 {
return Constant{kind=.Not_Constant}
}
target_pkg, available := expr_package(checker, expr, pkg, file, false)
if !available {
return Constant{kind=.Not_Constant}
}
template := find_template(checker, expr.name, target_pkg)
if template == ast.INVALID_FUNCTION || int(template) >= len(checker.ast_module.functions) {
return Constant{kind=.Not_Constant}
}
function := checker.ast_module.functions[template]
if function.c_abi || !function.has_body || types.is_valid(function.error) ||
len(function.unsupported_reason) > 0 || !valid_call_arity(function, len(expr.args)) {
return Constant{kind=.Not_Constant}
}
comptime_values, comptime_ok := collect_comptime_values(checker, function, expr.args, pkg, file, false, values)
defer delete(comptime_values, checker.allocator)
if !comptime_ok {
return Constant{kind=.Not_Constant}
}
env: [dynamic]Comptime_Value
env.allocator = checker.allocator
defer delete(env)
append(&env, ..comptime_values)
for param, index in function.params {
if param.comptime_value {
continue
}
if index >= len(expr.args) {
return Constant{kind=.Not_Constant}
}
value := eval_integer_constant_in_context(checker, expr.args[index], pkg, file, depth+1, values)
if value.kind != .Value {
return value
}
append(&env, Comptime_Value{name=param.name, type=types.I64, value=value.value})
}
result, produced, ok := eval_comptime_statements(checker, function.body, function.pkg, function.file, depth+1, env[:], false)
if !ok || !produced {
return Constant{kind=.Not_Constant}
}
return result
}
fits_signed_type :: proc(value: i128, value_type: types.Type, selected := target.DEFAULT) -> bool {
if !types.is_signed(value_type, selected) {
return false
@@ -1927,24 +1508,7 @@ infer_compound_expr :: proc(
store := &checker.module.types
#partial switch expr.kind {
case .Comptime:
constant := Constant{kind=.Not_Constant}
if expr.left != ast.INVALID_EXPR {
constant = eval_integer_constant_in_context(checker, expr.left, pkg, file)
}
if expr.left == ast.INVALID_EXPR {
value, produced, ok := eval_comptime_statements(checker, expr.body, pkg, file, 0, nil, true)
if ok && produced {
constant = value
}
}
if constant.kind == .Overflow || constant.kind == .Div_By_Zero ||
(constant.kind == .Value && !fits_i64(constant.value)) {
return types.I64
}
if constant.kind == .Value {
return types.smallest_signed_for_literal(i64(constant.value))
}
return types.INVALID
return infer_comptime_expr_type(checker, expr, pkg, file)
case .Bool:
return types.BOOL
case .Not:
@@ -4322,20 +3886,7 @@ build_compound_expr :: proc(
diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Comptime:
constant := Constant{kind=.Not_Constant}
if expr.left != ast.INVALID_EXPR {
constant = eval_integer_constant_in_context(checker, expr.left, pkg, file)
} else {
value, produced, ok := eval_comptime_statements(checker, expr.body, pkg, file, 0, nil, true)
if ok && produced {
constant = value
}
}
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
return build_constant_expr(checker, expr, constant, expected)
}
id := source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime")
return invalid_hir_expr(checker, expr.span, id)
return build_comptime_expr(checker, expr, expected, pkg, file)
case .Bool:
return add_hir_expr(checker, hir.Expr{
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
File diff suppressed because it is too large Load Diff