Skip to content

Commit

Permalink
x86_64: Implement integer saturating left shifting codegen
Browse files Browse the repository at this point in the history
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
  • Loading branch information
xtexx committed Jan 18, 2025
1 parent f38d7a9 commit ec2df79
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 4 deletions.
81 changes: 78 additions & 3 deletions src/arch/x86_64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12868,10 +12868,85 @@ fn airShlShrBinOp(self: *CodeGen, inst: Air.Inst.Index) !void {
}

fn airShlSat(self: *CodeGen, inst: Air.Inst.Index) !void {
const zcu = self.pt.zcu;
const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
_ = bin_op;
return self.fail("TODO implement shl_sat for {}", .{self.target.cpu.arch});
//return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
const lhs_ty = self.typeOf(bin_op.lhs);
const rhs_ty = self.typeOf(bin_op.rhs);

const result: MCValue = result: {
switch (lhs_ty.zigTypeTag(zcu)) {
.int => {
try self.spillRegisters(&.{.rcx});
try self.register_manager.getKnownReg(.rcx, null);
const lhs_mcv = try self.resolveInst(bin_op.lhs);
const rhs_mcv = try self.resolveInst(bin_op.rhs);

// 1. shift left
const dst_mcv = try self.genShiftBinOp(Air.Inst.Tag.shl, null, lhs_mcv, rhs_mcv, lhs_ty, rhs_ty);
switch (dst_mcv) {
.register => |dst_reg| try self.truncateRegister(lhs_ty, dst_reg),
.register_pair => |dst_regs| try self.truncateRegister(lhs_ty, dst_regs[1]),
.load_frame => |frame_addr| {
const tmp_reg =
try self.register_manager.allocReg(null, abi.RegisterClass.gp);
const tmp_lock = self.register_manager.lockRegAssumeUnused(tmp_reg);
defer self.register_manager.unlockReg(tmp_lock);

const lhs_bits: u31 = @intCast(lhs_ty.bitSize(zcu));
const tmp_ty: Type = if (lhs_bits > 64) .usize else lhs_ty;
const off = frame_addr.off + (lhs_bits - 1) / 64 * 8;
try self.genSetReg(
tmp_reg,
tmp_ty,
.{ .load_frame = .{ .index = frame_addr.index, .off = off } },
.{},
);
try self.truncateRegister(lhs_ty, tmp_reg);
try self.genSetMem(
.{ .frame = frame_addr.index },
off,
tmp_ty,
.{ .register = tmp_reg },
.{},
);
},
else => {},
}
const dst_lock = switch (dst_mcv) {
.register => |reg| self.register_manager.lockRegAssumeUnused(reg),
else => null,
};
defer if (dst_lock) |lock| self.register_manager.unlockReg(lock);

// 2. shift right
const tmp_mcv = try self.genShiftBinOp(Air.Inst.Tag.shr, null, dst_mcv, rhs_mcv, lhs_ty, rhs_ty);
const tmp_lock = switch (tmp_mcv) {
.register => |reg| self.register_manager.lockRegAssumeUnused(reg),
else => null,
};
defer if (tmp_lock) |lock| self.register_manager.unlockReg(lock);

// 3. check if overflow happens
try self.genBinOpMir(.{ ._, .cmp }, lhs_ty, tmp_mcv, lhs_mcv);
const reloc = try self.genCondBrMir(lhs_ty, .{ .eflags = Condition.ne });

// 4. if overflow, set the value to upper limit
const bound_val = try lhs_ty.maxIntScalar(self.pt, lhs_ty);
const bound_mcv = try self.genTypedValue(bound_val);
switch (dst_mcv) {
.register, .register_pair, .load_frame, .memory => try self.genCopy(lhs_ty, dst_mcv, bound_mcv, CopyOptions{}),
else => return self.fail("TODO implement shl_sat for {} dest type {}", .{ self.target.cpu.arch, lhs_ty.zigTypeTag(zcu) }),
}

self.performReloc(reloc);
break :result dst_mcv;
},
else => {
return self.fail("TODO implement shl_sat for {} op type {}", .{ self.target.cpu.arch, lhs_ty.zigTypeTag(zcu) });
},
}
};
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
}

fn airOptionalPayload(self: *CodeGen, inst: Air.Inst.Index) !void {
Expand Down
1 change: 0 additions & 1 deletion test/behavior/bit_shifting.zig
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ test "comptime shift safety check" {
}

test "Saturating Shift Left where lhs is of a computed 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
Expand Down

0 comments on commit ec2df79

Please sign in to comment.