mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 14:25:16 +00:00
stage2: @addWithOverflow
This commit is contained in:
parent
28bcd7dbdd
commit
f3d635b668
@ -443,12 +443,12 @@ pub const Mutable = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// r = a + b with 2s-complement wrapping semantics.
|
||||
/// r = a + b with 2s-complement wrapping semantics. Returns whether overflow occurred.
|
||||
/// r, a and b may be aliases
|
||||
///
|
||||
/// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
|
||||
/// r is `calcTwosCompLimbCount(bit_count)`.
|
||||
pub fn addWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) void {
|
||||
pub fn addWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) bool {
|
||||
const req_limbs = calcTwosCompLimbCount(bit_count);
|
||||
|
||||
// Slice of the upper bits if they exist, these will be ignored and allows us to use addCarry to determine
|
||||
@ -463,6 +463,7 @@ pub const Mutable = struct {
|
||||
.limbs = b.limbs[0..math.min(req_limbs, b.limbs.len)],
|
||||
};
|
||||
|
||||
var carry_truncated = false;
|
||||
if (r.addCarry(x, y)) {
|
||||
// There are two possibilities here:
|
||||
// - We overflowed req_limbs. In this case, the carry is ignored, as it would be removed by
|
||||
@ -473,10 +474,17 @@ pub const Mutable = struct {
|
||||
if (msl < req_limbs) {
|
||||
r.limbs[msl] = 1;
|
||||
r.len = req_limbs;
|
||||
} else {
|
||||
carry_truncated = true;
|
||||
}
|
||||
}
|
||||
|
||||
r.truncate(r.toConst(), signedness, bit_count);
|
||||
if (!r.toConst().fitsInTwosComp(signedness, bit_count)) {
|
||||
r.truncate(r.toConst(), signedness, bit_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
return carry_truncated;
|
||||
}
|
||||
|
||||
/// r = a + b with 2s-complement saturating semantics.
|
||||
@ -581,13 +589,13 @@ pub const Mutable = struct {
|
||||
r.add(a, b.negate());
|
||||
}
|
||||
|
||||
/// r = a - b with 2s-complement wrapping semantics.
|
||||
/// r = a - b with 2s-complement wrapping semantics. Returns whether any overflow occured.
|
||||
///
|
||||
/// r, a and b may be aliases
|
||||
/// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
|
||||
/// r is `calcTwosCompLimbCount(bit_count)`.
|
||||
pub fn subWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) void {
|
||||
r.addWrap(a, b.negate(), signedness, bit_count);
|
||||
pub fn subWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) bool {
|
||||
return r.addWrap(a, b.negate(), signedness, bit_count);
|
||||
}
|
||||
|
||||
/// r = a - b with 2s-complement saturating semantics.
|
||||
@ -1039,7 +1047,7 @@ pub const Mutable = struct {
|
||||
pub fn bitNotWrap(r: *Mutable, a: Const, signedness: Signedness, bit_count: usize) void {
|
||||
r.copy(a.negate());
|
||||
const negative_one = Const{ .limbs = &.{1}, .positive = false };
|
||||
r.addWrap(r.toConst(), negative_one, signedness, bit_count);
|
||||
_ = r.addWrap(r.toConst(), negative_one, signedness, bit_count);
|
||||
}
|
||||
|
||||
/// r = a | b under 2s complement semantics.
|
||||
@ -2443,17 +2451,18 @@ pub const Managed = struct {
|
||||
r.setMetadata(m.positive, m.len);
|
||||
}
|
||||
|
||||
/// r = a + b with 2s-complement wrapping semantics.
|
||||
/// r = a + b with 2s-complement wrapping semantics. Returns whether any overflow occured.
|
||||
///
|
||||
/// r, a and b may be aliases. If r aliases a or b, then caller must call
|
||||
/// `r.ensureTwosCompCapacity` prior to calling `add`.
|
||||
///
|
||||
/// Returns an error if memory could not be allocated.
|
||||
pub fn addWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!void {
|
||||
pub fn addWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!bool {
|
||||
try r.ensureTwosCompCapacity(bit_count);
|
||||
var m = r.toMutable();
|
||||
m.addWrap(a, b, signedness, bit_count);
|
||||
const wrapped = m.addWrap(a, b, signedness, bit_count);
|
||||
r.setMetadata(m.positive, m.len);
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/// r = a + b with 2s-complement saturating semantics.
|
||||
@ -2481,17 +2490,18 @@ pub const Managed = struct {
|
||||
r.setMetadata(m.positive, m.len);
|
||||
}
|
||||
|
||||
/// r = a - b with 2s-complement wrapping semantics.
|
||||
/// r = a - b with 2s-complement wrapping semantics. Returns whether any overflow occured.
|
||||
///
|
||||
/// r, a and b may be aliases. If r aliases a or b, then caller must call
|
||||
/// `r.ensureTwosCompCapacity` prior to calling `add`.
|
||||
///
|
||||
/// Returns an error if memory could not be allocated.
|
||||
pub fn subWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!void {
|
||||
pub fn subWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!bool {
|
||||
try r.ensureTwosCompCapacity(bit_count);
|
||||
var m = r.toMutable();
|
||||
m.subWrap(a, b, signedness, bit_count);
|
||||
const wrapped = m.subWrap(a, b, signedness, bit_count);
|
||||
r.setMetadata(m.positive, m.len);
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/// r = a - b with 2s-complement saturating semantics.
|
||||
|
||||
@ -590,8 +590,9 @@ test "big.int addWrap single-single, unsigned" {
|
||||
var b = try Managed.initSet(testing.allocator, 10);
|
||||
defer b.deinit();
|
||||
|
||||
try a.addWrap(a.toConst(), b.toConst(), .unsigned, 17);
|
||||
const wrapped = try a.addWrap(a.toConst(), b.toConst(), .unsigned, 17);
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(u17)) == 9);
|
||||
}
|
||||
|
||||
@ -602,8 +603,9 @@ test "big.int subWrap single-single, unsigned" {
|
||||
var b = try Managed.initSet(testing.allocator, maxInt(u17));
|
||||
defer b.deinit();
|
||||
|
||||
try a.subWrap(a.toConst(), b.toConst(), .unsigned, 17);
|
||||
const wrapped = try a.subWrap(a.toConst(), b.toConst(), .unsigned, 17);
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(u17)) == 1);
|
||||
}
|
||||
|
||||
@ -614,8 +616,9 @@ test "big.int addWrap multi-multi, unsigned, limb aligned" {
|
||||
var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb));
|
||||
defer b.deinit();
|
||||
|
||||
try a.addWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
|
||||
const wrapped = try a.addWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 1);
|
||||
}
|
||||
|
||||
@ -626,8 +629,9 @@ test "big.int subWrap single-multi, unsigned, limb aligned" {
|
||||
var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb) + 100);
|
||||
defer b.deinit();
|
||||
|
||||
try a.subWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
|
||||
const wrapped = try a.subWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 88);
|
||||
}
|
||||
|
||||
@ -638,8 +642,9 @@ test "big.int addWrap single-single, signed" {
|
||||
var b = try Managed.initSet(testing.allocator, 1 + 1 + maxInt(u21));
|
||||
defer b.deinit();
|
||||
|
||||
try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
|
||||
const wrapped = try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(i21)) == minInt(i21));
|
||||
}
|
||||
|
||||
@ -650,8 +655,9 @@ test "big.int subWrap single-single, signed" {
|
||||
var b = try Managed.initSet(testing.allocator, 1);
|
||||
defer b.deinit();
|
||||
|
||||
try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
|
||||
const wrapped = try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(i21)) == maxInt(i21));
|
||||
}
|
||||
|
||||
@ -662,8 +668,9 @@ test "big.int addWrap multi-multi, signed, limb aligned" {
|
||||
var b = try Managed.initSet(testing.allocator, maxInt(SignedDoubleLimb));
|
||||
defer b.deinit();
|
||||
|
||||
try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
|
||||
const wrapped = try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(SignedDoubleLimb)) == -2);
|
||||
}
|
||||
|
||||
@ -674,8 +681,9 @@ test "big.int subWrap single-multi, signed, limb aligned" {
|
||||
var b = try Managed.initSet(testing.allocator, 1);
|
||||
defer b.deinit();
|
||||
|
||||
try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
|
||||
const wrapped = try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
|
||||
|
||||
try testing.expect(wrapped);
|
||||
try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
|
||||
}
|
||||
|
||||
|
||||
@ -135,6 +135,12 @@ pub const Inst = struct {
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
min,
|
||||
/// Integer addition with overflow. Both operands are guaranteed to be the same type,
|
||||
/// and the result is bool. The wrapped value is written to the pointer given by the in
|
||||
/// operand of the `pl_op` field. Payload is `Bin` with `lhs` and `rhs` the relevant types
|
||||
/// of the operation.
|
||||
/// Uses the `pl_op` field with payload `Bin`.
|
||||
add_with_overflow,
|
||||
/// Allocates stack local memory.
|
||||
/// Uses the `ty` field.
|
||||
alloc,
|
||||
@ -804,6 +810,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
|
||||
const ptr_ty = air.typeOf(datas[inst].pl_op.operand);
|
||||
return ptr_ty.elemType();
|
||||
},
|
||||
|
||||
.add_with_overflow => return Type.initTag(.bool),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -381,7 +381,7 @@ fn analyzeInst(
|
||||
const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none });
|
||||
},
|
||||
.memset, .memcpy => {
|
||||
.memset, .memcpy, .add_with_overflow => {
|
||||
const pl_op = inst_datas[inst].pl_op;
|
||||
const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
|
||||
|
||||
99
src/Sema.zig
99
src/Sema.zig
@ -1051,10 +1051,10 @@ fn zirExtended(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
|
||||
.@"asm" => return sema.zirAsm( block, extended, inst),
|
||||
.typeof_peer => return sema.zirTypeofPeer( block, extended),
|
||||
.compile_log => return sema.zirCompileLog( block, extended),
|
||||
.add_with_overflow => return sema.zirOverflowArithmetic(block, extended),
|
||||
.sub_with_overflow => return sema.zirOverflowArithmetic(block, extended),
|
||||
.mul_with_overflow => return sema.zirOverflowArithmetic(block, extended),
|
||||
.shl_with_overflow => return sema.zirOverflowArithmetic(block, extended),
|
||||
.add_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
|
||||
.sub_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
|
||||
.mul_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
|
||||
.shl_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
|
||||
.c_undef => return sema.zirCUndef( block, extended),
|
||||
.c_include => return sema.zirCInclude( block, extended),
|
||||
.c_define => return sema.zirCDefine( block, extended),
|
||||
@ -7310,6 +7310,7 @@ fn zirOverflowArithmetic(
|
||||
sema: *Sema,
|
||||
block: *Block,
|
||||
extended: Zir.Inst.Extended.InstData,
|
||||
zir_tag: Zir.Inst.Extended,
|
||||
) CompileError!Air.Inst.Ref {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -7317,7 +7318,95 @@ fn zirOverflowArithmetic(
|
||||
const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data;
|
||||
const src: LazySrcLoc = .{ .node_offset = extra.node };
|
||||
|
||||
return sema.fail(block, src, "TODO implement Sema.zirOverflowArithmetic", .{});
|
||||
const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
|
||||
const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
|
||||
const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = extra.node };
|
||||
|
||||
const lhs = sema.resolveInst(extra.lhs);
|
||||
const rhs = sema.resolveInst(extra.rhs);
|
||||
const ptr = sema.resolveInst(extra.ptr);
|
||||
|
||||
const lhs_ty = sema.typeOf(lhs);
|
||||
|
||||
// Note, the types of lhs/rhs (also for shifting)/ptr are already correct as ensured by astgen.
|
||||
const dest_ty = lhs_ty;
|
||||
if (dest_ty.zigTypeTag() != .Int) {
|
||||
return sema.fail(block, src, "expected integer type, found '{}'", .{dest_ty});
|
||||
}
|
||||
|
||||
const target = sema.mod.getTarget();
|
||||
|
||||
const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
|
||||
const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
|
||||
|
||||
const result: struct {
|
||||
overflowed: enum { yes, no, undef },
|
||||
wrapped: Air.Inst.Ref,
|
||||
} = result: {
|
||||
const air_tag: Air.Inst.Tag = switch (zir_tag) {
|
||||
.add_with_overflow => blk: {
|
||||
// If either of the arguments is zero, `false` is returned and the other is stored
|
||||
// to the result, even if it is undefined..
|
||||
// Otherwise, if either of the argument is undefined, undefined is returned.
|
||||
if (maybe_lhs_val) |lhs_val| {
|
||||
if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
|
||||
break :result .{ .overflowed = .no, .wrapped = rhs };
|
||||
}
|
||||
}
|
||||
if (maybe_rhs_val) |rhs_val| {
|
||||
if (!rhs_val.isUndef() and rhs_val.compareWithZero(.eq)) {
|
||||
break :result .{ .overflowed = .no, .wrapped = lhs };
|
||||
}
|
||||
}
|
||||
if (maybe_lhs_val) |lhs_val| {
|
||||
if (maybe_rhs_val) |rhs_val| {
|
||||
if (lhs_val.isUndef() or rhs_val.isUndef()) {
|
||||
break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) };
|
||||
}
|
||||
|
||||
const result = try lhs_val.intAddWithOverflow(rhs_val, dest_ty, sema.arena, target);
|
||||
const inst = try sema.addConstant(
|
||||
dest_ty,
|
||||
result.wrapped_result,
|
||||
);
|
||||
|
||||
if (result.overflowed) {
|
||||
break :result .{ .overflowed = .yes, .wrapped = inst };
|
||||
} else {
|
||||
break :result .{ .overflowed = .no, .wrapped = inst };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break :blk .add_with_overflow;
|
||||
},
|
||||
.sub_with_overflow,
|
||||
.mul_with_overflow,
|
||||
.shl_with_overflow,
|
||||
=> return sema.fail(block, src, "TODO implement Sema.zirOverflowArithmetic for {}", .{zir_tag}),
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
try sema.requireRuntimeBlock(block, src);
|
||||
return block.addInst(.{
|
||||
.tag = air_tag,
|
||||
.data = .{ .pl_op = .{
|
||||
.operand = ptr,
|
||||
.payload = try sema.addExtra(Air.Bin{
|
||||
.lhs = lhs,
|
||||
.rhs = rhs,
|
||||
}),
|
||||
} },
|
||||
});
|
||||
};
|
||||
|
||||
try sema.storePtr2(block, src, ptr, ptr_src, result.wrapped, src, .store);
|
||||
|
||||
return switch (result.overflowed) {
|
||||
.yes => Air.Inst.Ref.bool_true,
|
||||
.no => Air.Inst.Ref.bool_false,
|
||||
.undef => try sema.addConstUndef(Type.initTag(.bool)),
|
||||
};
|
||||
}
|
||||
|
||||
fn analyzeArithmetic(
|
||||
|
||||
@ -521,6 +521,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.max => try self.airMax(inst),
|
||||
.slice => try self.airSlice(inst),
|
||||
|
||||
.add_with_overflow => try self.airAddWithOverflow(inst),
|
||||
|
||||
.div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
|
||||
|
||||
.cmp_lt => try self.airCmp(inst, .lt),
|
||||
@ -968,6 +970,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
|
||||
}
|
||||
|
||||
fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
|
||||
|
||||
@ -519,6 +519,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.max => try self.airMax(inst),
|
||||
.slice => try self.airSlice(inst),
|
||||
|
||||
.add_with_overflow => try self.airAddWithOverflow(inst),
|
||||
|
||||
.div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
|
||||
|
||||
.cmp_lt => try self.airCmp(inst, .lt),
|
||||
@ -998,6 +1000,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
|
||||
}
|
||||
|
||||
fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
|
||||
|
||||
@ -500,6 +500,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.max => try self.airMax(inst),
|
||||
.slice => try self.airSlice(inst),
|
||||
|
||||
.add_with_overflow => try self.airAddWithOverflow(inst),
|
||||
|
||||
.div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
|
||||
|
||||
.cmp_lt => try self.airCmp(inst, .lt),
|
||||
@ -913,6 +915,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
|
||||
}
|
||||
|
||||
fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
|
||||
|
||||
@ -553,6 +553,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
|
||||
.max => try self.airMax(inst),
|
||||
.slice => try self.airSlice(inst),
|
||||
|
||||
.add_with_overflow => try self.airAddWithOverflow(inst),
|
||||
|
||||
.div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
|
||||
|
||||
.cmp_lt => try self.airCmp(inst, .lt),
|
||||
@ -1027,6 +1029,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
|
||||
return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
|
||||
}
|
||||
|
||||
fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
|
||||
}
|
||||
|
||||
fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const result: MCValue = if (self.liveness.isUnused(inst))
|
||||
|
||||
@ -1155,6 +1155,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
|
||||
.mul_sat => try airSatOp(f, inst, "muls_"),
|
||||
.shl_sat => try airSatOp(f, inst, "shls_"),
|
||||
|
||||
.add_with_overflow => try airAddWithOverflow(f, inst),
|
||||
|
||||
.min => try airMinMax(f, inst, "<"),
|
||||
.max => try airMinMax(f, inst, ">"),
|
||||
|
||||
@ -1864,6 +1866,12 @@ fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue {
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn airAddWithOverflow(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
_ = f;
|
||||
_ = inst;
|
||||
return f.fail("TODO add with overflow", .{});
|
||||
}
|
||||
|
||||
fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
|
||||
if (f.liveness.isUnused(inst))
|
||||
return CValue.none;
|
||||
|
||||
@ -1714,6 +1714,8 @@ pub const FuncGen = struct {
|
||||
.max => try self.airMax(inst),
|
||||
.slice => try self.airSlice(inst),
|
||||
|
||||
.add_with_overflow => try self.airAddWithOverflow(inst),
|
||||
|
||||
.bit_and, .bool_and => try self.airAnd(inst),
|
||||
.bit_or, .bool_or => try self.airOr(inst),
|
||||
.xor => try self.airXor(inst),
|
||||
@ -3133,6 +3135,38 @@ pub const FuncGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn airAddWithOverflow(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
|
||||
|
||||
const ptr = try self.resolveInst(pl_op.operand);
|
||||
const lhs = try self.resolveInst(extra.lhs);
|
||||
const rhs = try self.resolveInst(extra.rhs);
|
||||
|
||||
const ptr_ty = self.air.typeOf(pl_op.operand);
|
||||
const lhs_ty = self.air.typeOf(extra.lhs);
|
||||
|
||||
const intrinsic_name: []const u8 = if (lhs_ty.isSignedInt())
|
||||
"llvm.sadd.with.overflow"
|
||||
else
|
||||
"llvm.uadd.with.overflow";
|
||||
|
||||
const llvm_lhs_ty = try self.dg.llvmType(lhs_ty);
|
||||
|
||||
const llvm_fn = self.getIntrinsic(intrinsic_name, &.{llvm_lhs_ty});
|
||||
const result_struct = self.builder.buildCall(llvm_fn, &[_]*const llvm.Value{ lhs, rhs }, 2, .Fast, .Auto, "");
|
||||
|
||||
const result = self.builder.buildExtractValue(result_struct, 0, "");
|
||||
const overflow_bit = self.builder.buildExtractValue(result_struct, 1, "");
|
||||
|
||||
self.store(ptr, ptr_ty, result, .NotAtomic);
|
||||
|
||||
return overflow_bit;
|
||||
}
|
||||
|
||||
fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
@ -3511,7 +3545,7 @@ pub const FuncGen = struct {
|
||||
|
||||
fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
_ = inst;
|
||||
const llvm_fn = self.getIntrinsic("llvm.debugtrap");
|
||||
const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{});
|
||||
_ = self.builder.buildCall(llvm_fn, undefined, 0, .C, .Auto, "");
|
||||
return null;
|
||||
}
|
||||
@ -3946,13 +3980,10 @@ pub const FuncGen = struct {
|
||||
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
|
||||
}
|
||||
|
||||
fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value {
|
||||
fn getIntrinsic(self: *FuncGen, name: []const u8, types: []*const llvm.Type) *const llvm.Value {
|
||||
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
|
||||
assert(id != 0);
|
||||
// TODO: add support for overload intrinsics by passing the prefix of the intrinsic
|
||||
// to `lookupIntrinsicID` and then passing the correct types to
|
||||
// `getIntrinsicDeclaration`
|
||||
return self.llvmModule().getIntrinsicDeclaration(id, null, 0);
|
||||
return self.llvmModule().getIntrinsicDeclaration(id, types.ptr, types.len);
|
||||
}
|
||||
|
||||
fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) ?*const llvm.Value {
|
||||
|
||||
@ -228,6 +228,7 @@ const Writer = struct {
|
||||
.atomic_rmw => try w.writeAtomicRmw(s, inst),
|
||||
.memcpy => try w.writeMemcpy(s, inst),
|
||||
.memset => try w.writeMemset(s, inst),
|
||||
.add_with_overflow => try w.writeAddWithOverflow(s, inst),
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,6 +349,17 @@ const Writer = struct {
|
||||
try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) });
|
||||
}
|
||||
|
||||
fn writeAddWithOverflow(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
|
||||
|
||||
try w.writeOperand(s, inst, 0, pl_op.operand);
|
||||
try s.writeAll(", ");
|
||||
try w.writeOperand(s, inst, 1, extra.lhs);
|
||||
try s.writeAll(", ");
|
||||
try w.writeOperand(s, inst, 2, extra.rhs);
|
||||
}
|
||||
|
||||
fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
|
||||
|
||||
@ -1969,6 +1969,37 @@ pub const Value = extern union {
|
||||
return @divFloor(@floatToInt(std.math.big.Limb, std.math.log2(w_value)), @typeInfo(std.math.big.Limb).Int.bits) + 1;
|
||||
}
|
||||
|
||||
pub const OverflowArithmeticResult = struct {
|
||||
overflowed: bool,
|
||||
wrapped_result: Value,
|
||||
};
|
||||
|
||||
pub fn intAddWithOverflow(
|
||||
lhs: Value,
|
||||
rhs: Value,
|
||||
ty: Type,
|
||||
arena: Allocator,
|
||||
target: Target,
|
||||
) !OverflowArithmeticResult {
|
||||
const info = ty.intInfo(target);
|
||||
|
||||
var lhs_space: Value.BigIntSpace = undefined;
|
||||
var rhs_space: Value.BigIntSpace = undefined;
|
||||
const lhs_bigint = lhs.toBigInt(&lhs_space);
|
||||
const rhs_bigint = rhs.toBigInt(&rhs_space);
|
||||
const limbs = try arena.alloc(
|
||||
std.math.big.Limb,
|
||||
std.math.big.int.calcTwosCompLimbCount(info.bits),
|
||||
);
|
||||
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
|
||||
const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
|
||||
const result = try fromBigInt(arena, result_bigint.toConst());
|
||||
return OverflowArithmeticResult{
|
||||
.overflowed = overflowed,
|
||||
.wrapped_result = result,
|
||||
};
|
||||
}
|
||||
|
||||
/// Supports both floats and ints; handles undefined.
|
||||
pub fn numberAddWrap(
|
||||
lhs: Value,
|
||||
@ -1983,19 +2014,8 @@ pub const Value = extern union {
|
||||
return floatAdd(lhs, rhs, ty, arena);
|
||||
}
|
||||
|
||||
const info = ty.intInfo(target);
|
||||
|
||||
var lhs_space: Value.BigIntSpace = undefined;
|
||||
var rhs_space: Value.BigIntSpace = undefined;
|
||||
const lhs_bigint = lhs.toBigInt(&lhs_space);
|
||||
const rhs_bigint = rhs.toBigInt(&rhs_space);
|
||||
const limbs = try arena.alloc(
|
||||
std.math.big.Limb,
|
||||
std.math.big.int.calcTwosCompLimbCount(info.bits),
|
||||
);
|
||||
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
|
||||
result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
|
||||
return fromBigInt(arena, result_bigint.toConst());
|
||||
const overflow_result = try intAddWithOverflow(lhs, rhs, ty, arena, target);
|
||||
return overflow_result.wrapped_result;
|
||||
}
|
||||
|
||||
fn fromBigInt(arena: Allocator, big_int: BigIntConst) !Value {
|
||||
@ -2040,6 +2060,32 @@ pub const Value = extern union {
|
||||
return fromBigInt(arena, result_bigint.toConst());
|
||||
}
|
||||
|
||||
pub fn intSubWithOverflow(
|
||||
lhs: Value,
|
||||
rhs: Value,
|
||||
ty: Type,
|
||||
arena: Allocator,
|
||||
target: Target,
|
||||
) !OverflowArithmeticResult {
|
||||
const info = ty.intInfo(target);
|
||||
|
||||
var lhs_space: Value.BigIntSpace = undefined;
|
||||
var rhs_space: Value.BigIntSpace = undefined;
|
||||
const lhs_bigint = lhs.toBigInt(&lhs_space);
|
||||
const rhs_bigint = rhs.toBigInt(&rhs_space);
|
||||
const limbs = try arena.alloc(
|
||||
std.math.big.Limb,
|
||||
std.math.big.int.calcTwosCompLimbCount(info.bits),
|
||||
);
|
||||
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
|
||||
const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
|
||||
const wrapped_result = try fromBigInt(arena, result_bigint.toConst());
|
||||
return OverflowArithmeticResult{
|
||||
.overflowed = overflowed,
|
||||
.wrapped_result = wrapped_result,
|
||||
};
|
||||
}
|
||||
|
||||
/// Supports both floats and ints; handles undefined.
|
||||
pub fn numberSubWrap(
|
||||
lhs: Value,
|
||||
@ -2054,19 +2100,8 @@ pub const Value = extern union {
|
||||
return floatSub(lhs, rhs, ty, arena);
|
||||
}
|
||||
|
||||
const info = ty.intInfo(target);
|
||||
|
||||
var lhs_space: Value.BigIntSpace = undefined;
|
||||
var rhs_space: Value.BigIntSpace = undefined;
|
||||
const lhs_bigint = lhs.toBigInt(&lhs_space);
|
||||
const rhs_bigint = rhs.toBigInt(&rhs_space);
|
||||
const limbs = try arena.alloc(
|
||||
std.math.big.Limb,
|
||||
std.math.big.int.calcTwosCompLimbCount(info.bits),
|
||||
);
|
||||
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
|
||||
result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
|
||||
return fromBigInt(arena, result_bigint.toConst());
|
||||
const overflow_result = try intSubWithOverflow(lhs, rhs, ty, arena, target);
|
||||
return overflow_result.wrapped_result;
|
||||
}
|
||||
|
||||
/// Supports integers only; asserts neither operand is undefined.
|
||||
|
||||
@ -444,3 +444,30 @@ test "128-bit multiplication" {
|
||||
var c = a * b;
|
||||
try expect(c == 6);
|
||||
}
|
||||
|
||||
test "@addWithOverflow" {
|
||||
var result: u8 = undefined;
|
||||
try expect(@addWithOverflow(u8, 250, 100, &result));
|
||||
try expect(result == 94);
|
||||
try expect(!@addWithOverflow(u8, 100, 150, &result));
|
||||
try expect(result == 250);
|
||||
}
|
||||
|
||||
test "small int addition" {
|
||||
var x: u2 = 0;
|
||||
try expect(x == 0);
|
||||
|
||||
x += 1;
|
||||
try expect(x == 1);
|
||||
|
||||
x += 1;
|
||||
try expect(x == 2);
|
||||
|
||||
x += 1;
|
||||
try expect(x == 3);
|
||||
|
||||
var result: @TypeOf(x) = 3;
|
||||
try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
|
||||
|
||||
try expect(result == 0);
|
||||
}
|
||||
|
||||
@ -6,14 +6,6 @@ const maxInt = std.math.maxInt;
|
||||
const minInt = std.math.minInt;
|
||||
const mem = std.mem;
|
||||
|
||||
test "@addWithOverflow" {
|
||||
var result: u8 = undefined;
|
||||
try expect(@addWithOverflow(u8, 250, 100, &result));
|
||||
try expect(result == 94);
|
||||
try expect(!@addWithOverflow(u8, 100, 150, &result));
|
||||
try expect(result == 250);
|
||||
}
|
||||
|
||||
test "@mulWithOverflow" {
|
||||
var result: u8 = undefined;
|
||||
try expect(@mulWithOverflow(u8, 86, 3, &result));
|
||||
@ -90,25 +82,6 @@ fn testCtzVectors() !void {
|
||||
try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
|
||||
}
|
||||
|
||||
test "small int addition" {
|
||||
var x: u2 = 0;
|
||||
try expect(x == 0);
|
||||
|
||||
x += 1;
|
||||
try expect(x == 1);
|
||||
|
||||
x += 1;
|
||||
try expect(x == 2);
|
||||
|
||||
x += 1;
|
||||
try expect(x == 3);
|
||||
|
||||
var result: @TypeOf(x) = 3;
|
||||
try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
|
||||
|
||||
try expect(result == 0);
|
||||
}
|
||||
|
||||
test "allow signed integer division/remainder when values are comptime known and positive or exact" {
|
||||
try expect(5 / 3 == 1);
|
||||
try expect(-5 / -3 == 1);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user