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
+183 -2
View File
@@ -399,6 +399,31 @@ Type_Builtin :: enum u8 {
Max_Value,
}
Division_Builtin :: enum u8 {
None,
Trunc,
Floor,
Exact,
Ceil,
Rem,
Mod,
}
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None
}
switch symbol_text(checker, expr.name) {
case "div_trunc": return .Trunc
case "div_floor": return .Floor
case "div_exact": return .Exact
case "div_ceil": return .Ceil
case "rem": return .Rem
case "mod": return .Mod
}
return .None
}
type_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Type_Builtin {
if expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
return .None
@@ -598,6 +623,10 @@ type_from_syntax :: proc(
changed = true
}
} else {
if constant.kind == .Integer_Division {
source.add(checker.diagnostics, span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
return types.INVALID
}
source.add(checker.diagnostics, span, "array count must be a compile-time integer expression")
return types.INVALID
}
@@ -2568,6 +2597,35 @@ infer_nested_expr :: proc(
return result
}
infer_division_builtin :: proc(
checker: ^Checker,
expr: ast.Expr,
locals: []Infer_Local,
pkg: ast.Package_Id,
file: ast.File_Id,
demanded: ^[dynamic]Spec_Id,
local_types: []types.Type,
expected: types.Type,
) -> types.Type {
if len(expr.args) != 2 {
return types.INVALID
}
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 := types.INVALID, types.INVALID
if left_const && !right_const && !types.is_valid(hint) {
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, right)
} else {
left = infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types, hint)
right_hint := hint if types.is_valid(hint) else left
right = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, right_hint)
}
result := types.widest(left, right)
return result if types.is_concrete_scalar(result) && !types.is_bool(result) else types.INVALID
}
infer_compound_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
@@ -2938,6 +2996,11 @@ infer_expr :: proc(
_ = pop(&stack)
continue
}
if division_builtin_call(checker, expr) != .None {
last = infer_division_builtin(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
_ = pop(&stack)
continue
}
if is_ptr_cast_call(checker, expr) {
if len(expr.args) != 2 {
last = types.INVALID
@@ -3952,6 +4015,12 @@ record_demand :: proc(
right := record_demand(checker, expr.right, demand, locals, local_types, pkg, file)
return left || right
}
case .Call:
if division_builtin_call(checker, expr) != .None && len(expr.args) == 2 && is_numeric_demand(demand, checker.target) {
left := record_demand(checker, expr.args[0], demand, locals, local_types, pkg, file)
right := record_demand(checker, expr.args[1], demand, locals, local_types, pkg, file)
return left || right
}
}
return false
}
@@ -4435,6 +4504,14 @@ build_constant_expr :: proc(
id := source.add(checker.diagnostics, expr.span, "division by zero in constant expression")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Non_Exact {
id := source.add(checker.diagnostics, expr.span, "exact division has a remainder")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Integer_Division {
id := source.add(checker.diagnostics, expr.span, "integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil")
return invalid_hir_expr(checker, expr.span, id, recovery_type)
}
if constant.kind == .Overflow ||
(!types.is_concrete_integer(expected) && !fits_i64(constant.value)) {
id := source.add(
@@ -4868,6 +4945,89 @@ build_nested_expr :: proc(
return result
}
try_build_comptime_division :: proc(
checker: ^Checker,
expr: ast.Expr,
kind: Division_Builtin,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> (hir.Expr_Id, bool) {
state := ct_state_make(checker, pkg, file, diagnose=false)
defer ct_state_destroy(&state)
value, flow, ok := ct_eval_division_call(&state, expr, kind, expected, 0)
if ok && flow.kind == .Normal && value != INVALID_CT_VALUE {
return ct_materialize_value(&state, value, expr.span, expected), true
}
message := ""
#partial switch state.error {
case .Div_By_Zero: message = "division builtin denominator is zero"
case .Overflow: message = "signed integer division overflow"
case .Non_Exact: message = "exact division has a remainder"
}
if len(message) == 0 {
return hir.INVALID_EXPR, false
}
id := source.add(checker.diagnostics, expr.span, message)
recovery := expected if types.is_concrete_scalar(expected) else types.I64
return invalid_hir_expr(checker, expr.span, id, recovery), true
}
build_division_builtin :: proc(
checker: ^Checker,
expr: ast.Expr,
kind: Division_Builtin,
locals: []Build_Local,
global_reads: ^[dynamic]hir.Global_Id,
calls: ^[dynamic]hir.Function_Id,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> hir.Expr_Id {
if len(expr.args) != 2 {
id := source.addf(
checker.diagnostics, expr.span, "%s expects 2 arguments, got %d",
symbol_text(checker, expr.name), len(expr.args),
)
return invalid_hir_expr(checker, expr.span, id)
}
if value, handled := try_build_comptime_division(checker, expr, kind, expected, pkg, file); handled {
return value
}
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 := hir.INVALID_EXPR, hir.INVALID_EXPR
if left_const && !right_const && !types.is_valid(hint) {
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
} else {
left = build_nested_expr(checker, expr.args[0], locals, global_reads, calls, hint, pkg, file)
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, right_hint, pkg, file)
}
result := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_scalar(result) || types.is_bool(result) {
id := source.add(checker.diagnostics, expr.span, "division builtins require compatible numeric operands")
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)
result_kind := hir.Expr_Kind.Div_Trunc
#partial switch kind {
case .Floor: result_kind = .Div_Floor
case .Exact: result_kind = .Div_Exact
case .Ceil: result_kind = .Div_Ceil
case .Rem: result_kind = .Rem
case .Mod: result_kind = .Mod
case:
}
return add_hir_expr(checker, hir.Expr{
kind=result_kind, span=expr.span, type=result, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
}
fallible_aggregate :: proc(
checker: ^Checker,
span: source.Span,
@@ -5578,6 +5738,13 @@ build_binary_arith :: proc(
id := source.add(checker.diagnostics, span, "arithmetic requires compatible numeric operands")
return invalid_hir_expr(checker, span, id)
}
if op == .Div && !types.is_float(result, checker.target) {
id := source.add(
checker.diagnostics, span,
"integer '/' is not allowed; use div_trunc, div_floor, div_exact, or div_ceil",
)
return invalid_hir_expr(checker, span, id, result)
}
result_kind := hir.Expr_Kind.Add
#partial switch op {
case .Sub: result_kind = .Sub
@@ -5627,7 +5794,7 @@ build_expr :: proc(
expr := checker.ast_module.exprs[frame.expr]
if frame.stage == 0 {
constant := eval_constant(checker, frame.expr)
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero {
if constant.kind == .Value || constant.kind == .Overflow || constant.kind == .Div_By_Zero || constant.kind == .Non_Exact {
last = build_constant_expr(checker, expr, constant, frame.expected)
_ = pop(&stack)
continue
@@ -5807,6 +5974,13 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
if builtin := division_builtin_call(checker, expr); builtin != .None {
last = build_division_builtin(
checker, expr, builtin, locals, global_reads, calls, frame.expected, pkg, file,
)
_ = pop(&stack)
continue
}
if is_ptr_cast_call(checker, expr) {
if len(expr.args) != 2 {
id := source.addf(checker.diagnostics, expr.span, "ptr_cast expects 2 arguments, got %d", len(expr.args))
@@ -6653,7 +6827,14 @@ build_block :: proc(
}
rhs_type := checker.module.exprs[value].type
result_type := types.widest(target_type, rhs_type)
if !types.is_concrete_scalar(result_type) ||
if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
id := source.add(
checker.diagnostics,
statement.span,
"integer '/=' is not allowed; assign through an explicit division builtin",
)
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else if !types.is_concrete_scalar(result_type) ||
types.is_bool(result_type) {
id := source.add(
checker.diagnostics,
+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")
+6
View File
@@ -113,6 +113,12 @@ Expr_Kind :: enum u8 {
Sub,
Mul,
Div,
Div_Trunc,
Div_Floor,
Div_Exact,
Div_Ceil,
Rem,
Mod,
Pointer_Add,
Eq,
Ne,
+6
View File
@@ -109,6 +109,12 @@ Opcode :: enum u8 {
Sub_Checked,
Mul_Checked,
Div_Checked,
Div_Trunc_Checked,
Div_Floor_Checked,
Div_Exact_Checked,
Div_Ceil_Checked,
Rem_Checked,
Mod_Checked,
Pointer_Add,
Not,
Compare,
+222 -40
View File
@@ -257,7 +257,10 @@ valid_value :: proc(
.Fallible_Error, .Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse,
.Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call:
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked,
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked,
.Pointer_Add, .Not, .Compare, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
@@ -582,9 +585,7 @@ emit_checked_arithmetic :: proc(
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
}
// emit_checked_division emits a trapping integer division guarding divide-by-zero
// and signed `INT_MIN / -1` overflow, or a plain floating-point division.
emit_checked_division :: proc(
emit_division_zero_guard :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
@@ -592,56 +593,225 @@ emit_checked_division :: proc(
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = fdiv %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
fmt.sbprintf(&emitter.builder, " %%divzero%d = fcmp oeq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
return
strings.write_string(&emitter.builder, ", 0.000000e+00\n")
} else {
fmt.sbprintf(&emitter.builder, " %%divzero%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0\n")
}
signed := !types.is_unsigned(instruction.type, emitter.module.target)
fmt.sbprintf(&emitter.builder, " %%divzero%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0\n")
fmt.sbprintf(
&emitter.builder,
" br i1 %%divzero%d, label %%divzero_trap%d, label %%divzero_ok%d\n",
" br i1 %%divzero%d, label %%divzero_trap%d, label %%divzero_ok%d\ndivzero_trap%d:\n",
instruction_index,
instruction_index,
instruction_index,
instruction_index,
)
fmt.sbprintf(&emitter.builder, "divzero_trap%d:\n", instruction_index)
zero_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer division by zero")
emit_trap_call(emitter, zero_message)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "division builtin denominator is zero")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\ndivzero_ok%d:\n", instruction_index)
if signed {
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target) - 1))
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
}
emit_division_overflow_guard :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n %%divminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(
&emitter.builder,
", -1\n %%divovf%d = and i1 %%divminlo%d, %%divminhi%d\n br i1 %%divovf%d, label %%divovf_trap%d, label %%divovf_ok%d\ndivovf_trap%d:\n",
instruction_index,
instruction_index,
instruction_index,
instruction_index,
instruction_index,
instruction_index,
instruction_index,
)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer division overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\ndivovf_ok%d:\n", instruction_index)
}
emit_float_division_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
suffix := "f32" if types.bits(instruction.type, emitter.module.target) == 32 else "f64"
if instruction.op == .Rem_Checked || instruction.op == .Mod_Checked {
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked else fmt.tprintf("%%rawrem%d", instruction_index)
fmt.sbprintf(&emitter.builder, " %s = frem %s ", name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n", min_value)
fmt.sbprintf(&emitter.builder, " %%divminhi%d = icmp eq %s ", instruction_index, type_name)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", -1\n")
fmt.sbprintf(&emitter.builder, " %%divovf%d = and i1 %%divminlo%d, %%divminhi%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(
&emitter.builder,
" br i1 %%divovf%d, label %%divovf_trap%d, label %%divovf_ok%d\n",
instruction_index,
instruction_index,
instruction_index,
)
fmt.sbprintf(&emitter.builder, "divovf_trap%d:\n", instruction_index)
ovf_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer division overflow")
emit_trap_call(emitter, ovf_message)
fmt.sbprintf(&emitter.builder, " unreachable\ndivovf_ok%d:\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%v%d = sdiv %s ", instruction_index, type_name)
} else {
fmt.sbprintf(&emitter.builder, " %%v%d = udiv %s ", instruction_index, type_name)
strings.write_string(&emitter.builder, "\n")
if instruction.op == .Rem_Checked {
return
}
fmt.sbprintf(&emitter.builder, " %%remnonzero%d = fcmp one %s %%rawrem%d, 0.000000e+00\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%remsign%d = fcmp olt %s %%rawrem%d, 0.000000e+00\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%denomsign%d = fcmp olt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0.000000e+00\n")
fmt.sbprintf(&emitter.builder, " %%signsdiffer%d = xor i1 %%remsign%d, %%denomsign%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%modadjust%d = and i1 %%remnonzero%d, %%signsdiffer%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%adjustedrem%d = fadd %s %%rawrem%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%v%d = select i1 %%modadjust%d, %s %%adjustedrem%d, %s %%rawrem%d\n", instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
return
}
fmt.sbprintf(&emitter.builder, " %%divq%d = fdiv %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
intrinsic := "trunc"
if instruction.op == .Div_Floor_Checked {
intrinsic = "floor"
} else if instruction.op == .Div_Ceil_Checked {
intrinsic = "ceil"
}
fmt.sbprintf(&emitter.builder, " %%v%d = call %s @llvm.%s.%s(%s %%divq%d)\n", instruction_index, type_name, intrinsic, suffix, type_name, instruction_index)
if instruction.op != .Div_Exact_Checked {
return
}
fmt.sbprintf(&emitter.builder, " %%exactprod%d = fmul %s %%v%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%exact%d = fcmp oeq %s %%exactprod%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n br i1 %%exact%d, label %%exact_ok%d, label %%exact_trap%d\nexact_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "exact division has a remainder")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nexact_ok%d:\n", instruction_index)
}
emit_integer_remainder_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
signed := types.is_signed(instruction.type, emitter.module.target)
raw_name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Rem_Checked || !signed else fmt.tprintf("%%rawrem%d", instruction_index)
if !signed {
fmt.sbprintf(&emitter.builder, " %s = urem %s ", raw_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
} else {
min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target)-1))
fmt.sbprintf(&emitter.builder, " %%remminlo%d = icmp eq %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %d\n %%remminhi%d = icmp eq %s ", min_value, instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", -1\n %%remspecial%d = and i1 %%remminlo%d, %%remminhi%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " br i1 %%remspecial%d, label %%rem_special%d, label %%rem_normal%d\nrem_special%d:\n br label %%rem_join%d\nrem_normal%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%remnormal%d = srem %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n br label %%rem_join%d\nrem_join%d:\n %s = phi %s [ 0, %%rem_special%d ], [ %%remnormal%d, %%rem_normal%d ]\n", instruction_index, instruction_index, raw_name, type_name, instruction_index, instruction_index, instruction_index)
}
if instruction.op == .Rem_Checked || !signed {
return
}
fmt.sbprintf(&emitter.builder, " %%remnonzero%d = icmp ne %s %%rawrem%d, 0\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%remsign%d = icmp slt %s %%rawrem%d, 0\n", instruction_index, type_name, instruction_index)
fmt.sbprintf(&emitter.builder, " %%denomsign%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%signsdiffer%d = xor i1 %%remsign%d, %%denomsign%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%modadjust%d = and i1 %%remnonzero%d, %%signsdiffer%d\n %%adjustedrem%d = add %s %%rawrem%d, ", instruction_index, instruction_index, instruction_index, instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%v%d = select i1 %%modadjust%d, %s %%adjustedrem%d, %s %%rawrem%d\n", instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
}
emit_integer_quotient_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
type_name := llvm_type(instruction.type, &emitter.module.types)
signed := types.is_signed(instruction.type, emitter.module.target)
operation := "sdiv" if signed else "udiv"
name := fmt.tprintf("%%v%d", instruction_index) if instruction.op == .Div_Trunc_Checked else fmt.tprintf("%%divq%d", instruction_index)
fmt.sbprintf(&emitter.builder, " %s = %s %s ", name, operation, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
if instruction.op == .Div_Trunc_Checked {
return
}
fmt.sbprintf(&emitter.builder, " %%divprod%d = mul %s %%divq%d, ", instruction_index, type_name, instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, "\n %%divrem%d = sub %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %%divprod%d\n", instruction_index)
if instruction.op == .Div_Exact_Checked {
fmt.sbprintf(&emitter.builder, " %%exact%d = icmp eq %s %%divrem%d, 0\n br i1 %%exact%d, label %%exact_ok%d, label %%exact_trap%d\nexact_trap%d:\n", instruction_index, type_name, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "exact division has a remainder")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nexact_ok%d:\n %%v%d = add %s %%divq%d, 0\n", instruction_index, instruction_index, type_name, instruction_index)
return
}
fmt.sbprintf(&emitter.builder, " %%remnonzero%d = icmp ne %s %%divrem%d, 0\n", instruction_index, type_name, instruction_index)
if signed {
fmt.sbprintf(&emitter.builder, " %%numsign%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%denomsign%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%signsdiffer%d = xor i1 %%numsign%d, %%denomsign%d\n", instruction_index, instruction_index, instruction_index)
predicate := fmt.tprintf("%%signsdiffer%d", instruction_index)
if instruction.op == .Div_Ceil_Checked {
fmt.sbprintf(&emitter.builder, " %%signssame%d = xor i1 %%signsdiffer%d, true\n", instruction_index, instruction_index)
predicate = fmt.tprintf("%%signssame%d", instruction_index)
}
fmt.sbprintf(&emitter.builder, " %%divadjust%d = and i1 %%remnonzero%d, %s\n", instruction_index, instruction_index, predicate)
} else {
fmt.sbprintf(&emitter.builder, " %%divadjust%d = and i1 %%remnonzero%d, true\n", instruction_index, instruction_index)
}
adjustment := "sub" if instruction.op == .Div_Floor_Checked else "add"
fmt.sbprintf(&emitter.builder, " %%adjustedq%d = %s %s %%divq%d, 1\n %%v%d = select i1 %%divadjust%d, %s %%adjustedq%d, %s %%divq%d\n", instruction_index, adjustment, type_name, instruction_index, instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
}
emit_division_builtin :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
emit_division_zero_guard(emitter, instructions, instruction_index, instruction)
if types.is_float(instruction.type, emitter.module.target) {
emit_float_division_builtin(emitter, instructions, instruction_index, instruction)
return
}
quotient := instruction.op == .Div_Trunc_Checked || instruction.op == .Div_Floor_Checked ||
instruction.op == .Div_Exact_Checked || instruction.op == .Div_Ceil_Checked
if quotient && types.is_signed(instruction.type, emitter.module.target) {
emit_division_overflow_guard(emitter, instructions, instruction_index, instruction)
}
if quotient {
emit_integer_quotient_builtin(emitter, instructions, instruction_index, instruction)
} else {
emit_integer_remainder_builtin(emitter, instructions, instruction_index, instruction)
}
}
emit_instruction_stream :: proc(
@@ -1609,11 +1779,22 @@ emit_instruction_stream :: proc(
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow")
case .Div_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
!types.is_float(instruction.type, emitter.module.target) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand")
continue
}
emit_checked_division(emitter, instructions, instruction_index, instruction)
emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "div", "fdiv", "")
case .Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked:
if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) ||
(!types.is_concrete_integer(instruction.type) &&
!types.is_float(instruction.type, emitter.module.target)) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid division builtin operands")
continue
}
emit_division_builtin(emitter, instructions, instruction_index, instruction)
case .Pointer_Add:
result_item, result_ok := types.node(&emitter.module.types, instruction.type)
base_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
@@ -2283,6 +2464,7 @@ emit_messages :: proc(emitter: ^Emitter) {
emit_declarations :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\ndeclare void @llvm.memset.p0.i64(ptr, i8, i64, i1 immarg)\n")
strings.write_string(&emitter.builder, "declare float @llvm.trunc.f32(float)\ndeclare double @llvm.trunc.f64(double)\ndeclare float @llvm.floor.f32(float)\ndeclare double @llvm.floor.f64(double)\ndeclare float @llvm.ceil.f32(float)\ndeclare double @llvm.ceil.f64(double)\n")
widths := [?]int{8, 16, 32, 64}
overflow_intrinsics := [?]string{"sadd", "uadd", "ssub", "usub", "smul", "umul"}
for bits in widths {
+8 -1
View File
@@ -681,7 +681,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Negate:
stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add, .Sub, .Mul, .Div, .Pointer_Add:
case .Add, .Sub, .Mul, .Div, .Div_Trunc, .Div_Floor, .Div_Exact, .Div_Ceil,
.Rem, .Mod, .Pointer_Add:
stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call:
@@ -760,6 +761,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Sub: op = .Sub_Checked
case .Mul: op = .Mul_Checked
case .Div: op = .Div_Checked
case .Div_Trunc: op = .Div_Trunc_Checked
case .Div_Floor: op = .Div_Floor_Checked
case .Div_Exact: op = .Div_Exact_Checked
case .Div_Ceil: op = .Div_Ceil_Checked
case .Rem: op = .Rem_Checked
case .Mod: op = .Mod_Checked
case .Pointer_Add: op = .Pointer_Add
}
last = append_instruction(state, ir.Instruction{