x86_64: rewrite scalar <<|

Closes #23035
This commit is contained in:
Jacob Young 2025-05-17 02:08:33 -04:00
parent d3dfe61eaa
commit 58d2bd601e
16 changed files with 3741 additions and 186 deletions

View File

@ -774,18 +774,15 @@ pub fn Log2IntCeil(comptime T: type) type {
/// Returns the smallest integer type that can hold both from and to.
pub fn IntFittingRange(comptime from: comptime_int, comptime to: comptime_int) type {
assert(from <= to);
if (from == 0 and to == 0) {
return u0;
}
const signedness: std.builtin.Signedness = if (from < 0) .signed else .unsigned;
const largest_positive_integer = @max(if (from < 0) (-from) - 1 else from, to); // two's complement
const base = log2(largest_positive_integer);
const upper = (1 << base) - 1;
var magnitude_bits = if (upper >= largest_positive_integer) base else base + 1;
if (signedness == .signed) {
magnitude_bits += 1;
}
return std.meta.Int(signedness, magnitude_bits);
return @Type(.{ .int = .{
.signedness = signedness,
.bits = @as(u16, @intFromBool(signedness == .signed)) +
switch (if (from < 0) @max(@abs(from) - 1, to) else to) {
0 => 0,
else => |pos_max| 1 + log2(pos_max),
},
} });
}
test IntFittingRange {
@ -1267,6 +1264,19 @@ pub fn log2_int(comptime T: type, x: T) Log2Int(T) {
return @as(Log2Int(T), @intCast(@typeInfo(T).int.bits - 1 - @clz(x)));
}
test log2_int {
try testing.expect(log2_int(u32, 1) == 0);
try testing.expect(log2_int(u32, 2) == 1);
try testing.expect(log2_int(u32, 3) == 1);
try testing.expect(log2_int(u32, 4) == 2);
try testing.expect(log2_int(u32, 5) == 2);
try testing.expect(log2_int(u32, 6) == 2);
try testing.expect(log2_int(u32, 7) == 2);
try testing.expect(log2_int(u32, 8) == 3);
try testing.expect(log2_int(u32, 9) == 3);
try testing.expect(log2_int(u32, 10) == 3);
}
/// Return the log base 2 of integer value x, rounding up to the
/// nearest integer.
pub fn log2_int_ceil(comptime T: type, x: T) Log2IntCeil(T) {

View File

@ -415,12 +415,12 @@ pub const Mutable = struct {
// in the case that scalar happens to be small in magnitude within its type, but it
// is well worth being able to use the stack and not needing an allocator passed in.
// Note that Mutable.init still sets len to calcLimbLen(scalar) in any case.
const limb_len = comptime switch (@typeInfo(@TypeOf(scalar))) {
const limbs_len = comptime switch (@typeInfo(@TypeOf(scalar))) {
.comptime_int => calcLimbLen(scalar),
.int => |info| calcTwosCompLimbCount(info.bits),
else => @compileError("expected scalar to be an int"),
};
var limbs: [limb_len]Limb = undefined;
var limbs: [limbs_len]Limb = undefined;
const operand = init(&limbs, scalar).toConst();
return add(r, a, operand);
}
@ -2454,12 +2454,12 @@ pub const Const = struct {
// in the case that scalar happens to be small in magnitude within its type, but it
// is well worth being able to use the stack and not needing an allocator passed in.
// Note that Mutable.init still sets len to calcLimbLen(scalar) in any case.
const limb_len = comptime switch (@typeInfo(@TypeOf(scalar))) {
const limbs_len = comptime switch (@typeInfo(@TypeOf(scalar))) {
.comptime_int => calcLimbLen(scalar),
.int => |info| calcTwosCompLimbCount(info.bits),
else => @compileError("expected scalar to be an int"),
};
var limbs: [limb_len]Limb = undefined;
var limbs: [limbs_len]Limb = undefined;
const rhs = Mutable.init(&limbs, scalar);
return order(lhs, rhs.toConst());
}

View File

@ -2295,8 +2295,6 @@ test "sat shift-left signed simple positive" {
}
test "sat shift-left signed multi positive" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
var x: SignedDoubleLimb = 1;
_ = &x;
@ -2310,8 +2308,6 @@ test "sat shift-left signed multi positive" {
}
test "sat shift-left signed multi negative" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
var x: SignedDoubleLimb = -1;
_ = &x;

View File

@ -12,12 +12,10 @@ const expect = std.testing.expect;
/// - log2(nan) = nan
pub fn log2(x: anytype) @TypeOf(x) {
const T = @TypeOf(x);
switch (@typeInfo(T)) {
.comptime_float => {
return @as(comptime_float, @log2(x));
},
.float => return @log2(x),
return switch (@typeInfo(T)) {
.comptime_float, .float => @log2(x),
.comptime_int => comptime {
std.debug.assert(x > 0);
var x_shifted = x;
// First, calculate floorPowerOfTwo(x)
var shift_amt = 1;
@ -34,12 +32,15 @@ pub fn log2(x: anytype) @TypeOf(x) {
}
return result;
},
.int => |IntType| switch (IntType.signedness) {
.signed => @compileError("log2 not implemented for signed integers"),
.unsigned => return math.log2_int(T, x),
},
.int => |int_info| math.log2_int(switch (int_info.signedness) {
.signed => @Type(.{ .int = .{
.signedness = .unsigned,
.bits = int_info.bits -| 1,
} }),
.unsigned => T,
}, @intCast(x)),
else => @compileError("log2 not implemented for " ++ @typeName(T)),
}
};
}
test log2 {

View File

@ -1115,14 +1115,15 @@ static inline bool zig_mulo_i16(int16_t *res, int16_t lhs, int16_t rhs, uint8_t
\
static inline uint##w##_t zig_shls_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \
uint##w##_t res; \
if (rhs >= bits) return lhs != UINT##w##_C(0) ? zig_maxInt_u(w, bits) : lhs; \
return zig_shlo_u##w(&res, lhs, (uint8_t)rhs, bits) ? zig_maxInt_u(w, bits) : res; \
if (rhs < bits && !zig_shlo_u##w(&res, lhs, rhs, bits)) return res; \
return lhs == INT##w##_C(0) ? INT##w##_C(0) : zig_maxInt_u(w, bits); \
} \
\
static inline int##w##_t zig_shls_i##w(int##w##_t lhs, int##w##_t rhs, uint8_t bits) { \
static inline int##w##_t zig_shls_i##w(int##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \
int##w##_t res; \
if ((uint##w##_t)rhs < (uint##w##_t)bits && !zig_shlo_i##w(&res, lhs, (uint8_t)rhs, bits)) return res; \
return lhs < INT##w##_C(0) ? zig_minInt_i(w, bits) : zig_maxInt_i(w, bits); \
if (rhs < bits && !zig_shlo_i##w(&res, lhs, rhs, bits)) return res; \
return lhs == INT##w##_C(0) ? INT##w##_C(0) : \
lhs < INT##w##_C(0) ? zig_minInt_i(w, bits) : zig_maxInt_i(w, bits); \
} \
\
static inline uint##w##_t zig_adds_u##w(uint##w##_t lhs, uint##w##_t rhs, uint8_t bits) { \
@ -1851,15 +1852,23 @@ static inline bool zig_shlo_i128(zig_i128 *res, zig_i128 lhs, uint8_t rhs, uint8
static inline zig_u128 zig_shls_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) {
zig_u128 res;
if (zig_cmp_u128(rhs, zig_make_u128(0, bits)) >= INT32_C(0))
return zig_cmp_u128(lhs, zig_make_u128(0, 0)) != INT32_C(0) ? zig_maxInt_u(128, bits) : lhs;
return zig_shlo_u128(&res, lhs, (uint8_t)zig_lo_u128(rhs), bits) ? zig_maxInt_u(128, bits) : res;
if (zig_cmp_u128(rhs, zig_make_u128(0, bits)) < INT32_C(0) && !zig_shlo_u128(&res, lhs, (uint8_t)zig_lo_u128(rhs), bits)) return res;
switch (zig_cmp_u128(lhs, zig_make_u128(0, 0))) {
case 0: return zig_make_u128(0, 0);
case 1: return zig_maxInt_u(128, bits);
default: zig_unreachable();
}
}
static inline zig_i128 zig_shls_i128(zig_i128 lhs, zig_i128 rhs, uint8_t bits) {
static inline zig_i128 zig_shls_i128(zig_i128 lhs, zig_u128 rhs, uint8_t bits) {
zig_i128 res;
if (zig_cmp_u128(zig_bitCast_u128(rhs), zig_make_u128(0, bits)) < INT32_C(0) && !zig_shlo_i128(&res, lhs, (uint8_t)zig_lo_i128(rhs), bits)) return res;
return zig_cmp_i128(lhs, zig_make_i128(0, 0)) < INT32_C(0) ? zig_minInt_i(128, bits) : zig_maxInt_i(128, bits);
if (zig_cmp_u128(rhs, zig_make_u128(0, bits)) < INT32_C(0) && !zig_shlo_i128(&res, lhs, (uint8_t)zig_lo_u128(rhs), bits)) return res;
switch (zig_cmp_i128(lhs, zig_make_i128(0, 0))) {
case -1: return zig_minInt_i(128, bits);
case 0: return zig_make_i128(0, 0);
case 1: return zig_maxInt_i(128, bits);
default: zig_unreachable();
}
}
static inline zig_u128 zig_adds_u128(zig_u128 lhs, zig_u128 rhs, uint8_t bits) {

View File

@ -257,7 +257,9 @@ pub const Inst = struct {
/// it shifts out any bits that disagree with the resultant sign bit.
/// Uses the `bin_op` field.
shl_exact,
/// Saturating integer shift left. `<<|`
/// Saturating integer shift left. `<<|`. The result is the same type as the `lhs`.
/// The `rhs` must have the same vector shape as the `lhs`, but with any unsigned
/// integer as the scalar type.
/// Uses the `bin_op` field.
shl_sat,
/// Bitwise XOR. `^`

View File

@ -14215,14 +14215,15 @@ fn zirShl(
const rhs_ty = sema.typeOf(rhs);
const src = block.nodeOffset(inst_data.src_node);
const lhs_src = switch (air_tag) {
.shl, .shl_sat => block.src(.{ .node_offset_bin_lhs = inst_data.src_node }),
.shl_exact => block.builtinCallArgSrc(inst_data.src_node, 0),
else => unreachable,
};
const rhs_src = switch (air_tag) {
.shl, .shl_sat => block.src(.{ .node_offset_bin_rhs = inst_data.src_node }),
.shl_exact => block.builtinCallArgSrc(inst_data.src_node, 1),
const lhs_src, const rhs_src = switch (air_tag) {
.shl, .shl_sat => .{
block.src(.{ .node_offset_bin_lhs = inst_data.src_node }),
block.src(.{ .node_offset_bin_rhs = inst_data.src_node }),
},
.shl_exact => .{
block.builtinCallArgSrc(inst_data.src_node, 0),
block.builtinCallArgSrc(inst_data.src_node, 1),
},
else => unreachable,
};
@ -14231,8 +14232,7 @@ fn zirShl(
const scalar_ty = lhs_ty.scalarType(zcu);
const scalar_rhs_ty = rhs_ty.scalarType(zcu);
// TODO coerce rhs if air_tag is not shl_sat
const rhs_is_comptime_int = try sema.checkIntType(block, rhs_src, scalar_rhs_ty);
_ = try sema.checkIntType(block, rhs_src, scalar_rhs_ty);
const maybe_lhs_val = try sema.resolveValueResolveLazy(lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(rhs);
@ -14245,7 +14245,7 @@ fn zirShl(
if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
return lhs;
}
if (scalar_ty.zigTypeTag(zcu) != .comptime_int and air_tag != .shl_sat) {
if (air_tag != .shl_sat and scalar_ty.zigTypeTag(zcu) != .comptime_int) {
const bit_value = try pt.intValue(Type.comptime_int, scalar_ty.intInfo(zcu).bits);
if (rhs_ty.zigTypeTag(zcu) == .vector) {
var i: usize = 0;
@ -14282,6 +14282,8 @@ fn zirShl(
rhs_val.fmtValueSema(pt, sema),
});
}
} else if (scalar_rhs_ty.isSignedInt(zcu)) {
return sema.fail(block, rhs_src, "shift by signed type '{}'", .{rhs_ty.fmt(pt)});
}
const runtime_src = if (maybe_lhs_val) |lhs_val| rs: {
@ -14309,18 +14311,34 @@ fn zirShl(
return Air.internedToRef(val.toIntern());
} else lhs_src;
const new_rhs = if (air_tag == .shl_sat) rhs: {
// Limit the RHS type for saturating shl to be an integer as small as the LHS.
if (rhs_is_comptime_int or
scalar_rhs_ty.intInfo(zcu).bits > scalar_ty.intInfo(zcu).bits)
{
const max_int = Air.internedToRef((try lhs_ty.maxInt(pt, lhs_ty)).toIntern());
const rhs_limited = try sema.analyzeMinMax(block, rhs_src, .min, &.{ rhs, max_int }, &.{ rhs_src, rhs_src });
break :rhs try sema.intCast(block, src, lhs_ty, rhs_src, rhs_limited, rhs_src, false, false);
} else {
break :rhs rhs;
}
} else rhs;
const rt_rhs = switch (air_tag) {
else => unreachable,
.shl, .shl_exact => rhs,
// The backend can handle a large runtime rhs better than we can, but
// we can limit a large comptime rhs better here. This also has the
// necessary side effect of preventing rhs from being a `comptime_int`.
.shl_sat => if (maybe_rhs_val) |rhs_val| Air.internedToRef(rt_rhs: {
const bit_count = scalar_ty.intInfo(zcu).bits;
const rt_rhs_scalar_ty = try pt.smallestUnsignedInt(bit_count);
if (!rhs_ty.isVector(zcu)) break :rt_rhs (try pt.intValue(
rt_rhs_scalar_ty,
@min(try rhs_val.getUnsignedIntSema(pt) orelse bit_count, bit_count),
)).toIntern();
const rhs_len = rhs_ty.vectorLen(zcu);
const rhs_elems = try sema.arena.alloc(InternPool.Index, rhs_len);
for (rhs_elems, 0..) |*rhs_elem, i| rhs_elem.* = (try pt.intValue(
rt_rhs_scalar_ty,
@min(try (try rhs_val.elemValue(pt, i)).getUnsignedIntSema(pt) orelse bit_count, bit_count),
)).toIntern();
break :rt_rhs try pt.intern(.{ .aggregate = .{
.ty = (try pt.vectorType(.{
.len = rhs_len,
.child = rt_rhs_scalar_ty.toIntern(),
})).toIntern(),
.storage = .{ .elems = rhs_elems },
} });
}) else rhs,
};
try sema.requireRuntimeBlock(block, src, runtime_src);
if (block.wantSafety()) {
@ -14374,7 +14392,7 @@ fn zirShl(
return sema.tupleFieldValByIndex(block, op_ov, 0, op_ov_tuple_ty);
}
}
return block.addBinOp(air_tag, lhs, new_rhs);
return block.addBinOp(air_tag, lhs, rt_rhs);
}
fn zirShr(
@ -36432,10 +36450,7 @@ fn generateUnionTagTypeSimple(
const enum_ty = try ip.getGeneratedTagEnumType(gpa, pt.tid, .{
.name = name,
.owner_union_ty = union_type,
.tag_ty = if (enum_field_names.len == 0)
(try pt.intType(.unsigned, 0)).toIntern()
else
(try pt.smallestUnsignedInt(enum_field_names.len - 1)).toIntern(),
.tag_ty = (try pt.smallestUnsignedInt(enum_field_names.len -| 1)).toIntern(),
.names = enum_field_names,
.values = &.{},
.tag_mode = .auto,

View File

@ -4132,10 +4132,10 @@ pub const empty_tuple: Type = .{ .ip_index = .empty_tuple_type };
pub const generic_poison: Type = .{ .ip_index = .generic_poison_type };
pub fn smallestUnsignedBits(max: u64) u16 {
if (max == 0) return 0;
const base = std.math.log2(max);
const upper = (@as(u64, 1) << @as(u6, @intCast(base))) - 1;
return @as(u16, @intCast(base + @intFromBool(upper < max)));
return switch (max) {
0 => 0,
else => 1 + std.math.log2_int(u64, max),
};
}
/// This is only used for comptime asserts. Bump this number when you make a change

File diff suppressed because it is too large Load Diff

View File

@ -9023,19 +9023,25 @@ pub const FuncGen = struct {
const rhs = try self.resolveInst(bin_op.rhs);
const lhs_ty = self.typeOf(bin_op.lhs);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
const lhs_bits = lhs_scalar_ty.bitSize(zcu);
const casted_rhs = try self.wip.conv(.unsigned, rhs, try o.lowerType(lhs_ty), "");
const lhs_info = lhs_ty.intInfo(zcu);
const llvm_lhs_ty = try o.lowerType(lhs_ty);
const llvm_lhs_scalar_ty = llvm_lhs_ty.scalarType(&o.builder);
const rhs_ty = self.typeOf(bin_op.rhs);
const rhs_info = rhs_ty.intInfo(zcu);
assert(rhs_info.signedness == .unsigned);
const llvm_rhs_ty = try o.lowerType(rhs_ty);
const llvm_rhs_scalar_ty = llvm_rhs_ty.scalarType(&o.builder);
const result = try self.wip.callIntrinsic(
.normal,
.none,
if (lhs_scalar_ty.isSignedInt(zcu)) .@"sshl.sat" else .@"ushl.sat",
switch (lhs_info.signedness) {
.signed => .@"sshl.sat",
.unsigned => .@"ushl.sat",
},
&.{llvm_lhs_ty},
&.{ lhs, casted_rhs },
&.{ lhs, try self.wip.conv(.unsigned, rhs, llvm_lhs_ty, "") },
"",
);
@ -9044,16 +9050,45 @@ pub const FuncGen = struct {
// poison value."
// However Zig semantics says that saturating shift left can never produce
// undefined; instead it saturates.
if (rhs_info.bits <= math.log2_int(u16, lhs_info.bits)) return result;
const bits = try o.builder.splatValue(
llvm_lhs_ty,
try o.builder.intConst(llvm_lhs_scalar_ty, lhs_bits),
llvm_rhs_ty,
try o.builder.intConst(llvm_rhs_scalar_ty, lhs_info.bits),
);
const lhs_max = try o.builder.splatValue(
llvm_lhs_ty,
try o.builder.intConst(llvm_lhs_scalar_ty, -1),
);
const in_range = try self.wip.icmp(.ult, casted_rhs, bits, "");
return self.wip.select(.normal, in_range, result, lhs_max, "");
const in_range = try self.wip.icmp(.ult, rhs, bits, "");
const lhs_sat = lhs_sat: switch (lhs_info.signedness) {
.signed => {
const zero = try o.builder.splatValue(
llvm_lhs_ty,
try o.builder.intConst(llvm_lhs_scalar_ty, 0),
);
const smin = try o.builder.splatValue(
llvm_lhs_ty,
try minIntConst(&o.builder, lhs_ty, llvm_lhs_ty, zcu),
);
const smax = try o.builder.splatValue(
llvm_lhs_ty,
try maxIntConst(&o.builder, lhs_ty, llvm_lhs_ty, zcu),
);
const lhs_lt_zero = try self.wip.icmp(.slt, lhs, zero, "");
const slimit = try self.wip.select(.normal, lhs_lt_zero, smin, smax, "");
const lhs_eq_zero = try self.wip.icmp(.eq, lhs, zero, "");
break :lhs_sat try self.wip.select(.normal, lhs_eq_zero, zero, slimit, "");
},
.unsigned => {
const zero = try o.builder.splatValue(
llvm_lhs_ty,
try o.builder.intConst(llvm_lhs_scalar_ty, 0),
);
const umax = try o.builder.splatValue(
llvm_lhs_ty,
try o.builder.intConst(llvm_lhs_scalar_ty, -1),
);
const lhs_eq_zero = try self.wip.icmp(.eq, lhs, zero, "");
break :lhs_sat try self.wip.select(.normal, lhs_eq_zero, zero, umax, "");
},
};
return self.wip.select(.normal, in_range, result, lhs_sat, "");
}
fn airShr(self: *FuncGen, inst: Air.Inst.Index, is_exact: bool) !Builder.Value {

View File

@ -128,12 +128,12 @@ test "Saturating Shift Left where lhs is of a computed type" {
});
}
pub fn FixedPoint(comptime value_type: type) type {
pub fn FixedPoint(comptime ValueType: type) type {
return struct {
value: value_type,
value: ValueType,
exponent: ShiftType,
const ShiftType: type = getIntShiftType(value_type);
const ShiftType: type = getIntShiftType(ValueType);
pub fn shiftExponent(self: @This(), shift: ShiftType) @This() {
const shiftAbs = @abs(shift);
@ -199,8 +199,7 @@ test "Saturating Shift Left" {
try expectEqual(0xffffffffffffffffffffffffffffffff, S.shlSat(@as(u128, 0x0fffffffffffffff0fffffffffffffff), 5));
try expectEqual(-0x80000000000000000000000000000000, S.shlSat(@as(i128, -0x0fffffffffffffff0fffffffffffffff), 5));
// TODO
// try expectEqual(51146728248377216718956089012931236753385031969422887335676427626502090568823039920051095192592252455482604439493126109519019633529459266458258243583, S.shlSat(@as(i495, 0x2fe6bc5448c55ce18252e2c9d44777505dfe63ff249a8027a6626c7d8dd9893fd5731e51474727be556f757facb586a4e04bbc0148c6c7ad692302f46fbd), 0x31));
try expectEqual(51146728248377216718956089012931236753385031969422887335676427626502090568823039920051095192592252455482604439493126109519019633529459266458258243583, S.shlSat(@as(i495, 0x2fe6bc5448c55ce18252e2c9d44777505dfe63ff249a8027a6626c7d8dd9893fd5731e51474727be556f757facb586a4e04bbc0148c6c7ad692302f46fbd), 0x31));
try expectEqual(-57896044618658097711785492504343953926634992332820282019728792003956564819968, S.shlSat(@as(i256, -0x53d4148cee74ea43477a65b3daa7b8fdadcbf4508e793f4af113b8d8da5a7eb6), 0x91));
try expectEqual(170141183460469231731687303715884105727, S.shlSat(@as(i128, 0x2fe6bc5448c55ce18252e2c9d4477750), 0x31));
try expectEqual(0, S.shlSat(@as(i128, 0), 127));

View File

@ -230,9 +230,10 @@ test "saturating multiplication <= 32 bits" {
try testSatMul(i32, 10, -12, -120);
}
// TODO: remove this test, integrate into general test
test "saturating mul i64, i128, wasm only" {
if (builtin.zig_backend != .stage2_wasm) return error.SkipZigTest;
test "saturating mul i64, i128" {
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
try testSatMul(i64, 0, maxInt(i64), 0);
try testSatMul(i64, 0, minInt(i64), 0);
@ -298,29 +299,34 @@ test "saturating multiplication" {
}
test "saturating shift-left" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
try testSatShl(i8, 1, 2, 4);
try testSatShl(i8, 127, 1, 127);
try testSatShl(i8, -128, 1, -128);
try testSatShl(i8, 1, u8, 2, 4);
try testSatShl(i8, 127, u8, 1, 127);
try testSatShl(i8, -128, u8, 1, -128);
// TODO: remove this check once #9668 is completed
if (!builtin.cpu.arch.isWasm()) {
// skip testing ints > 64 bits on wasm due to miscompilation / wasmtime ci error
try testSatShl(i128, maxInt(i128), 64, maxInt(i128));
try testSatShl(u128, maxInt(u128), 64, maxInt(u128));
try testSatShl(i128, maxInt(i128), u128, 64, maxInt(i128));
try testSatShl(u128, maxInt(u128), u128, 64, maxInt(u128));
}
try testSatShl(u8, 1, 2, 4);
try testSatShl(u8, 255, 1, 255);
try testSatShl(u8, 1, u8, 2, 4);
try testSatShl(u8, 255, u8, 1, 255);
try testSatShl(i8, -3, u4, 8, minInt(i8));
try testSatShl(i8, 0, u4, 8, 0);
try testSatShl(i8, 3, u4, 8, maxInt(i8));
try testSatShl(u8, 0, u4, 8, 0);
try testSatShl(u8, 3, u4, 8, maxInt(u8));
}
fn testSatShl(comptime T: type, lhs: T, rhs: T, expected: T) !void {
fn testSatShl(comptime Lhs: type, lhs: Lhs, comptime Rhs: type, rhs: Rhs, expected: Lhs) !void {
try expect((lhs <<| rhs) == expected);
var x = lhs;
@ -332,19 +338,37 @@ test "saturating shift-left" {
try S.doTheTest();
try comptime S.doTheTest();
try comptime S.testSatShl(comptime_int, 0, 0, 0);
try comptime S.testSatShl(comptime_int, 1, 2, 4);
try comptime S.testSatShl(comptime_int, 13, 150, 18554220005177478453757717602843436772975706112);
try comptime S.testSatShl(comptime_int, -582769, 180, -893090893854873184096635538665358532628308979495815656505344);
try comptime S.testSatShl(comptime_int, 0, comptime_int, 0, 0);
try comptime S.testSatShl(comptime_int, 1, comptime_int, 2, 4);
try comptime S.testSatShl(comptime_int, 13, comptime_int, 150, 18554220005177478453757717602843436772975706112);
try comptime S.testSatShl(comptime_int, -582769, comptime_int, 180, -893090893854873184096635538665358532628308979495815656505344);
}
test "saturating shift-left large rhs" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
{
var lhs: u8 = undefined;
lhs = 1;
const ct_rhs: u1024 = 1 << 1023;
var rt_rhs: u1024 = undefined;
rt_rhs = ct_rhs;
try expect(lhs <<| ct_rhs == maxInt(u8));
try expect(lhs <<| rt_rhs == maxInt(u8));
}
}
test "saturating shl uses the LHS type" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
const lhs_const: u8 = 1;
var lhs_var: u8 = 1;

View File

@ -6,6 +6,7 @@ const DoubleBits = math.DoubleBits;
const fmax = math.fmax;
const fmin = math.fmin;
const Gpr = math.Gpr;
const imax = math.imax;
const inf = math.inf;
const Log2Int = math.Log2Int;
const math = @import("math.zig");
@ -5388,6 +5389,22 @@ test shlExactUnsafe {
try test_shl_exact_unsafe.testInts();
}
inline fn shlSat(comptime Type: type, lhs: Type, rhs: Type) Type {
// workaround https://github.com/ziglang/zig/issues/23034
if (@inComptime()) {
// workaround https://github.com/ziglang/zig/issues/23139
//return lhs <<| @min(@abs(rhs), imax(u64));
return lhs <<| @min(@abs(rhs), @as(u64, imax(u64)));
}
// workaround https://github.com/ziglang/zig/issues/23033
@setRuntimeSafety(false);
return lhs <<| @abs(rhs);
}
test shlSat {
const test_shl_sat = binary(shlSat, .{});
try test_shl_sat.testInts();
}
inline fn bitXor(comptime Type: type, lhs: Type, rhs: Type) @TypeOf(lhs ^ rhs) {
return lhs ^ rhs;
}

View File

@ -1,12 +0,0 @@
export fn a() void {
comptime {
var x = @as(i32, 1);
x <<|= @as(i32, -2);
}
}
// error
// backend=stage2
// target=native
//
// :4:16: error: shift by negative amount '-2'

View File

@ -0,0 +1,36 @@
export fn a() void {
_ = @as(i32, 1) <<| @as(i32, -1);
}
comptime {
var x: i32 = 1;
x <<|= @as(i32, -2);
}
export fn b() void {
_ = @Vector(1, i32){1} <<| @Vector(1, i32){-3};
}
comptime {
var x: @Vector(2, i32) = .{ 1, 2 };
x <<|= @Vector(2, i32){ 0, -4 };
}
export fn c(rhs: i32) void {
_ = @as(i32, 1) <<| rhs;
}
export fn d(rhs: @Vector(3, i32)) void {
_ = @Vector(3, i32){ 1, 2, 3 } <<| rhs;
}
// error
// backend=stage2
// target=native
//
// :2:25: error: shift by negative amount '-1'
// :7:12: error: shift by negative amount '-2'
// :11:47: error: shift by negative amount '-3' at index '0'
// :16:27: error: shift by negative amount '-4' at index '1'
// :20:25: error: shift by signed type 'i32'
// :24:40: error: shift by signed type '@Vector(3, i32)'

View File

@ -1,9 +0,0 @@
export fn a() void {
_ = @as(i32, 1) <<| @as(i32, -2);
}
// error
// backend=stage2
// target=native
//
// :2:25: error: shift by negative amount '-2'