Skip to content

Commit

Permalink
Merge pull request #14024 from Vexu/overflow-arithmetic
Browse files Browse the repository at this point in the history
Make overflow arithmetic builtins return tuples
  • Loading branch information
andrewrk authored Dec 27, 2022
2 parents 55c3efc + a777373 commit 19056cb
Show file tree
Hide file tree
Showing 40 changed files with 700 additions and 612 deletions.
58 changes: 26 additions & 32 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -5413,14 +5413,14 @@ pub fn parseU64(buf: []const u8, radix: u8) !u64 {
}

// x *= radix
if (@mulWithOverflow(u64, x, radix, &x)) {
return error.Overflow;
}
var ov = @mulWithOverflow(x, radix);
if (ov[1] != 0) return error.OverFlow;


// x += digit
if (@addWithOverflow(u64, x, digit, &x)) {
return error.Overflow;
}
ov = @addWithOverflow(ov[0], digit);
if (ov[1] != 0) return error.OverFlow;
x = ov[0];
}

return x;
Expand Down Expand Up @@ -5832,14 +5832,16 @@ test "merge error sets" {
{#code_begin|test|inferred_error_sets#}
// With an inferred error set
pub fn add_inferred(comptime T: type, a: T, b: T) !T {
var answer: T = undefined;
return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer;
const ov = @addWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}

// With an explicit error set
pub fn add_explicit(comptime T: type, a: T, b: T) Error!T {
var answer: T = undefined;
return if (@addWithOverflow(T, a, b, &answer)) error.Overflow else answer;
const ov = @addWithOverflow(a, b);
if (ov[1] != 0) return error.Overflow;
return ov[0];
}

const Error = error {
Expand Down Expand Up @@ -7632,11 +7634,9 @@ test "global assembly" {
</p>
{#header_close#}
{#header_open|@addWithOverflow#}
<pre>{#syntax#}@addWithOverflow(comptime T: type, a: T, b: T, result: *T) bool{#endsyntax#}</pre>
<pre>{#syntax#}@addWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }{#endsyntax#}</pre>
<p>
Performs {#syntax#}result.* = a + b{#endsyntax#}. If overflow or underflow occurs,
stores the overflowed bits in {#syntax#}result{#endsyntax#} and returns {#syntax#}true{#endsyntax#}.
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
Performs {#syntax#}a + b{#endsyntax#} and returns a tuple with the result and a possible overflow bit.
</p>
{#header_close#}
{#header_open|@alignCast#}
Expand Down Expand Up @@ -8695,11 +8695,9 @@ test "@wasmMemoryGrow" {
{#header_close#}

{#header_open|@mulWithOverflow#}
<pre>{#syntax#}@mulWithOverflow(comptime T: type, a: T, b: T, result: *T) bool{#endsyntax#}</pre>
<pre>{#syntax#}@mulWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }{#endsyntax#}</pre>
<p>
Performs {#syntax#}result.* = a * b{#endsyntax#}. If overflow or underflow occurs,
stores the overflowed bits in {#syntax#}result{#endsyntax#} and returns {#syntax#}true{#endsyntax#}.
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
Performs {#syntax#}a * b{#endsyntax#} and returns a tuple with the result and a possible overflow bit.
</p>
{#header_close#}

Expand Down Expand Up @@ -8973,15 +8971,13 @@ test "@setRuntimeSafety" {
{#header_close#}

{#header_open|@shlWithOverflow#}
<pre>{#syntax#}@shlWithOverflow(comptime T: type, a: T, shift_amt: Log2T, result: *T) bool{#endsyntax#}</pre>
<pre>{#syntax#}@shlWithOverflow(a: anytype, shift_amt: Log2T) struct { @TypeOf(a), u1 }{#endsyntax#}</pre>
<p>
Performs {#syntax#}result.* = a << b{#endsyntax#}. If overflow or underflow occurs,
stores the overflowed bits in {#syntax#}result{#endsyntax#} and returns {#syntax#}true{#endsyntax#}.
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
Performs {#syntax#}a << b{#endsyntax#} and returns a tuple with the result and a possible overflow bit.
</p>
<p>
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(@typeInfo(T).Int.bits){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= @typeInfo(T).Int.bits{#endsyntax#} is undefined behavior.
The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(@typeInfo(@TypeOf(a)).Int.bits){#endsyntax#} bits.
This is because {#syntax#}shift_amt >= @typeInfo(@TypeOf(a)).Int.bits{#endsyntax#} is undefined behavior.
</p>
{#see_also|@shlExact|@shrExact#}
{#header_close#}
Expand Down Expand Up @@ -9323,11 +9319,9 @@ fn doTheTest() !void {
{#header_close#}

{#header_open|@subWithOverflow#}
<pre>{#syntax#}@subWithOverflow(comptime T: type, a: T, b: T, result: *T) bool{#endsyntax#}</pre>
<pre>{#syntax#}@subWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }{#endsyntax#}</pre>
<p>
Performs {#syntax#}result.* = a - b{#endsyntax#}. If overflow or underflow occurs,
stores the overflowed bits in {#syntax#}result{#endsyntax#} and returns {#syntax#}true{#endsyntax#}.
If no overflow or underflow occurs, returns {#syntax#}false{#endsyntax#}.
Performs {#syntax#}a - b{#endsyntax#} and returns a tuple with the result and a possible overflow bit.
</p>
{#header_close#}

Expand Down Expand Up @@ -9774,11 +9768,11 @@ const print = @import("std").debug.print;
pub fn main() void {
var byte: u8 = 255;

var result: u8 = undefined;
if (@addWithOverflow(u8, byte, 10, &result)) {
print("overflowed result: {}\n", .{result});
const ov = @addWithOverflow(byte, 10);
if (ov[1] != 0) {
print("overflowed result: {}\n", .{ov[0]});
} else {
print("result: {}\n", .{result});
print("result: {}\n", .{ov[0]});
}
}
{#code_end#}
Expand Down
14 changes: 8 additions & 6 deletions lib/compiler_rt/trunctfxf2.zig
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,16 @@ pub fn __trunctfxf2(a: f128) callconv(.C) f80 {
const round_bits = a_abs & round_mask;
if (round_bits > halfway) {
// Round to nearest
const carry = @boolToInt(@addWithOverflow(u64, res.fraction, 1, &res.fraction));
res.exp += carry;
res.fraction |= @as(u64, carry) << 63; // Restore integer bit after carry
const ov = @addWithOverflow(res.fraction, 1);
res.fraction = ov[0];
res.exp += ov[1];
res.fraction |= @as(u64, ov[1]) << 63; // Restore integer bit after carry
} else if (round_bits == halfway) {
// Ties to even
const carry = @boolToInt(@addWithOverflow(u64, res.fraction, res.fraction & 1, &res.fraction));
res.exp += carry;
res.fraction |= @as(u64, carry) << 63; // Restore integer bit after carry
const ov = @addWithOverflow(res.fraction, res.fraction & 1);
res.fraction = ov[0];
res.exp += ov[1];
res.fraction |= @as(u64, ov[1]) << 63; // Restore integer bit after carry
}
if (res.exp == 0) res.fraction &= ~@as(u64, integer_bit); // Remove integer bit for de-normals
}
Expand Down
4 changes: 1 addition & 3 deletions lib/std/compress/deflate/compressor_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,7 @@ test "deflate/inflate" {
defer testing.allocator.free(large_data_chunk);
// fill with random data
for (large_data_chunk) |_, i| {
var mul: u8 = @truncate(u8, i);
_ = @mulWithOverflow(u8, mul, mul, &mul);
large_data_chunk[i] = mul;
large_data_chunk[i] = @truncate(u8, i) *% @truncate(u8, i);
}
try testToFromWithLimit(large_data_chunk, limits);
}
Expand Down
16 changes: 8 additions & 8 deletions lib/std/crypto/pcurves/p256/p256_64.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ pub const NonMontgomeryDomainFieldElement = [4]u64;
inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @addWithOverflow(u64, arg2, arg3, &t);
const carry2 = @addWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @addWithOverflow(arg2, arg3);
const ov2 = @addWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function subborrowxU64 is a subtraction with borrow.
Expand All @@ -97,10 +97,10 @@ inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) vo
inline fn subborrowxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @subWithOverflow(u64, arg2, arg3, &t);
const carry2 = @subWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @subWithOverflow(arg2, arg3);
const ov2 = @subWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function mulxU64 is a multiplication, returning the full double-width result.
Expand Down
16 changes: 8 additions & 8 deletions lib/std/crypto/pcurves/p256/p256_scalar_64.zig
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ pub const NonMontgomeryDomainFieldElement = [4]u64;
inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @addWithOverflow(u64, arg2, arg3, &t);
const carry2 = @addWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @addWithOverflow(arg2, arg3);
const ov2 = @addWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function subborrowxU64 is a subtraction with borrow.
Expand All @@ -97,10 +97,10 @@ inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) vo
inline fn subborrowxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @subWithOverflow(u64, arg2, arg3, &t);
const carry2 = @subWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @subWithOverflow(arg2, arg3);
const ov2 = @subWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function mulxU64 is a multiplication, returning the full double-width result.
Expand Down
16 changes: 8 additions & 8 deletions lib/std/crypto/pcurves/p384/p384_64.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ pub const NonMontgomeryDomainFieldElement = [6]u64;
inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @addWithOverflow(u64, arg2, arg3, &t);
const carry2 = @addWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @addWithOverflow(arg2, arg3);
const ov2 = @addWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function subborrowxU64 is a subtraction with borrow.
Expand All @@ -66,10 +66,10 @@ inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) vo
inline fn subborrowxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @subWithOverflow(u64, arg2, arg3, &t);
const carry2 = @subWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @subWithOverflow(arg2, arg3);
const ov2 = @subWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function mulxU64 is a multiplication, returning the full double-width result.
Expand Down
16 changes: 8 additions & 8 deletions lib/std/crypto/pcurves/p384/p384_scalar_64.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ pub const NonMontgomeryDomainFieldElement = [6]u64;
inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @addWithOverflow(u64, arg2, arg3, &t);
const carry2 = @addWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @addWithOverflow(arg2, arg3);
const ov2 = @addWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function subborrowxU64 is a subtraction with borrow.
Expand All @@ -66,10 +66,10 @@ inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) vo
inline fn subborrowxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @subWithOverflow(u64, arg2, arg3, &t);
const carry2 = @subWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @subWithOverflow(arg2, arg3);
const ov2 = @subWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function mulxU64 is a multiplication, returning the full double-width result.
Expand Down
16 changes: 8 additions & 8 deletions lib/std/crypto/pcurves/secp256k1/secp256k1_64.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ pub const NonMontgomeryDomainFieldElement = [4]u64;
inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @addWithOverflow(u64, arg2, arg3, &t);
const carry2 = @addWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @addWithOverflow(arg2, arg3);
const ov2 = @addWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function subborrowxU64 is a subtraction with borrow.
Expand All @@ -66,10 +66,10 @@ inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) vo
inline fn subborrowxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @subWithOverflow(u64, arg2, arg3, &t);
const carry2 = @subWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @subWithOverflow(arg2, arg3);
const ov2 = @subWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function mulxU64 is a multiplication, returning the full double-width result.
Expand Down
16 changes: 8 additions & 8 deletions lib/std/crypto/pcurves/secp256k1/secp256k1_scalar_64.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ pub const NonMontgomeryDomainFieldElement = [4]u64;
inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @addWithOverflow(u64, arg2, arg3, &t);
const carry2 = @addWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @addWithOverflow(arg2, arg3);
const ov2 = @addWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function subborrowxU64 is a subtraction with borrow.
Expand All @@ -66,10 +66,10 @@ inline fn addcarryxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) vo
inline fn subborrowxU64(out1: *u64, out2: *u1, arg1: u1, arg2: u64, arg3: u64) void {
@setRuntimeSafety(mode == .Debug);

var t: u64 = undefined;
const carry1 = @subWithOverflow(u64, arg2, arg3, &t);
const carry2 = @subWithOverflow(u64, t, arg1, out1);
out2.* = @boolToInt(carry1) | @boolToInt(carry2);
const ov1 = @subWithOverflow(arg2, arg3);
const ov2 = @subWithOverflow(ov1[0], arg1);
out1.* = ov2[0];
out2.* = ov1[1] | ov2[1];
}

/// The function mulxU64 is a multiplication, returning the full double-width result.
Expand Down
4 changes: 3 additions & 1 deletion lib/std/crypto/salsa20.zig
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ fn SalsaNonVecImpl(comptime rounds: comptime_int) type {
while (j < 64) : (j += 1) {
xout[j] ^= buf[j];
}
ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8]));
const ov = @addWithOverflow(ctx[8], 1);
ctx[8] = ov[0];
ctx[9] += ov[1];
}
if (i < in.len) {
salsaCore(x[0..], ctx, true);
Expand Down
24 changes: 16 additions & 8 deletions lib/std/crypto/utils.zig
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,19 @@ pub fn timingSafeAdd(comptime T: type, a: []const T, b: []const T, result: []T,
if (endian == .Little) {
var i: usize = 0;
while (i < len) : (i += 1) {
const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i]));
carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i]));
const ov1 = @addWithOverflow(a[i], b[i]);
const ov2 = @addWithOverflow(ov1[0], carry);
result[i] = ov2[0];
carry = ov1[1] | ov2[1];
}
} else {
var i: usize = len;
while (i != 0) {
i -= 1;
const tmp = @boolToInt(@addWithOverflow(u8, a[i], b[i], &result[i]));
carry = tmp | @boolToInt(@addWithOverflow(u8, result[i], carry, &result[i]));
const ov1 = @addWithOverflow(a[i], b[i]);
const ov2 = @addWithOverflow(ov1[0], carry);
result[i] = ov2[0];
carry = ov1[1] | ov2[1];
}
}
return @bitCast(bool, carry);
Expand All @@ -110,15 +114,19 @@ pub fn timingSafeSub(comptime T: type, a: []const T, b: []const T, result: []T,
if (endian == .Little) {
var i: usize = 0;
while (i < len) : (i += 1) {
const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i]));
borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i]));
const ov1 = @subWithOverflow(a[i], b[i]);
const ov2 = @subWithOverflow(ov1[0], borrow);
result[i] = ov2[0];
borrow = ov1[1] | ov2[1];
}
} else {
var i: usize = len;
while (i != 0) {
i -= 1;
const tmp = @boolToInt(@subWithOverflow(u8, a[i], b[i], &result[i]));
borrow = tmp | @boolToInt(@subWithOverflow(u8, result[i], borrow, &result[i]));
const ov1 = @subWithOverflow(a[i], b[i]);
const ov2 = @subWithOverflow(ov1[0], borrow);
result[i] = ov2[0];
borrow = ov1[1] | ov2[1];
}
}
return @bitCast(bool, borrow);
Expand Down
Loading

0 comments on commit 19056cb

Please sign in to comment.