authorgravatar for xtex@astrafall.orgxtex <xtex@astrafall.org> 2025-01-18 21:59:00+08:00
committergravatar for xtex@astrafall.orgxtex <xtex@astrafall.org> 2025-03-02 10:30:32+08:00
log4e32193de33df13a90499bc4b678b4b69716018c
treeb16e0c98c4face146e72d080d4c4659536277361
parent6c3cbb0c87a33f4ae408874f6ceb40e372b65914
signaturelock-open Commit is signed but in an unrecognized format.

x86_64: implement integer saturating left shifting codegen

Simliarly to shl_with_overflow, we first SHL/SAL the integer, then SHR/SAR it back to compare if overflow happens. If overflow happened, set result to the upper limit to make it saturating. Bug: #17645 Co-authored-by: Jacob Young <jacobly0@users.noreply.github.com> Signed-off-by: Bingwu Zhang <xtex@aosc.io>

2 files changed, 137 insertions(+), 6 deletions(-)

src/arch/x86_64/CodeGen.zig+137-5
...@@ -85049,10 +85049,132 @@ fn airShlShrBinOp(self: *CodeGen, inst: Air.Inst.Index) !void {...@@ -85049,10 +85049,132 @@ fn airShlShrBinOp(self: *CodeGen, inst: Air.Inst.Index) !void {
85049}85049}
8505085050
85051fn airShlSat(self: *CodeGen, inst: Air.Inst.Index) !void {85051fn airShlSat(self: *CodeGen, inst: Air.Inst.Index) !void {
85052 const zcu = self.pt.zcu;
85052 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;85053 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
85053 _ = bin_op;85054 const lhs_ty = self.typeOf(bin_op.lhs);
85054 return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch});85055 const rhs_ty = self.typeOf(bin_op.rhs);
85055 //return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });85056
85057 const result: MCValue = result: {
85058 switch (lhs_ty.zigTypeTag(zcu)) {
85059 .int => {
85060 const lhs_bits = lhs_ty.bitSize(zcu);
85061 const rhs_bits = rhs_ty.bitSize(zcu);
85062 if (!(lhs_bits <= 32 and rhs_bits <= 5) and !(lhs_bits > 32 and lhs_bits <= 64 and rhs_bits <= 6) and !(rhs_bits <= std.math.log2(lhs_bits))) {
85063 return self.fail("TODO implement shl_sat for {} with lhs bits {}, rhs bits {}", .{ self.target.cpu.arch, lhs_bits, rhs_bits });
85064 }
85065
85066 // clobberred by genShiftBinOp
85067 try self.spillRegisters(&.{.rcx});
85068
85069 const lhs_mcv = try self.resolveInst(bin_op.lhs);
85070 var lhs_temp1 = try self.tempInit(lhs_ty, lhs_mcv);
85071 const rhs_mcv = try self.resolveInst(bin_op.rhs);
85072
85073 const lhs_lock = switch (lhs_mcv) {
85074 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
85075 else => null,
85076 };
85077 defer if (lhs_lock) |lock| self.register_manager.unlockReg(lock);
85078
85079 // shift left
85080 const dst_mcv = try self.genShiftBinOp(.shl, null, lhs_mcv, rhs_mcv, lhs_ty, rhs_ty);
85081 switch (dst_mcv) {
85082 .register => |dst_reg| try self.truncateRegister(lhs_ty, dst_reg),
85083 .register_pair => |dst_regs| try self.truncateRegister(lhs_ty, dst_regs[1]),
85084 .load_frame => |frame_addr| {
85085 const tmp_reg =
85086 try self.register_manager.allocReg(null, abi.RegisterClass.gp);
85087 const tmp_lock = self.register_manager.lockRegAssumeUnused(tmp_reg);
85088 defer self.register_manager.unlockReg(tmp_lock);
85089
85090 const lhs_bits_u31: u31 = @intCast(lhs_bits);
85091 const tmp_ty: Type = if (lhs_bits_u31 > 64) .usize else lhs_ty;
85092 const off = frame_addr.off + (lhs_bits_u31 - 1) / 64 * 8;
85093 try self.genSetReg(
85094 tmp_reg,
85095 tmp_ty,
85096 .{ .load_frame = .{ .index = frame_addr.index, .off = off } },
85097 .{},
85098 );
85099 try self.truncateRegister(lhs_ty, tmp_reg);
85100 try self.genSetMem(
85101 .{ .frame = frame_addr.index },
85102 off,
85103 tmp_ty,
85104 .{ .register = tmp_reg },
85105 .{},
85106 );
85107 },
85108 else => {},
85109 }
85110 const dst_lock = switch (dst_mcv) {
85111 .register => |reg| self.register_manager.lockRegAssumeUnused(reg),
85112 else => null,
85113 };
85114 defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);
85115
85116 // shift right
85117 const tmp_mcv = try self.genShiftBinOp(.shr, null, dst_mcv, rhs_mcv, lhs_ty, rhs_ty);
85118 var tmp_temp = try self.tempInit(lhs_ty, tmp_mcv);
85119
85120 // check if overflow happens
85121 const cc_temp = lhs_temp1.cmpInts(.neq, &tmp_temp, self) catch |err| switch (err) {
85122 error.SelectFailed => unreachable,
85123 else => |e| return e,
85124 };
85125 try lhs_temp1.die(self);
85126 try tmp_temp.die(self);
85127 const overflow_reloc = try self.genCondBrMir(lhs_ty, cc_temp.tracking(self).short);
85128 try cc_temp.die(self);
85129
85130 // if overflow,
85131 // for unsigned integers, the saturating result is just its max
85132 // for signed integers,
85133 // if lhs is positive, the result is its max
85134 // if lhs is negative, it is min
85135 switch (lhs_ty.intInfo(zcu).signedness) {
85136 .unsigned => {
85137 const bound_mcv = try self.genTypedValue(try lhs_ty.maxIntScalar(self.pt, lhs_ty));
85138 try self.genCopy(lhs_ty, dst_mcv, bound_mcv, .{});
85139 },
85140 .signed => {
85141 // check the sign of lhs
85142 // TODO: optimize this.
85143 // we only need the highest bit so shifting the highest part of lhs_mcv
85144 // is enough to check the signedness. other parts can be skipped here.
85145 var lhs_temp2 = try self.tempInit(lhs_ty, lhs_mcv);
85146 var zero_temp = try self.tempInit(lhs_ty, try self.genTypedValue(try self.pt.intValue(lhs_ty, 0)));
85147 const sign_cc_temp = lhs_temp2.cmpInts(.lt, &zero_temp, self) catch |err| switch (err) {
85148 error.SelectFailed => unreachable,
85149 else => |e| return e,
85150 };
85151 try lhs_temp2.die(self);
85152 try zero_temp.die(self);
85153 const sign_reloc_condbr = try self.genCondBrMir(lhs_ty, sign_cc_temp.tracking(self).short);
85154 try sign_cc_temp.die(self);
85155
85156 // if it is negative
85157 const min_mcv = try self.genTypedValue(try lhs_ty.minIntScalar(self.pt, lhs_ty));
85158 try self.genCopy(lhs_ty, dst_mcv, min_mcv, .{});
85159 const sign_reloc_br = try self.asmJmpReloc(undefined);
85160 self.performReloc(sign_reloc_condbr);
85161
85162 // if it is positive
85163 const max_mcv = try self.genTypedValue(try lhs_ty.maxIntScalar(self.pt, lhs_ty));
85164 try self.genCopy(lhs_ty, dst_mcv, max_mcv, .{});
85165 self.performReloc(sign_reloc_br);
85166 },
85167 }
85168
85169 self.performReloc(overflow_reloc);
85170 break :result dst_mcv;
85171 },
85172 else => {
85173 return self.fail("TODO implement shl_sat for {} op type {}", .{ self.target.cpu.arch, lhs_ty.zigTypeTag(zcu) });
85174 },
85175 }
85176 };
85177 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
85056}85178}
8505785179
85058fn airOptionalPayload(self: *CodeGen, inst: Air.Inst.Index) !void {85180fn airOptionalPayload(self: *CodeGen, inst: Air.Inst.Index) !void {
...@@ -88437,7 +88559,7 @@ fn genShiftBinOpMir(...@@ -88437,7 +88559,7 @@ fn genShiftBinOpMir(
88437) !void {88559) !void {
88438 const pt = self.pt;88560 const pt = self.pt;
88439 const zcu = pt.zcu;88561 const zcu = pt.zcu;
88440 const abi_size: u32 = @intCast(lhs_ty.abiSize(zcu));88562 const abi_size: u31 = @intCast(lhs_ty.abiSize(zcu));
88441 const shift_abi_size: u32 = @intCast(rhs_ty.abiSize(zcu));88563 const shift_abi_size: u32 = @intCast(rhs_ty.abiSize(zcu));
88442 try self.spillEflagsIfOccupied();88564 try self.spillEflagsIfOccupied();
8844388565
...@@ -88621,7 +88743,17 @@ fn genShiftBinOpMir(...@@ -88621,7 +88743,17 @@ fn genShiftBinOpMir(
88621 .immediate => {},88743 .immediate => {},
88622 else => self.performReloc(skip),88744 else => self.performReloc(skip),
88623 }88745 }
88624 }88746 } else try self.asmRegisterMemory(.{ ._, .mov }, temp_regs[2].to64(), .{
88747 .base = .{ .frame = lhs_mcv.load_frame.index },
88748 .mod = .{ .rm = .{
88749 .size = .qword,
88750 .disp = switch (tag[0]) {
88751 ._l => lhs_mcv.load_frame.off,
88752 ._r => lhs_mcv.load_frame.off + abi_size - 8,
88753 else => unreachable,
88754 },
88755 } },
88756 });
88625 switch (rhs_mcv) {88757 switch (rhs_mcv) {
88626 .immediate => |shift_imm| try self.asmRegisterImmediate(88758 .immediate => |shift_imm| try self.asmRegisterImmediate(
88627 tag,88759 tag,
test/behavior/bit_shifting.zig-1
...@@ -111,7 +111,6 @@ test "comptime shift safety check" {...@@ -111,7 +111,6 @@ test "comptime shift safety check" {
111}111}
112112
113test "Saturating Shift Left where lhs is of a computed type" {113test "Saturating Shift Left where lhs is of a computed type" {
114 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
115 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO114 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
116 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO115 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
117 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO116 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO