enforce integer division via explicit builtins

This commit is contained in:
2026-07-13 11:39:06 +02:00
parent a4d0fb1e26
commit 2ed333c70d
13 changed files with 1004 additions and 98 deletions
+172 -26
View File
@@ -6,6 +6,8 @@ import "../source"
import "../symbol"
import "../types"
import "base:intrinsics"
import "core:math"
import "core:mem"
COMPTIME_EVAL_QUOTA :: 100_000
@@ -28,6 +30,8 @@ Constant_Kind :: enum {
Value,
Overflow,
Div_By_Zero,
Non_Exact,
Integer_Division,
}
Constant :: struct {
@@ -94,8 +98,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
continue
}
expr := checker.ast_module.exprs[frame.expr]
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul &&
expr.kind != .Div && expr.kind != .Negate {
if expr.kind != .Add && expr.kind != .Sub && expr.kind != .Mul && expr.kind != .Negate {
result := Constant{kind = .Not_Constant}
if expr.kind == .Integer {
result = Constant{kind = .Value, value = i128(expr.integer)}
@@ -118,9 +121,7 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
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 {
if operand.kind == .Overflow {
result = Constant{kind = .Overflow}
} else if operand.kind == .Value {
value, overflow := intrinsics.overflow_sub(i128(0), operand.value)
@@ -147,29 +148,19 @@ eval_constant :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> Constant {
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 {
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}
case overflow: result = Constant{kind = .Overflow}
case: result = Constant{kind = .Value, value = value}
}
}
checker.constants[frame.expr] = result
@@ -226,6 +217,8 @@ Ct_Error_Kind :: enum u8 {
Not_Comptime,
Overflow,
Div_By_Zero,
Non_Exact,
Integer_Division,
Quota,
}
@@ -1105,7 +1098,8 @@ ct_eval_expr :: proc(
}
return ct_eval_unary(state, expr.kind, value, expr.span)
case .Add, .Sub, .Mul, .Div, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
left, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
@@ -1858,6 +1852,12 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if ok else 0}), ct_flow(.Normal), true
}
if op == .Div {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Integer_Division, span,
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
)
}
value: i128
overflow := false
#partial switch op {
@@ -1865,12 +1865,6 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
value, overflow = intrinsics.overflow_sub(left.integer, right.integer)
case .Mul:
value, overflow = intrinsics.overflow_mul(left.integer, right.integer)
case .Div:
if right.integer == 0 {
state.error = .Div_By_Zero
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
value = left.integer / right.integer
case:
value, overflow = intrinsics.overflow_add(left.integer, right.integer)
}
@@ -1887,6 +1881,137 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime binary expression requires compatible operands")
}
ct_eval_division_builtin :: proc(
state: ^Ct_State,
kind: Division_Builtin,
left_id, right_id: Ct_Value_Id,
span: source.Span,
) -> (Ct_Value_Id, Ct_Flow, bool) {
if left_id == INVALID_CT_VALUE || right_id == INVALID_CT_VALUE ||
int(left_id) >= len(state.values) || int(right_id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
left := state.values[left_id]
right := state.values[right_id]
result_type := types.widest(left.type, right.type)
if !types.is_concrete_scalar(result_type) || types.is_bool(result_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "division builtins require compatible numeric operands",
)
}
left_id, left_ok := ct_coerce_value(state, left_id, result_type, span)
right_id, right_ok := ct_coerce_value(state, right_id, result_type, span)
if !left_ok || !right_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
left = state.values[left_id]
right = state.values[right_id]
if left.kind == .Float && right.kind == .Float {
if right.float == 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
}
quotient := left.float / right.float
result := quotient
#partial switch kind {
case .Trunc: result = math.trunc(quotient)
case .Floor: result = math.floor(quotient)
case .Ceil: result = math.ceil(quotient)
case .Exact:
result = math.trunc(quotient)
if result * right.float != left.float {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
}
case .Rem, .Mod:
result = left.float - math.trunc(quotient) * right.float
if kind == .Mod && result != 0 && (result < 0) != (right.float < 0) {
result += right.float
}
}
if types.bits(result_type, state.checker.target) == 32 {
result = f64(f32(result))
}
return ct_add_value(state, Ct_Value{kind=.Float, type=result_type, float=result}), ct_flow(.Normal), true
}
if left.kind != .Integer || right.kind != .Integer {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "division builtins require compatible numeric operands")
}
if right.integer == 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Div_By_Zero, span, "division builtin denominator is zero")
}
is_quotient := kind == .Trunc || kind == .Floor || kind == .Exact || kind == .Ceil
if is_quotient && types.is_signed(result_type, state.checker.target) {
minimum := -(i128(1) << u32(types.bits(result_type, state.checker.target)-1))
if left.integer == minimum && right.integer == -1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Overflow, span, "signed integer division overflow")
}
}
quotient := left.integer / right.integer
remainder := left.integer % right.integer
result := quotient
#partial switch kind {
case .Floor:
if remainder != 0 && (left.integer < 0) != (right.integer < 0) {
result -= 1
}
case .Ceil:
if remainder != 0 && (left.integer < 0) == (right.integer < 0) {
result += 1
}
case .Exact:
if remainder != 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Non_Exact, span, "exact division has a remainder")
}
case .Rem: result = remainder
case .Mod:
result = remainder
if result != 0 && (result < 0) != (right.integer < 0) {
result += right.integer
}
case:
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=result}), ct_flow(.Normal), true
}
ct_eval_division_call :: proc(
state: ^Ct_State,
expr: ast.Expr,
kind: Division_Builtin,
expected: types.Type,
depth: int,
) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, expr.span, "%s expects 2 arguments, got %d",
symbol_text(checker, expr.name), len(expr.args),
)
}
hint := expected if types.is_concrete_scalar(expected) && !types.is_bool(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.args[0])
right_const := is_numeric_constant_expr(checker, expr.args[1])
left, right := INVALID_CT_VALUE, INVALID_CT_VALUE
flow := ct_flow(.Normal)
ok := false
if left_const && !right_const && !types.is_valid(hint) {
right, flow, ok = ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
left, flow, ok = ct_eval_expr(state, expr.args[0], state.values[right].type, depth+1)
} else {
left, flow, ok = ct_eval_expr(state, expr.args[0], hint, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right_hint := hint if types.is_valid(hint) else state.values[left].type
right, flow, ok = ct_eval_expr(state, expr.args[1], right_hint, depth+1)
}
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
return ct_eval_division_builtin(state, kind, left, right, expr.span)
}
ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
@@ -1943,6 +2068,9 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
result_type := types.USIZE if builtin == .Size_Of || builtin == .Align_Of else target
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=type_builtin_value(checker, builtin, target)}), ct_flow(.Normal), true
}
if builtin := division_builtin_call(checker, expr); builtin != .None {
return ct_eval_division_call(state, expr, builtin, expected, depth+1)
}
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
if !available {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "unavailable function package")
@@ -2899,6 +3027,10 @@ eval_integer_constant_in_context :: proc(
return Constant{kind=.Overflow}
case .Div_By_Zero:
return Constant{kind=.Div_By_Zero}
case .Non_Exact:
return Constant{kind=.Non_Exact}
case .Integer_Division:
return Constant{kind=.Integer_Division}
}
return Constant{kind=.Not_Constant}
}
@@ -2927,6 +3059,10 @@ eval_comptime_statements :: proc(
return Constant{kind=.Overflow}, false, false
case .Div_By_Zero:
return Constant{kind=.Div_By_Zero}, false, false
case .Non_Exact:
return Constant{kind=.Non_Exact}, false, false
case .Integer_Division:
return Constant{kind=.Integer_Division}, false, false
}
return Constant{kind=.Not_Constant}, false, false
}
@@ -2958,6 +3094,10 @@ eval_comptime_call :: proc(
return Constant{kind=.Overflow}
case .Div_By_Zero:
return Constant{kind=.Div_By_Zero}
case .Non_Exact:
return Constant{kind=.Non_Exact}
case .Integer_Division:
return Constant{kind=.Integer_Division}
}
return Constant{kind=.Not_Constant}
}
@@ -2996,7 +3136,7 @@ infer_comptime_expr_type :: proc(
}
}
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) {
if state.error == .Overflow || state.error == .Div_By_Zero {
if state.error == .Overflow || state.error == .Div_By_Zero || state.error == .Non_Exact || state.error == .Integer_Division {
return types.I64
}
return types.INVALID
@@ -3038,6 +3178,12 @@ build_comptime_expr :: proc(
if state.error == .Overflow {
return build_constant_expr(checker, expr, Constant{kind=.Overflow}, expected)
}
if state.error == .Non_Exact {
return build_constant_expr(checker, expr, Constant{kind=.Non_Exact}, expected)
}
if state.error == .Integer_Division {
return build_constant_expr(checker, expr, Constant{kind=.Integer_Division}, expected)
}
diagnostic := state.diagnostic
if diagnostic == source.INVALID_DIAGNOSTIC {
diagnostic = source.add(checker.diagnostics, expr.span, "expression cannot be evaluated at comptime")