aboutsummaryrefslogtreecommitdiff
path: root/src/Sema.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2025-03-10 03:31:36 +0000
committerMatthew Lugg <mlugg@mlugg.co.uk>2025-03-16 08:17:50 +0000
commit2a4e06bcb30f71e83b14026bcbade6aac3aece84 (patch)
tree7af2416d23614ba48803f841af9e4861c0ead8a3 /src/Sema.zig
parentaa3db7cc15520228c0b44328198c78a31e194209 (diff)
downloadzig-2a4e06bcb30f71e83b14026bcbade6aac3aece84.tar.gz
zig-2a4e06bcb30f71e83b14026bcbade6aac3aece84.zip
Sema: rewrite comptime arithmetic
This commit reworks how Sema handles arithmetic on comptime-known values, fixing many bugs in the process. The general pattern is that arithmetic on comptime-known values is now handled by the new namespace `Sema.arith`. Functions handling comptime arithmetic no longer live on `Value`; this is because some of them can emit compile errors, so some *can't* go on `Value`. Only semantic analysis should really be doing arithmetic on `Value`s anyway, so it makes sense for it to integrate more tightly with `Sema`. This commit also implements more coherent rules surrounding how `undefined` interacts with comptime and mixed-comptime-runtime arithmetic. The rules are as follows. * If an operation cannot trigger Illegal Behavior, and any operand is `undefined`, the result is `undefined`. This includes operations like `0 *| undef`, where the LHS logically *could* be used to determine a defined result. This is partly to simplify the language, but mostly to permit codegen backends to represent `undefined` values as completely invalid states. * If an operation *can* trigger Illegal Behvaior, and any operand is `undefined`, then Illegal Behavior results. This occurs even if the operand in question isn't the one that "decides" illegal behavior; for instance, `undef / 1` is undefined. This is for the same reasons as described above. * An operation which would trigger Illegal Behavior, when evaluated at comptime, instead triggers a compile error. Additionally, if one operand is comptime-known undef, such that the other (runtime-known) operand isn't needed to determine that Illegal Behavior would occur, the compile error is triggered. * The only situation in which an operation with one comptime-known operand has a comptime-known result is if that operand is undefined, in which case the result is either undefined or a compile error per the above rules. This could potentially be loosened in future (for instance, `0 * rt` could be comptime-known 0 with a runtime assertion that `rt` is not undefined), but at least for now, defining it more conservatively simplifies the language and allows us to easily change this in future if desired. This commit fixes many bugs regarding the handling of `undefined`, particularly in vectors. Along with a collection of smaller tests, two very large test cases are added to check arithmetic on `undefined`. The operations which have been rewritten in this PR are: * `+`, `+%`, `+|`, `@addWithOverflow` * `-`, `-%`, `-|`, `@subWithOverflow` * `*`, `*%`, `*|`, `@mulWithOverflow` * `/`, `@divFloor`, `@divTrunc`, `@divExact` * `%`, `@rem`, `@mod` Other arithmetic operations are currently unchanged. Resolves: #22743 Resolves: #22745 Resolves: #22748 Resolves: #22749 Resolves: #22914
Diffstat (limited to 'src/Sema.zig')
-rw-r--r--src/Sema.zig1753
1 files changed, 387 insertions, 1366 deletions
diff --git a/src/Sema.zig b/src/Sema.zig
index 9fd862f50c..cd1711c8b7 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -188,6 +188,7 @@ const AnalUnit = InternPool.AnalUnit;
const ComptimeAllocIndex = InternPool.ComptimeAllocIndex;
const Cache = std.Build.Cache;
const LowerZon = @import("Sema/LowerZon.zig");
+const arith = @import("Sema/arith.zig");
pub const default_branch_quota = 1000;
pub const default_reference_trace_len = 2;
@@ -2325,11 +2326,11 @@ fn failWithNeededComptime(sema: *Sema, block: *Block, src: LazySrcLoc, reason: ?
return sema.failWithOwnedErrorMsg(fail_block, msg);
}
-fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
+pub fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
return sema.fail(block, src, "use of undefined value here causes undefined behavior", .{});
}
-fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
+pub fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
return sema.fail(block, src, "division by zero here causes undefined behavior", .{});
}
@@ -2391,22 +2392,15 @@ fn failWithErrorSetCodeMissing(
});
}
-fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: usize) CompileError {
+pub fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: ?usize) CompileError {
const pt = sema.pt;
- const zcu = pt.zcu;
- if (int_ty.zigTypeTag(zcu) == .vector) {
- const msg = msg: {
- const msg = try sema.errMsg(src, "overflow of vector type '{}' with value '{}'", .{
- int_ty.fmt(pt), val.fmtValueSema(pt, sema),
- });
- errdefer msg.destroy(sema.gpa);
- try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{vector_index});
- break :msg msg;
- };
- return sema.failWithOwnedErrorMsg(block, msg);
- }
- return sema.fail(block, src, "overflow of integer type '{}' with value '{}'", .{
- int_ty.fmt(pt), val.fmtValueSema(pt, sema),
+ return sema.failWithOwnedErrorMsg(block, msg: {
+ const msg = try sema.errMsg(src, "overflow of integer type '{}' with value '{}'", .{
+ int_ty.fmt(pt), val.fmtValueSema(pt, sema),
+ });
+ errdefer msg.destroy(sema.gpa);
+ if (vector_index) |i| try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{i});
+ break :msg msg;
});
}
@@ -10327,7 +10321,9 @@ fn intCast(
.storage = .{ .repeated_elem = one_scalar.toIntern() },
} })) else one_scalar;
const range_minus_one = try dest_max_val.shl(one, unsigned_operand_ty, sema.arena, pt);
- break :range_val try sema.intAdd(range_minus_one, one, unsigned_operand_ty, undefined);
+ const result = try arith.addWithOverflow(sema, unsigned_operand_ty, range_minus_one, one);
+ assert(result.overflow_bit.compareAllWithZero(.eq, zcu));
+ break :range_val result.wrapped_result;
} else try pt.getCoerced(dest_max_val, unsigned_operand_ty);
const dest_range = Air.internedToRef(dest_range_val.toIntern());
@@ -12723,10 +12719,9 @@ fn analyzeSwitchRuntimeBlock(
while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
// Previous validation has resolved any possible lazy values.
- item = sema.intAddScalar(item, try pt.intValue(operand_ty, 1), operand_ty) catch |err| switch (err) {
- error.Overflow => unreachable,
- else => |e| return e,
- };
+ const result = try arith.incrementDefinedInt(sema, operand_ty, item);
+ assert(!result.overflow);
+ item = result.val;
}) {
cases_len += 1;
@@ -14183,8 +14178,8 @@ fn zirShl(
// TODO coerce rhs if air_tag is not shl_sat
const rhs_is_comptime_int = try sema.checkIntType(block, rhs_src, scalar_rhs_ty);
- const maybe_lhs_val = try sema.resolveValueIntable(lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(rhs);
if (maybe_rhs_val) |rhs_val| {
if (rhs_val.isUndef(zcu)) {
@@ -14359,8 +14354,8 @@ fn zirShr(
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const scalar_ty = lhs_ty.scalarType(zcu);
- const maybe_lhs_val = try sema.resolveValueIntable(lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(rhs);
const runtime_src = if (maybe_rhs_val) |rhs_val| rs: {
if (rhs_val.isUndef(zcu)) {
@@ -14511,8 +14506,8 @@ fn zirBitwise(
const runtime_src = runtime: {
// TODO: ask the linker what kind of relocations are available, and
// in some cases emit a Value that means "this decl's address AND'd with this operand".
- if (try sema.resolveValueIntable(casted_lhs)) |lhs_val| {
- if (try sema.resolveValueIntable(casted_rhs)) |rhs_val| {
+ if (try sema.resolveValueResolveLazy(casted_lhs)) |lhs_val| {
+ if (try sema.resolveValueResolveLazy(casted_rhs)) |rhs_val| {
const result_val = switch (air_tag) {
.bit_and => try lhs_val.bitwiseAnd(rhs_val, resolved_type, sema.arena, pt),
.bit_or => try lhs_val.bitwiseOr(rhs_val, resolved_type, sema.arena, pt),
@@ -15289,8 +15284,8 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
if (rhs_scalar_ty.isAnyFloat()) {
// We handle float negation here to ensure negative zero is represented in the bits.
if (try sema.resolveValue(rhs)) |rhs_val| {
- if (rhs_val.isUndef(zcu)) return pt.undefRef(rhs_ty);
- return Air.internedToRef((try rhs_val.floatNeg(rhs_ty, sema.arena, pt)).toIntern());
+ const result = try arith.negateFloat(sema, rhs_ty, rhs_val);
+ return Air.internedToRef(result.toIntern());
}
try sema.requireRuntimeBlock(block, src, null);
return block.addUnOp(if (block.float_mode == .optimized) .neg_optimized else .neg, rhs);
@@ -15359,24 +15354,22 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
- const rhs_scalar_ty = rhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
if ((lhs_ty.zigTypeTag(zcu) == .comptime_float and rhs_ty.zigTypeTag(zcu) == .comptime_int) or
(lhs_ty.zigTypeTag(zcu) == .comptime_int and rhs_ty.zigTypeTag(zcu) == .comptime_float))
@@ -15399,92 +15392,38 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
// TODO: emit compile error when .div is used on integers and there would be an
// ambiguous result between div_floor and div_trunc.
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined:
- // * if lhs type is signed:
- // * if rhs is comptime-known and not -1, result is undefined
- // * if rhs is -1 or runtime-known, compile error because there is a
- // possible value (-min_int / -1) for which division would be
- // illegal behavior.
- // * if lhs type is unsigned, undef is returned regardless of rhs.
+ // The rules here are like those in `analyzeArithmetic`:
//
- // For floats:
- // If the rhs is zero:
- // * comptime_float: compile error for division by zero.
- // * other float type:
- // * if the lhs is zero: QNaN
- // * otherwise: +Inf or -Inf depending on lhs sign
- // If the rhs is undefined:
- // * comptime_float: compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // * other float type: result is undefined
- // If the lhs is undefined, result is undefined.
- switch (scalar_tag) {
- .int, .comptime_int, .comptime_float => {
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu)) {
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(resolved_type.scalarType(zcu), 0.0),
- .comptime_int, .int => try pt.intValue(resolved_type.scalarType(zcu), 0),
- else => unreachable,
- };
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- // TODO: if the RHS is one, return the LHS directly
- }
- },
- else => {},
- }
+ // * If both operands are comptime-known, call the corresponding function in `arith`.
+ // Inputs which would be IB at runtime are compile errors.
+ //
+ // * Otherwise, if one operand is comptime-known `undefined`, we either trigger a compile error
+ // or return `undefined`, depending on whether this operator can trigger IB.
+ //
+ // * No other comptime operand determines a comptime result, so remaining cases are runtime ops.
- const runtime_src = rs: {
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- if (lhs_scalar_ty.isSignedInt(zcu) and rhs_scalar_ty.isSignedInt(zcu)) {
- if (maybe_rhs_val) |rhs_val| {
- if (try sema.compareAll(rhs_val, .neq, try pt.intValue(resolved_type, -1), resolved_type)) {
- return pt.undefRef(resolved_type);
- }
- }
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- return pt.undefRef(resolved_type);
- }
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- var overflow_idx: ?usize = null;
- const res = try lhs_val.intDiv(rhs_val, resolved_type, &overflow_idx, sema.arena, pt);
- if (overflow_idx) |vec_idx| {
- return sema.failWithIntegerOverflow(block, src, resolved_type, res, vec_idx);
- }
- return Air.internedToRef(res.toIntern());
- } else {
- return Air.internedToRef((try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else {
- break :rs rhs_src;
- }
+ if (maybe_lhs_val) |lhs_val| {
+ if (maybe_rhs_val) |rhs_val| {
+ const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div);
+ return Air.internedToRef(result.toIntern());
+ }
+ if (allow_div_zero) {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
} else {
- break :rs lhs_src;
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
}
- };
-
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
@@ -15525,9 +15464,8 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
@@ -15540,75 +15478,21 @@ fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_exact);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- const runtime_src = rs: {
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, compile error because there is a possible
- // value for which the division would result in a remainder.
- // TODO: emit runtime safety for if there is a remainder
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, compile error because there is a possible
- // value for which the division would result in a remainder.
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- } else {
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(resolved_type.scalarType(zcu), 0.0),
- .comptime_int, .int => try pt.intValue(resolved_type.scalarType(zcu), 0),
- else => unreachable,
- };
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- }
- }
+ // Because `@divExact` can trigger Illegal Behavior, undefined operands trigger Illegal Behavior.
+
+ if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- // TODO: if the RHS is one, return the LHS directly
+ const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_exact);
+ return Air.internedToRef(result.toIntern());
}
- if (maybe_lhs_val) |lhs_val| {
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- const modulus_val = try lhs_val.intMod(rhs_val, resolved_type, sema.arena, pt);
- if (!(modulus_val.compareAllWithZero(.eq, zcu))) {
- return sema.fail(block, src, "exact division produced remainder", .{});
- }
- var overflow_idx: ?usize = null;
- const res = try lhs_val.intDiv(rhs_val, resolved_type, &overflow_idx, sema.arena, pt);
- if (overflow_idx) |vec_idx| {
- return sema.failWithIntegerOverflow(block, src, resolved_type, res, vec_idx);
- }
- return Air.internedToRef(res.toIntern());
- } else {
- const modulus_val = try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, pt);
- if (!(modulus_val.compareAllWithZero(.eq, zcu))) {
- return sema.fail(block, src, "exact division produced remainder", .{});
- }
- return Air.internedToRef((try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else break :rs rhs_src;
- } else break :rs lhs_src;
- };
-
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
// Depending on whether safety is enabled, we will have a slightly different strategy
// here. The `div_exact` AIR instruction causes undefined behavior if a remainder
@@ -15691,91 +15575,45 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
- const rhs_scalar_ty = rhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_floor);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- const runtime_src = rs: {
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined:
- // * if lhs type is signed:
- // * if rhs is comptime-known and not -1, result is undefined
- // * if rhs is -1 or runtime-known, compile error because there is a
- // possible value (-min_int / -1) for which division would be
- // illegal behavior.
- // * if lhs type is unsigned, undef is returned regardless of rhs.
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu)) {
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(resolved_type.scalarType(zcu), 0.0),
- .comptime_int, .int => try pt.intValue(resolved_type.scalarType(zcu), 0),
- else => unreachable,
- };
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- }
- }
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
+
+ if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- // TODO: if the RHS is one, return the LHS directly
+ const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_floor);
+ return Air.internedToRef(result.toIntern());
}
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- if (lhs_scalar_ty.isSignedInt(zcu) and rhs_scalar_ty.isSignedInt(zcu)) {
- if (maybe_rhs_val) |rhs_val| {
- if (try sema.compareAll(rhs_val, .neq, try pt.intValue(resolved_type, -1), resolved_type)) {
- return pt.undefRef(resolved_type);
- }
- }
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- return pt.undefRef(resolved_type);
- }
-
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- return Air.internedToRef((try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- } else {
- return Air.internedToRef((try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else break :rs rhs_src;
- } else break :rs lhs_src;
- };
-
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ if (allow_div_zero) {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
@@ -15802,95 +15640,45 @@ fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
- const rhs_scalar_ty = rhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_trunc);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- const runtime_src = rs: {
- // For integers:
- // If the lhs is zero, then zero is returned regardless of rhs.
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined:
- // * if lhs type is signed:
- // * if rhs is comptime-known and not -1, result is undefined
- // * if rhs is -1 or runtime-known, compile error because there is a
- // possible value (-min_int / -1) for which division would be
- // illegal behavior.
- // * if lhs type is unsigned, undef is returned regardless of rhs.
- // TODO: emit runtime safety for division by zero
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu)) {
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(resolved_type.scalarType(zcu), 0.0),
- .comptime_int, .int => try pt.intValue(resolved_type.scalarType(zcu), 0),
- else => unreachable,
- };
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- }
- }
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
+
+ if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
+ const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_trunc);
+ return Air.internedToRef(result.toIntern());
}
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- if (lhs_scalar_ty.isSignedInt(zcu) and rhs_scalar_ty.isSignedInt(zcu)) {
- if (maybe_rhs_val) |rhs_val| {
- if (try sema.compareAll(rhs_val, .neq, try pt.intValue(resolved_type, -1), resolved_type)) {
- return pt.undefRef(resolved_type);
- }
- }
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- return pt.undefRef(resolved_type);
- }
-
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- var overflow_idx: ?usize = null;
- const res = try lhs_val.intDiv(rhs_val, resolved_type, &overflow_idx, sema.arena, pt);
- if (overflow_idx) |vec_idx| {
- return sema.failWithIntegerOverflow(block, src, resolved_type, res, vec_idx);
- }
- return Air.internedToRef(res.toIntern());
- } else {
- return Air.internedToRef((try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else break :rs rhs_src;
- } else break :rs lhs_src;
- };
-
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ if (allow_div_zero) {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
@@ -15939,46 +15727,81 @@ fn addDivIntOverflowSafety(
if (try rhs_val.compareAll(.neq, neg_one, resolved_type, pt)) return;
}
- var ok: Air.Inst.Ref = .none;
if (resolved_type.zigTypeTag(zcu) == .vector) {
- if (maybe_lhs_val == null) {
+ const vec_len = resolved_type.vectorLen(zcu);
+
+ // This is a bool vector whose elements are true if the LHS element does NOT equal `min_int`.
+ const lhs_ok: Air.Inst.Ref = if (maybe_lhs_val) |lhs_val| ok: {
+ // The operand is comptime-known; intern a constant bool vector for the potentially unsafe elements.
+ const min_int_scalar = try min_int.elemValue(pt, 0);
+ const elems_ok = try sema.arena.alloc(InternPool.Index, vec_len);
+ for (elems_ok, 0..) |*elem_ok, elem_idx| {
+ const elem_val = try lhs_val.elemValue(pt, elem_idx);
+ elem_ok.* = if (elem_val.eqlScalarNum(min_int_scalar, zcu)) .bool_false else .bool_true;
+ }
+ break :ok Air.internedToRef(try pt.intern(.{ .aggregate = .{
+ .ty = (try pt.vectorType(.{
+ .len = vec_len,
+ .child = .bool_type,
+ })).toIntern(),
+ .storage = .{ .elems = elems_ok },
+ } }));
+ } else ok: {
+ // The operand isn't comptime-known; add a runtime comparison.
const min_int_ref = Air.internedToRef(min_int.toIntern());
- ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq);
- }
- if (maybe_rhs_val == null) {
+ break :ok try block.addCmpVector(casted_lhs, min_int_ref, .neq);
+ };
+
+ // This is a bool vector whose elements are true if the RHS element does NOT equal -1.
+ const rhs_ok: Air.Inst.Ref = if (maybe_rhs_val) |rhs_val| ok: {
+ // The operand is comptime-known; intern a constant bool vector for the potentially unsafe elements.
+ const elems_ok = try sema.arena.alloc(InternPool.Index, vec_len);
+ for (elems_ok, 0..) |*elem_ok, elem_idx| {
+ const elem_val = try rhs_val.elemValue(pt, elem_idx);
+ elem_ok.* = if (elem_val.eqlScalarNum(neg_one_scalar, zcu)) .bool_false else .bool_true;
+ }
+ break :ok Air.internedToRef(try pt.intern(.{ .aggregate = .{
+ .ty = (try pt.vectorType(.{
+ .len = vec_len,
+ .child = .bool_type,
+ })).toIntern(),
+ .storage = .{ .elems = elems_ok },
+ } }));
+ } else ok: {
+ // The operand isn't comptime-known; add a runtime comparison.
const neg_one_ref = Air.internedToRef(neg_one.toIntern());
- const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq);
- if (ok == .none) {
- ok = rhs_ok;
- } else {
- ok = try block.addBinOp(.bool_or, ok, rhs_ok);
- }
- }
- assert(ok != .none);
- ok = try block.addInst(.{
+ break :ok try block.addCmpVector(casted_rhs, neg_one_ref, .neq);
+ };
+
+ const ok = try block.addInst(.{
.tag = .reduce,
.data = .{ .reduce = .{
- .operand = ok,
+ .operand = try block.addBinOp(.bool_or, lhs_ok, rhs_ok),
.operation = .And,
} },
});
+ try sema.addSafetyCheck(block, src, ok, .integer_overflow);
} else {
- if (maybe_lhs_val == null) {
+ const lhs_ok: Air.Inst.Ref = if (maybe_lhs_val == null) ok: {
const min_int_ref = Air.internedToRef(min_int.toIntern());
- ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref);
- }
- if (maybe_rhs_val == null) {
+ break :ok try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref);
+ } else .none; // means false
+ const rhs_ok: Air.Inst.Ref = if (maybe_rhs_val == null) ok: {
const neg_one_ref = Air.internedToRef(neg_one.toIntern());
- const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref);
- if (ok == .none) {
- ok = rhs_ok;
- } else {
- ok = try block.addBinOp(.bool_or, ok, rhs_ok);
- }
- }
- assert(ok != .none);
+ break :ok try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref);
+ } else .none; // means false
+
+ const ok = if (lhs_ok != .none and rhs_ok != .none)
+ try block.addBinOp(.bool_or, lhs_ok, rhs_ok)
+ else if (lhs_ok != .none)
+ lhs_ok
+ else if (rhs_ok != .none)
+ rhs_ok
+ else
+ unreachable;
+
+ try sema.addSafetyCheck(block, src, ok, .integer_overflow);
}
- try sema.addSafetyCheck(block, src, ok, .integer_overflow);
}
fn addDivByZeroSafety(
@@ -16046,13 +15869,10 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
- const is_vector = resolved_type.zigTypeTag(zcu) == .vector;
-
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
@@ -16064,96 +15884,66 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .mod_rem);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- const runtime_src = rs: {
- // For integers:
- // Either operand being undef is a compile error because there exists
- // a possible value (TODO what is it?) that would invoke illegal behavior.
- // TODO: can lhs undef be handled better?
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- //
- // For either one: if the result would be different between @mod and @rem,
- // then emit a compile error saying you have to pick one.
- if (is_int) {
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, lhs_src);
- }
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(resolved_type.scalarType(zcu), 0.0),
- .comptime_int, .int => try pt.intValue(resolved_type.scalarType(zcu), 0),
- else => unreachable,
- };
- const zero_val = if (is_vector) Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = resolved_type.toIntern(),
- .storage = .{ .repeated_elem = scalar_zero.toIntern() },
- } })) else scalar_zero;
- return Air.internedToRef(zero_val.toIntern());
- }
- } else if (lhs_scalar_ty.isSignedInt(zcu)) {
- return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.gte, pt))) {
- return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
- }
- if (maybe_lhs_val) |lhs_val| {
- const rem_result = try sema.intRem(resolved_type, lhs_val, rhs_val);
- // If this answer could possibly be different by doing `intMod`,
- // we must emit a compile error. Otherwise, it's OK.
- if (!(try lhs_val.compareAllWithZeroSema(.gte, pt)) and
- !(try rem_result.compareAllWithZeroSema(.eq, pt)))
- {
- return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
- }
- return Air.internedToRef(rem_result.toIntern());
- }
- break :rs lhs_src;
- } else if (rhs_scalar_ty.isSignedInt(zcu)) {
- return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
- } else {
- break :rs rhs_src;
- }
- }
- // float operands
+ const lhs_maybe_negative = a: {
+ if (lhs_scalar_ty.isUnsignedInt(zcu)) break :a false;
+ const lhs_val = maybe_lhs_val orelse break :a true;
+ if (lhs_val.compareAllWithZero(.gte, zcu)) break :a false;
+ break :a true;
+ };
+ const rhs_maybe_negative = a: {
+ if (rhs_scalar_ty.isUnsignedInt(zcu)) break :a false;
+ const rhs_val = maybe_rhs_val orelse break :a true;
+ if (rhs_val.compareAllWithZero(.gte, zcu)) break :a false;
+ break :a true;
+ };
+
+ if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.gte, pt))) {
- return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu) or !(try lhs_val.compareAllWithZeroSema(.gte, pt))) {
- return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
+ const result = try arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .rem);
+ if (lhs_maybe_negative or rhs_maybe_negative) {
+ if (!result.compareAllWithZero(.eq, zcu)) {
+ // Non-zero result means ambiguity between mod and rem
+ return sema.failWithModRemNegative(block, src: {
+ if (lhs_maybe_negative) break :src lhs_src;
+ if (rhs_maybe_negative) break :src rhs_src;
+ unreachable;
+ }, lhs_ty, rhs_ty);
}
- return Air.internedToRef((try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- } else {
- return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty);
}
- } else {
- return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty);
+ return Air.internedToRef(result.toIntern());
}
- };
+ }
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ // Result not comptime-known, so floats and signed integers are illegal due to mod/rem ambiguity
+ if (lhs_maybe_negative or rhs_maybe_negative) {
+ return sema.failWithModRemNegative(block, src: {
+ if (lhs_maybe_negative) break :src lhs_src;
+ if (rhs_maybe_negative) break :src rhs_src;
+ unreachable;
+ }, lhs_ty, rhs_ty);
+ }
+
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
+
+ if (maybe_lhs_val) |lhs_val| {
+ if (allow_div_zero) {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
if (block.wantSafety()) {
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
@@ -16163,58 +15953,6 @@ fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
-fn intRem(
- sema: *Sema,
- ty: Type,
- lhs: Value,
- rhs: Value,
-) CompileError!Value {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (ty.zigTypeTag(zcu) == .vector) {
- const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(zcu));
- const scalar_ty = ty.scalarType(zcu);
- for (result_data, 0..) |*scalar, i| {
- const lhs_elem = try lhs.elemValue(pt, i);
- const rhs_elem = try rhs.elemValue(pt, i);
- scalar.* = (try sema.intRemScalar(lhs_elem, rhs_elem, scalar_ty)).toIntern();
- }
- return Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = ty.toIntern(),
- .storage = .{ .elems = result_data },
- } }));
- }
- return sema.intRemScalar(lhs, rhs, ty);
-}
-
-fn intRemScalar(sema: *Sema, lhs: Value, rhs: Value, scalar_ty: Type) CompileError!Value {
- const pt = sema.pt;
- // TODO is this a performance issue? maybe we should try the operation without
- // resorting to BigInt first.
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = try lhs.toBigIntSema(&lhs_space, pt);
- const rhs_bigint = try rhs.toBigIntSema(&rhs_space, pt);
- const limbs_q = try sema.arena.alloc(
- math.big.Limb,
- lhs_bigint.limbs.len,
- );
- const limbs_r = try sema.arena.alloc(
- math.big.Limb,
- // TODO: consider reworking Sema to re-use Values rather than
- // always producing new Value objects.
- rhs_bigint.limbs.len,
- );
- const limbs_buffer = try sema.arena.alloc(
- math.big.Limb,
- math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
- );
- var result_q = math.big.int.Mutable{ .limbs = limbs_q, .positive = undefined, .len = undefined };
- var result_r = math.big.int.Mutable{ .limbs = limbs_r, .positive = undefined, .len = undefined };
- result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer);
- return pt.intValue_big(scalar_ty, result_r.toConst());
-}
-
fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
@@ -16232,9 +15970,8 @@ fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
@@ -16246,62 +15983,31 @@ fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .mod);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- const runtime_src = rs: {
- // For integers:
- // Either operand being undef is a compile error because there exists
- // a possible value (TODO what is it?) that would invoke illegal behavior.
- // TODO: can lhs zero be handled better?
- // TODO: can lhs undef be handled better?
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- if (is_int) {
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, lhs_src);
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- if (maybe_lhs_val) |lhs_val| {
- return Air.internedToRef((try lhs_val.intMod(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- break :rs lhs_src;
- } else {
- break :rs rhs_src;
- }
- }
- // float operands
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
+
+ if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
+ const result = try arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .mod);
+ return Air.internedToRef(result.toIntern());
}
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (maybe_rhs_val) |rhs_val| {
- return Air.internedToRef((try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- } else break :rs rhs_src;
- } else break :rs lhs_src;
- };
-
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ if (allow_div_zero) {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
if (block.wantSafety()) {
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
@@ -16328,9 +16034,8 @@ fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
@@ -16342,62 +16047,31 @@ fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .rem);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- const runtime_src = rs: {
- // For integers:
- // Either operand being undef is a compile error because there exists
- // a possible value (TODO what is it?) that would invoke illegal behavior.
- // TODO: can lhs zero be handled better?
- // TODO: can lhs undef be handled better?
- //
- // For floats:
- // If the rhs is zero, compile error for division by zero.
- // If the rhs is undefined, compile error because there is a possible
- // value (zero) for which the division would be illegal behavior.
- // If the lhs is undefined, result is undefined.
- if (is_int) {
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, lhs_src);
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
- if (maybe_lhs_val) |lhs_val| {
- return Air.internedToRef((try sema.intRem(resolved_type, lhs_val, rhs_val)).toIntern());
- }
- break :rs lhs_src;
- } else {
- break :rs rhs_src;
- }
- }
- // float operands
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
+
+ if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return sema.failWithUseOfUndef(block, rhs_src);
- }
- if (!(try rhs_val.compareAllWithZeroSema(.neq, pt))) {
- return sema.failWithDivideByZero(block, rhs_src);
- }
+ const result = try arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .rem);
+ return Air.internedToRef(result.toIntern());
}
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (maybe_rhs_val) |rhs_val| {
- return Air.internedToRef((try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- } else break :rs rhs_src;
- } else break :rs lhs_src;
- };
-
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ if (allow_div_zero) {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
if (block.wantSafety()) {
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
@@ -16486,7 +16160,7 @@ fn zirOverflowArithmetic(
break :result .{ .overflow_bit = Value.undef, .wrapped = Value.undef };
}
- const result = try sema.intAddWithOverflow(lhs_val, rhs_val, dest_ty);
+ const result = try arith.addWithOverflow(sema, dest_ty, lhs_val, rhs_val);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
}
@@ -16504,7 +16178,7 @@ fn zirOverflowArithmetic(
break :result .{ .overflow_bit = Value.undef, .wrapped = Value.undef };
}
- const result = try sema.intSubWithOverflow(lhs_val, rhs_val, dest_ty);
+ const result = try arith.subWithOverflow(sema, dest_ty, lhs_val, rhs_val);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
}
@@ -16540,7 +16214,7 @@ fn zirOverflowArithmetic(
break :result .{ .overflow_bit = Value.undef, .wrapped = Value.undef };
}
- const result = try lhs_val.intMulWithOverflow(rhs_val, dest_ty, sema.arena, pt);
+ const result = try arith.mulWithOverflow(sema, dest_ty, lhs_val, rhs_val);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
}
@@ -16741,9 +16415,8 @@ fn analyzeArithmetic(
}
}
- const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
- const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
- .override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
@@ -16752,407 +16425,74 @@ fn analyzeArithmetic(
const scalar_type = resolved_type.scalarType(zcu);
const scalar_tag = scalar_type.zigTypeTag(zcu);
- const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
-
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag);
- const maybe_lhs_val = try sema.resolveValueIntable(casted_lhs);
- const maybe_rhs_val = try sema.resolveValueIntable(casted_rhs);
- const runtime_src: LazySrcLoc, const air_tag: Air.Inst.Tag, const air_tag_safe: Air.Inst.Tag = rs: {
- switch (zir_tag) {
- .add, .add_unsafe => {
- // For integers:intAddSat
- // If either of the operands are zero, then the other operand is
- // returned, even if it is undefined.
- // If either of the operands are undefined, it's a compile error
- // because there is a possible value for which the addition would
- // overflow (max_int), causing illegal behavior.
- // For floats: either operand being undef makes the result undef.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu) and (try lhs_val.compareAllWithZeroSema(.eq, pt))) {
- return casted_rhs;
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- if (is_int) {
- return sema.failWithUseOfUndef(block, rhs_src);
- } else {
- return pt.undefRef(resolved_type);
- }
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- return casted_lhs;
- }
- }
- const air_tag: Air.Inst.Tag = if (block.float_mode == .optimized) .add_optimized else .add;
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- if (is_int) {
- return sema.failWithUseOfUndef(block, lhs_src);
- } else {
- return pt.undefRef(resolved_type);
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- var overflow_idx: ?usize = null;
- const sum = try sema.intAdd(lhs_val, rhs_val, resolved_type, &overflow_idx);
- if (overflow_idx) |vec_idx| {
- return sema.failWithIntegerOverflow(block, src, resolved_type, sum, vec_idx);
- }
- return Air.internedToRef(sum.toIntern());
- } else {
- return Air.internedToRef((try Value.floatAdd(lhs_val, rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else break :rs .{ rhs_src, air_tag, .add_safe };
- } else break :rs .{ lhs_src, air_tag, .add_safe };
- },
- .addwrap => {
- // Integers only; floats are checked above.
- // If either of the operands are zero, the other operand is returned.
- // If either of the operands are undefined, the result is undefined.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu) and (try lhs_val.compareAllWithZeroSema(.eq, pt))) {
- return casted_rhs;
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- return casted_lhs;
- }
- if (maybe_lhs_val) |lhs_val| {
- return Air.internedToRef((try sema.numberAddWrapScalar(lhs_val, rhs_val, resolved_type)).toIntern());
- } else break :rs .{ lhs_src, .add_wrap, .add_wrap };
- } else break :rs .{ rhs_src, .add_wrap, .add_wrap };
- },
- .add_sat => {
- // Integers only; floats are checked above.
- // If either of the operands are zero, then the other operand is returned.
- // If either of the operands are undefined, the result is undefined.
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu) and (try lhs_val.compareAllWithZeroSema(.eq, pt))) {
- return casted_rhs;
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- return casted_lhs;
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
-
- const val = if (scalar_tag == .comptime_int)
- try sema.intAdd(lhs_val, rhs_val, resolved_type, undefined)
- else
- try lhs_val.intAddSat(rhs_val, resolved_type, sema.arena, pt);
+ // The rules we'll implement below are as follows:
+ //
+ // * If both operands are comptime-known, we call the corresponding function in `arith` to get
+ // the comptime-known result. Inputs which would be IB at runtime are compile errors.
+ //
+ // * Otherwise, if one operand is comptime-known `undefined`, we trigger a compile error if this
+ // operator can ever possibly trigger IB, or otherwise return comptime-known `undefined`.
+ //
+ // * No other comptime operand detemines a comptime result; e.g. `0 * x` isn't always `0` because
+ // of `undefined`. Therefore, the remaining cases all become runtime operations.
- return Air.internedToRef(val.toIntern());
- } else break :rs .{
- lhs_src,
- .add_sat,
- .add_sat,
- };
- } else break :rs .{
- rhs_src,
- .add_sat,
- .add_sat,
- };
- },
- .sub => {
- // For integers:
- // If the rhs is zero, then the other operand is
- // returned, even if it is undefined.
- // If either of the operands are undefined, it's a compile error
- // because there is a possible value for which the subtraction would
- // overflow, causing illegal behavior.
- // For floats: either operand being undef makes the result undef.
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- if (is_int) {
- return sema.failWithUseOfUndef(block, rhs_src);
- } else {
- return pt.undefRef(resolved_type);
- }
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- return casted_lhs;
- }
- }
- const air_tag: Air.Inst.Tag = if (block.float_mode == .optimized) .sub_optimized else .sub;
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- if (is_int) {
- return sema.failWithUseOfUndef(block, lhs_src);
- } else {
- return pt.undefRef(resolved_type);
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (is_int) {
- var overflow_idx: ?usize = null;
- const diff = try sema.intSub(lhs_val, rhs_val, resolved_type, &overflow_idx);
- if (overflow_idx) |vec_idx| {
- return sema.failWithIntegerOverflow(block, src, resolved_type, diff, vec_idx);
- }
- return Air.internedToRef(diff.toIntern());
- } else {
- return Air.internedToRef((try Value.floatSub(lhs_val, rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else break :rs .{ rhs_src, air_tag, .sub_safe };
- } else break :rs .{ lhs_src, air_tag, .sub_safe };
- },
- .subwrap => {
- // Integers only; floats are checked above.
- // If the RHS is zero, then the LHS is returned, even if it is undefined.
- // If either of the operands are undefined, the result is undefined.
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- return casted_lhs;
- }
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (maybe_rhs_val) |rhs_val| {
- return Air.internedToRef((try sema.numberSubWrapScalar(lhs_val, rhs_val, resolved_type)).toIntern());
- } else break :rs .{ rhs_src, .sub_wrap, .sub_wrap };
- } else break :rs .{ lhs_src, .sub_wrap, .sub_wrap };
- },
- .sub_sat => {
- // Integers only; floats are checked above.
- // If the RHS is zero, then the LHS is returned, even if it is undefined.
- // If either of the operands are undefined, the result is undefined.
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- return casted_lhs;
- }
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (maybe_rhs_val) |rhs_val| {
- const val = if (scalar_tag == .comptime_int)
- try sema.intSub(lhs_val, rhs_val, resolved_type, undefined)
- else
- try lhs_val.intSubSat(rhs_val, resolved_type, sema.arena, pt);
-
- return Air.internedToRef(val.toIntern());
- } else break :rs .{ rhs_src, .sub_sat, .sub_sat };
- } else break :rs .{ lhs_src, .sub_sat, .sub_sat };
- },
- .mul => {
- // For integers:
- // If either of the operands are zero, the result is zero.
- // If either of the operands are one, the result is the other
- // operand, even if it is undefined.
- // If either of the operands are undefined, it's a compile error
- // because there is a possible value for which the addition would
- // overflow (max_int), causing illegal behavior.
- //
- // For floats:
- // If either of the operands are undefined, the result is undefined.
- // If either of the operands are inf, and the other operand is zero,
- // the result is nan.
- // If either of the operands are nan, the result is nan.
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(scalar_type, 0.0),
- .comptime_int, .int => try pt.intValue(scalar_type, 0),
- else => unreachable,
- };
- const scalar_one = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(scalar_type, 1.0),
- .comptime_int, .int => try pt.intValue(scalar_type, 1),
- else => unreachable,
- };
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu)) {
- if (lhs_val.isNan(zcu)) {
- return Air.internedToRef(lhs_val.toIntern());
- }
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) lz: {
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isNan(zcu)) {
- return Air.internedToRef(rhs_val.toIntern());
- }
- if (rhs_val.isInf(zcu)) {
- return Air.internedToRef((try pt.floatValue(resolved_type, std.math.nan(f128))).toIntern());
- }
- } else if (resolved_type.isAnyFloat()) {
- break :lz;
- }
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- if (try sema.compareAll(lhs_val, .eq, try sema.splat(resolved_type, scalar_one), resolved_type)) {
- return casted_rhs;
- }
- }
- }
- const air_tag: Air.Inst.Tag = if (block.float_mode == .optimized) .mul_optimized else .mul;
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- if (is_int) {
- return sema.failWithUseOfUndef(block, rhs_src);
- } else {
- return pt.undefRef(resolved_type);
- }
- }
- if (rhs_val.isNan(zcu)) {
- return Air.internedToRef(rhs_val.toIntern());
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) rz: {
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isInf(zcu)) {
- return Air.internedToRef((try pt.floatValue(resolved_type, std.math.nan(f128))).toIntern());
- }
- } else if (resolved_type.isAnyFloat()) {
- break :rz;
- }
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- if (try sema.compareAll(rhs_val, .eq, try sema.splat(resolved_type, scalar_one), resolved_type)) {
- return casted_lhs;
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- if (is_int) {
- return sema.failWithUseOfUndef(block, lhs_src);
- } else {
- return pt.undefRef(resolved_type);
- }
- }
- if (is_int) {
- var overflow_idx: ?usize = null;
- const product = try lhs_val.intMul(rhs_val, resolved_type, &overflow_idx, sema.arena, pt);
- if (overflow_idx) |vec_idx| {
- return sema.failWithIntegerOverflow(block, src, resolved_type, product, vec_idx);
- }
- return Air.internedToRef(product.toIntern());
- } else {
- return Air.internedToRef((try lhs_val.floatMul(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- }
- } else break :rs .{ lhs_src, air_tag, .mul_safe };
- } else break :rs .{ rhs_src, air_tag, .mul_safe };
- },
- .mulwrap => {
- // Integers only; floats are handled above.
- // If either of the operands are zero, result is zero.
- // If either of the operands are one, result is the other operand.
- // If either of the operands are undefined, result is undefined.
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(scalar_type, 0.0),
- .comptime_int, .int => try pt.intValue(scalar_type, 0),
- else => unreachable,
- };
- const scalar_one = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(scalar_type, 1.0),
- .comptime_int, .int => try pt.intValue(scalar_type, 1),
- else => unreachable,
- };
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu)) {
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- if (try sema.compareAll(lhs_val, .eq, try sema.splat(resolved_type, scalar_one), resolved_type)) {
- return casted_rhs;
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- if (try sema.compareAll(rhs_val, .eq, try sema.splat(resolved_type, scalar_one), resolved_type)) {
- return casted_lhs;
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- return Air.internedToRef((try lhs_val.numberMulWrap(rhs_val, resolved_type, sema.arena, pt)).toIntern());
- } else break :rs .{ lhs_src, .mul_wrap, .mul_wrap };
- } else break :rs .{ rhs_src, .mul_wrap, .mul_wrap };
- },
- .mul_sat => {
- // Integers only; floats are checked above.
- // If either of the operands are zero, result is zero.
- // If either of the operands are one, result is the other operand.
- // If either of the operands are undefined, result is undefined.
- const scalar_zero = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(scalar_type, 0.0),
- .comptime_int, .int => try pt.intValue(scalar_type, 0),
- else => unreachable,
- };
- const scalar_one = switch (scalar_tag) {
- .comptime_float, .float => try pt.floatValue(scalar_type, 1.0),
- .comptime_int, .int => try pt.intValue(scalar_type, 1),
- else => unreachable,
- };
- if (maybe_lhs_val) |lhs_val| {
- if (!lhs_val.isUndef(zcu)) {
- if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- if (try sema.compareAll(lhs_val, .eq, try sema.splat(resolved_type, scalar_one), resolved_type)) {
- return casted_rhs;
- }
- }
- }
- if (maybe_rhs_val) |rhs_val| {
- if (rhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
- if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
- const zero_val = try sema.splat(resolved_type, scalar_zero);
- return Air.internedToRef(zero_val.toIntern());
- }
- if (try sema.compareAll(rhs_val, .eq, try sema.splat(resolved_type, scalar_one), resolved_type)) {
- return casted_lhs;
- }
- if (maybe_lhs_val) |lhs_val| {
- if (lhs_val.isUndef(zcu)) {
- return pt.undefRef(resolved_type);
- }
+ const is_int = switch (scalar_tag) {
+ .int, .comptime_int => true,
+ .float, .comptime_float => false,
+ else => unreachable,
+ };
- const val = if (scalar_tag == .comptime_int)
- try lhs_val.intMul(rhs_val, resolved_type, undefined, sema.arena, pt)
- else
- try lhs_val.intMulSat(rhs_val, resolved_type, sema.arena, pt);
+ const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
+ const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
- return Air.internedToRef(val.toIntern());
- } else break :rs .{ lhs_src, .mul_sat, .mul_sat };
- } else break :rs .{ rhs_src, .mul_sat, .mul_sat };
- },
- else => unreachable,
+ if (maybe_lhs_val) |lhs_val| {
+ if (maybe_rhs_val) |rhs_val| {
+ const result_val = switch (zir_tag) {
+ .add, .add_unsafe => try arith.add(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src),
+ .addwrap => try arith.addWrap(sema, resolved_type, lhs_val, rhs_val),
+ .add_sat => try arith.addSat(sema, resolved_type, lhs_val, rhs_val),
+ .sub => try arith.sub(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src),
+ .subwrap => try arith.subWrap(sema, resolved_type, lhs_val, rhs_val),
+ .sub_sat => try arith.subSat(sema, resolved_type, lhs_val, rhs_val),
+ .mul => try arith.mul(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src),
+ .mulwrap => try arith.mulWrap(sema, resolved_type, lhs_val, rhs_val),
+ .mul_sat => try arith.mulSat(sema, resolved_type, lhs_val, rhs_val),
+ else => unreachable,
+ };
+ return Air.internedToRef(result_val.toIntern());
}
+ }
+
+ const air_tag: Air.Inst.Tag, const air_tag_safe: Air.Inst.Tag, const allow_undef: bool = switch (zir_tag) {
+ .add, .add_unsafe => .{ if (block.float_mode == .optimized) .add_optimized else .add, .add_safe, !is_int },
+ .addwrap => .{ .add_wrap, .add_wrap, true },
+ .add_sat => .{ .add_sat, .add_sat, true },
+ .sub => .{ if (block.float_mode == .optimized) .sub_optimized else .sub, .sub_safe, !is_int },
+ .subwrap => .{ .sub_wrap, .sub_wrap, true },
+ .sub_sat => .{ .sub_sat, .sub_sat, true },
+ .mul => .{ if (block.float_mode == .optimized) .mul_optimized else .mul, .mul_safe, !is_int },
+ .mulwrap => .{ .mul_wrap, .mul_wrap, true },
+ .mul_sat => .{ .mul_sat, .mul_sat, true },
+ else => unreachable,
};
- try sema.requireRuntimeBlock(block, src, runtime_src);
+ if (allow_undef) {
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.isUndefDeep(zcu)) return pt.undefRef(resolved_type);
+ }
+ } else {
+ if (maybe_lhs_val) |lhs_val| {
+ if (lhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, lhs_src);
+ }
+ if (maybe_rhs_val) |rhs_val| {
+ if (rhs_val.anyScalarIsUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src);
+ }
+ }
if (block.wantSafety() and want_safety and scalar_tag == .int) {
if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
@@ -24124,7 +23464,7 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
}
}
- if (try sema.resolveValueIntable(operand)) |val| {
+ if (try sema.resolveValueResolveLazy(operand)) |val| {
if (val.isUndef(zcu)) return pt.undefRef(dest_ty);
if (!dest_is_vector) {
return Air.internedToRef((try pt.getCoerced(
@@ -25087,8 +24427,8 @@ fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
.Xor => accum = try accum.bitwiseXor(elem_val, scalar_ty, sema.arena, pt),
.Min => accum = accum.numberMin(elem_val, zcu),
.Max => accum = accum.numberMax(elem_val, zcu),
- .Add => accum = try sema.numberAddWrapScalar(accum, elem_val, scalar_ty),
- .Mul => accum = try accum.numberMulWrap(elem_val, scalar_ty, sema.arena, pt),
+ .Add => accum = try arith.addMaybeWrap(sema, scalar_ty, accum, elem_val),
+ .Mul => accum = try arith.mulMaybeWrap(sema, scalar_ty, accum, elem_val),
}
}
return Air.internedToRef(accum.toIntern());
@@ -25485,14 +24825,14 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
const new_val = switch (op) {
// zig fmt: off
.Xchg => operand_val,
- .Add => try sema.numberAddWrapScalar(stored_val, operand_val, elem_ty),
- .Sub => try sema.numberSubWrapScalar(stored_val, operand_val, elem_ty),
- .And => try stored_val.bitwiseAnd (operand_val, elem_ty, sema.arena, pt ),
- .Nand => try stored_val.bitwiseNand (operand_val, elem_ty, sema.arena, pt ),
- .Or => try stored_val.bitwiseOr (operand_val, elem_ty, sema.arena, pt ),
- .Xor => try stored_val.bitwiseXor (operand_val, elem_ty, sema.arena, pt ),
- .Max => stored_val.numberMax (operand_val, zcu),
- .Min => stored_val.numberMin (operand_val, zcu),
+ .Add => try arith.addMaybeWrap(sema, elem_ty, stored_val, operand_val),
+ .Sub => try arith.subMaybeWrap(sema, elem_ty, stored_val, operand_val),
+ .And => try stored_val.bitwiseAnd (operand_val, elem_ty, sema.arena, pt ),
+ .Nand => try stored_val.bitwiseNand (operand_val, elem_ty, sema.arena, pt ),
+ .Or => try stored_val.bitwiseOr (operand_val, elem_ty, sema.arena, pt ),
+ .Xor => try stored_val.bitwiseXor (operand_val, elem_ty, sema.arena, pt ),
+ .Max => stored_val.numberMax (operand_val, zcu),
+ .Min => stored_val.numberMin (operand_val, zcu),
// zig fmt: on
};
try sema.storePtrVal(block, src, ptr_val, new_val, elem_ty);
@@ -31437,16 +30777,12 @@ fn storePtr2(
};
const maybe_operand_val = try sema.resolveValue(operand);
- const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: {
- const operand_val = maybe_operand_val orelse {
- try sema.checkPtrIsNotComptimeMutable(block, ptr_val, ptr_src, operand_src);
- break :rs operand_src;
- };
- if (sema.isComptimeMutablePtr(ptr_val)) {
- try sema.storePtrVal(block, src, ptr_val, operand_val, elem_ty);
- return;
- } else break :rs ptr_src;
- } else ptr_src;
+ const runtime_src = rs: {
+ const ptr_val = try sema.resolveDefinedValue(block, ptr_src, ptr) orelse break :rs ptr_src;
+ if (!sema.isComptimeMutablePtr(ptr_val)) break :rs ptr_src;
+ const operand_val = maybe_operand_val orelse return sema.fail(block, ptr_src, "cannot store runtime value in compile time variable", .{});
+ return sema.storePtrVal(block, src, ptr_val, operand_val, elem_ty);
+ };
// We're performing the store at runtime; as such, we need to make sure the pointee type
// is not comptime-only. We can hit this case with a `@ptrFromInt` pointer.
@@ -36719,13 +36055,19 @@ fn unionFields(
break :blk val;
} else blk: {
- const val = if (last_tag_val) |val|
- try sema.intAdd(val, Value.one_comptime_int, int_tag_ty, undefined)
- else
- try pt.intValue(int_tag_ty, 0);
- last_tag_val = val;
-
- break :blk val;
+ if (last_tag_val) |last_tag| {
+ const result = try arith.incrementDefinedInt(sema, int_tag_ty, last_tag);
+ if (result.overflow) return sema.fail(
+ &block_scope,
+ value_src,
+ "enumeration value '{}' too large for type '{}'",
+ .{ result.val.fmtValueSema(pt, sema), int_tag_ty.fmt(pt) },
+ );
+ last_tag_val = result.val;
+ } else {
+ last_tag_val = try pt.intValue(int_tag_ty, 0);
+ }
+ break :blk last_tag_val.?;
};
const gop = enum_field_vals.getOrPutAssumeCapacity(enum_tag_val.toIntern());
if (gop.found_existing) {
@@ -37532,260 +36874,6 @@ fn structFieldIndex(
return sema.failWithBadStructFieldAccess(block, struct_ty, struct_type, field_src, field_name);
}
-/// If the value overflowed the type, returns a comptime_int (or vector thereof) instead, setting
-/// overflow_idx to the vector index the overflow was at (or 0 for a scalar).
-fn intAdd(sema: *Sema, lhs: Value, rhs: Value, ty: Type, overflow_idx: *?usize) !Value {
- const pt = sema.pt;
- var overflow: usize = undefined;
- return sema.intAddInner(lhs, rhs, ty, &overflow) catch |err| switch (err) {
- error.Overflow => {
- const is_vec = ty.isVector(pt.zcu);
- overflow_idx.* = if (is_vec) overflow else 0;
- const safe_ty = if (is_vec) try pt.vectorType(.{
- .len = ty.vectorLen(pt.zcu),
- .child = .comptime_int_type,
- }) else Type.comptime_int;
- return sema.intAddInner(lhs, rhs, safe_ty, undefined) catch |err1| switch (err1) {
- error.Overflow => unreachable,
- else => |e| return e,
- };
- },
- else => |e| return e,
- };
-}
-
-fn intAddInner(sema: *Sema, lhs: Value, rhs: Value, ty: Type, overflow_idx: *usize) !Value {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (ty.zigTypeTag(zcu) == .vector) {
- const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(zcu));
- const scalar_ty = ty.scalarType(zcu);
- for (result_data, 0..) |*scalar, i| {
- const lhs_elem = try lhs.elemValue(pt, i);
- const rhs_elem = try rhs.elemValue(pt, i);
- const val = sema.intAddScalar(lhs_elem, rhs_elem, scalar_ty) catch |err| switch (err) {
- error.Overflow => {
- overflow_idx.* = i;
- return error.Overflow;
- },
- else => |e| return e,
- };
- scalar.* = val.toIntern();
- }
- return Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = ty.toIntern(),
- .storage = .{ .elems = result_data },
- } }));
- }
- return sema.intAddScalar(lhs, rhs, ty);
-}
-
-fn intAddScalar(sema: *Sema, lhs: Value, rhs: Value, scalar_ty: Type) !Value {
- const pt = sema.pt;
- if (scalar_ty.toIntern() != .comptime_int_type) {
- const res = try sema.intAddWithOverflowScalar(lhs, rhs, scalar_ty);
- if (res.overflow_bit.compareAllWithZero(.neq, pt.zcu)) return error.Overflow;
- return res.wrapped_result;
- }
- // TODO is this a performance issue? maybe we should try the operation without
- // resorting to BigInt first.
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = try lhs.toBigIntSema(&lhs_space, pt);
- const rhs_bigint = try rhs.toBigIntSema(&rhs_space, pt);
- const limbs = try sema.arena.alloc(
- std.math.big.Limb,
- @max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
- );
- var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined };
- result_bigint.add(lhs_bigint, rhs_bigint);
- return pt.intValue_big(scalar_ty, result_bigint.toConst());
-}
-
-/// Supports both floats and ints; handles undefined.
-fn numberAddWrapScalar(
- sema: *Sema,
- lhs: Value,
- rhs: Value,
- ty: Type,
-) !Value {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (lhs.isUndef(zcu) or rhs.isUndef(zcu)) return pt.undefValue(ty);
-
- if (ty.zigTypeTag(zcu) == .comptime_int) {
- return sema.intAdd(lhs, rhs, ty, undefined);
- }
-
- if (ty.isAnyFloat()) {
- return Value.floatAdd(lhs, rhs, ty, sema.arena, pt);
- }
-
- const overflow_result = try sema.intAddWithOverflow(lhs, rhs, ty);
- return overflow_result.wrapped_result;
-}
-
-/// If the value overflowed the type, returns a comptime_int (or vector thereof) instead, setting
-/// overflow_idx to the vector index the overflow was at (or 0 for a scalar).
-fn intSub(sema: *Sema, lhs: Value, rhs: Value, ty: Type, overflow_idx: *?usize) !Value {
- const pt = sema.pt;
- var overflow: usize = undefined;
- return sema.intSubInner(lhs, rhs, ty, &overflow) catch |err| switch (err) {
- error.Overflow => {
- const is_vec = ty.isVector(pt.zcu);
- overflow_idx.* = if (is_vec) overflow else 0;
- const safe_ty = if (is_vec) try pt.vectorType(.{
- .len = ty.vectorLen(pt.zcu),
- .child = .comptime_int_type,
- }) else Type.comptime_int;
- return sema.intSubInner(lhs, rhs, safe_ty, undefined) catch |err1| switch (err1) {
- error.Overflow => unreachable,
- else => |e| return e,
- };
- },
- else => |e| return e,
- };
-}
-
-fn intSubInner(sema: *Sema, lhs: Value, rhs: Value, ty: Type, overflow_idx: *usize) !Value {
- const pt = sema.pt;
- if (ty.zigTypeTag(pt.zcu) == .vector) {
- const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(pt.zcu));
- const scalar_ty = ty.scalarType(pt.zcu);
- for (result_data, 0..) |*scalar, i| {
- const lhs_elem = try lhs.elemValue(pt, i);
- const rhs_elem = try rhs.elemValue(pt, i);
- const val = sema.intSubScalar(lhs_elem, rhs_elem, scalar_ty) catch |err| switch (err) {
- error.Overflow => {
- overflow_idx.* = i;
- return error.Overflow;
- },
- else => |e| return e,
- };
- scalar.* = val.toIntern();
- }
- return Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = ty.toIntern(),
- .storage = .{ .elems = result_data },
- } }));
- }
- return sema.intSubScalar(lhs, rhs, ty);
-}
-
-fn intSubScalar(sema: *Sema, lhs: Value, rhs: Value, scalar_ty: Type) !Value {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (scalar_ty.toIntern() != .comptime_int_type) {
- const res = try sema.intSubWithOverflowScalar(lhs, rhs, scalar_ty);
- if (res.overflow_bit.compareAllWithZero(.neq, zcu)) return error.Overflow;
- return res.wrapped_result;
- }
- // TODO is this a performance issue? maybe we should try the operation without
- // resorting to BigInt first.
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = try lhs.toBigIntSema(&lhs_space, pt);
- const rhs_bigint = try rhs.toBigIntSema(&rhs_space, pt);
- const limbs = try sema.arena.alloc(
- std.math.big.Limb,
- @max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
- );
- var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined };
- result_bigint.sub(lhs_bigint, rhs_bigint);
- return pt.intValue_big(scalar_ty, result_bigint.toConst());
-}
-
-/// Supports both floats and ints; handles undefined.
-fn numberSubWrapScalar(
- sema: *Sema,
- lhs: Value,
- rhs: Value,
- ty: Type,
-) !Value {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (lhs.isUndef(zcu) or rhs.isUndef(zcu)) return pt.undefValue(ty);
-
- if (ty.zigTypeTag(zcu) == .comptime_int) {
- return sema.intSub(lhs, rhs, ty, undefined);
- }
-
- if (ty.isAnyFloat()) {
- return Value.floatSub(lhs, rhs, ty, sema.arena, pt);
- }
-
- const overflow_result = try sema.intSubWithOverflow(lhs, rhs, ty);
- return overflow_result.wrapped_result;
-}
-
-fn intSubWithOverflow(
- sema: *Sema,
- lhs: Value,
- rhs: Value,
- ty: Type,
-) !Value.OverflowArithmeticResult {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (ty.zigTypeTag(zcu) == .vector) {
- const vec_len = ty.vectorLen(zcu);
- const overflowed_data = try sema.arena.alloc(InternPool.Index, vec_len);
- const result_data = try sema.arena.alloc(InternPool.Index, vec_len);
- const scalar_ty = ty.scalarType(zcu);
- for (overflowed_data, result_data, 0..) |*of, *scalar, i| {
- const lhs_elem = try lhs.elemValue(pt, i);
- const rhs_elem = try rhs.elemValue(pt, i);
- const of_math_result = try sema.intSubWithOverflowScalar(lhs_elem, rhs_elem, scalar_ty);
- of.* = of_math_result.overflow_bit.toIntern();
- scalar.* = of_math_result.wrapped_result.toIntern();
- }
- return Value.OverflowArithmeticResult{
- .overflow_bit = Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = (try pt.vectorType(.{ .len = vec_len, .child = .u1_type })).toIntern(),
- .storage = .{ .elems = overflowed_data },
- } })),
- .wrapped_result = Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = ty.toIntern(),
- .storage = .{ .elems = result_data },
- } })),
- };
- }
- return sema.intSubWithOverflowScalar(lhs, rhs, ty);
-}
-
-fn intSubWithOverflowScalar(
- sema: *Sema,
- lhs: Value,
- rhs: Value,
- ty: Type,
-) !Value.OverflowArithmeticResult {
- const pt = sema.pt;
- const zcu = pt.zcu;
- const info = ty.intInfo(zcu);
-
- if (lhs.isUndef(zcu) or rhs.isUndef(zcu)) {
- return .{
- .overflow_bit = try pt.undefValue(Type.u1),
- .wrapped_result = try pt.undefValue(ty),
- };
- }
-
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = try lhs.toBigIntSema(&lhs_space, pt);
- const rhs_bigint = try rhs.toBigIntSema(&rhs_space, pt);
- const limbs = try sema.arena.alloc(
- std.math.big.Limb,
- std.math.big.int.calcTwosCompLimbCount(info.bits),
- );
- var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined };
- const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
- const wrapped_result = try pt.intValue_big(ty, result_bigint.toConst());
- return Value.OverflowArithmeticResult{
- .overflow_bit = try pt.intValue(Type.u1, @intFromBool(overflowed)),
- .wrapped_result = wrapped_result,
- };
-}
-
const IntFromFloatMode = enum { exact, truncate };
fn intFromFloat(
@@ -37982,74 +37070,6 @@ fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool {
return enum_type.tagValueIndex(&zcu.intern_pool, int_coerced.toIntern()) != null;
}
-fn intAddWithOverflow(
- sema: *Sema,
- lhs: Value,
- rhs: Value,
- ty: Type,
-) !Value.OverflowArithmeticResult {
- const pt = sema.pt;
- const zcu = pt.zcu;
- if (ty.zigTypeTag(zcu) == .vector) {
- const vec_len = ty.vectorLen(zcu);
- const overflowed_data = try sema.arena.alloc(InternPool.Index, vec_len);
- const result_data = try sema.arena.alloc(InternPool.Index, vec_len);
- const scalar_ty = ty.scalarType(zcu);
- for (overflowed_data, result_data, 0..) |*of, *scalar, i| {
- const lhs_elem = try lhs.elemValue(pt, i);
- const rhs_elem = try rhs.elemValue(pt, i);
- const of_math_result = try sema.intAddWithOverflowScalar(lhs_elem, rhs_elem, scalar_ty);
- of.* = of_math_result.overflow_bit.toIntern();
- scalar.* = of_math_result.wrapped_result.toIntern();
- }
- return Value.OverflowArithmeticResult{
- .overflow_bit = Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = (try pt.vectorType(.{ .len = vec_len, .child = .u1_type })).toIntern(),
- .storage = .{ .elems = overflowed_data },
- } })),
- .wrapped_result = Value.fromInterned(try pt.intern(.{ .aggregate = .{
- .ty = ty.toIntern(),
- .storage = .{ .elems = result_data },
- } })),
- };
- }
- return sema.intAddWithOverflowScalar(lhs, rhs, ty);
-}
-
-fn intAddWithOverflowScalar(
- sema: *Sema,
- lhs: Value,
- rhs: Value,
- ty: Type,
-) !Value.OverflowArithmeticResult {
- const pt = sema.pt;
- const zcu = pt.zcu;
- const info = ty.intInfo(zcu);
-
- if (lhs.isUndef(zcu) or rhs.isUndef(zcu)) {
- return .{
- .overflow_bit = try pt.undefValue(Type.u1),
- .wrapped_result = try pt.undefValue(ty),
- };
- }
-
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = try lhs.toBigIntSema(&lhs_space, pt);
- const rhs_bigint = try rhs.toBigIntSema(&rhs_space, pt);
- const limbs = try sema.arena.alloc(
- std.math.big.Limb,
- std.math.big.int.calcTwosCompLimbCount(info.bits),
- );
- var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined };
- const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
- const result = try pt.intValue_big(ty, result_bigint.toConst());
- return Value.OverflowArithmeticResult{
- .overflow_bit = try pt.intValue(Type.u1, @intFromBool(overflowed)),
- .wrapped_result = result,
- };
-}
-
/// Asserts the values are comparable. Both operands have type `ty`.
/// For vectors, returns true if the comparison is true for ALL elements.
///
@@ -38736,12 +37756,13 @@ fn resolveDeclaredEnumInner(
}
break :overflow false;
} else if (any_values) overflow: {
- var overflow: ?usize = null;
- last_tag_val = if (last_tag_val) |val|
- try sema.intAdd(val, try pt.intValue(int_tag_ty, 1), int_tag_ty, &overflow)
- else
- try pt.intValue(int_tag_ty, 0);
- if (overflow != null) break :overflow true;
+ if (last_tag_val) |last_tag| {
+ const result = try arith.incrementDefinedInt(sema, int_tag_ty, last_tag);
+ last_tag_val = result.val;
+ if (result.overflow) break :overflow true;
+ } else {
+ last_tag_val = try pt.intValue(int_tag_ty, 0);
+ }
if (wip_ty.nextField(ip, field_name, last_tag_val.?.toIntern())) |conflict| {
assert(conflict.kind == .value); // AstGen validated names are unique
const other_field_src: LazySrcLoc = .{