authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-10-16 15:06:13-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-10-16 15:06:13-04:00
log82ec56e47e004176cc380cc69764602c4a8d0768
tree311a130761e959a49953d57bbef4d0eff40b025b
parent6f30c8c098fcbf52f4a78e662c89508997945e8a
parent1e09157b53441d06cd1f49b9c3917a58ee244cb1
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #9954 from Snektron/shifts

Big int saturating left shift

5 files changed, 232 insertions(+), 38 deletions(-)

lib/std/math/big/int.zig+101-32
......@@ -18,18 +18,12 @@ const debug_safety = false;
1818/// Returns the number of limbs needed to store `scalar`, which must be a
1919/// primitive integer value.
2020pub fn calcLimbLen(scalar: anytype) usize {
21 const T = @TypeOf(scalar);
22 switch (@typeInfo(T)) {
23 .Int => |info| {
24 const UT = if (info.signedness == .signed) std.meta.Int(.unsigned, info.bits - 1) else T;
25 return @sizeOf(UT) / @sizeOf(Limb);
26 },
27 .ComptimeInt => {
28 const w_value = if (scalar < 0) -scalar else scalar;
29 return @divFloor(math.log2(w_value), limb_bits) + 1;
30 },
31 else => @compileError("parameter must be a primitive integer type"),
21 if (scalar == 0) {
22 return 1;
3223 }
24
25 const w_value = std.math.absCast(scalar);
26 return @divFloor(@intCast(Limb, math.log2(w_value)), limb_bits) + 1;
3327}
3428
3529pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
......@@ -218,26 +212,22 @@ pub const Mutable = struct {
218212 /// needs to be to store a specific value.
219213 pub fn set(self: *Mutable, value: anytype) void {
220214 const T = @TypeOf(value);
215 const needed_limbs = calcLimbLen(value);
216 assert(needed_limbs <= self.limbs.len); // value too big
217
218 self.len = needed_limbs;
219 self.positive = value >= 0;
221220
222221 switch (@typeInfo(T)) {
223222 .Int => |info| {
224 const UT = if (info.signedness == .signed) std.meta.Int(.unsigned, info.bits - 1) else T;
225
226 const needed_limbs = @sizeOf(UT) / @sizeOf(Limb);
227 assert(needed_limbs <= self.limbs.len); // value too big
228 self.len = 0;
229 self.positive = value >= 0;
230
231 var w_value: UT = if (value < 0) @intCast(UT, -value) else @intCast(UT, value);
223 var w_value = std.math.absCast(value);
232224
233225 if (info.bits <= limb_bits) {
234 self.limbs[0] = @as(Limb, w_value);
235 self.len += 1;
226 self.limbs[0] = w_value;
236227 } else {
237228 var i: usize = 0;
238229 while (w_value != 0) : (i += 1) {
239230 self.limbs[i] = @truncate(Limb, w_value);
240 self.len += 1;
241231
242232 // TODO: shift == 64 at compile-time fails. Fails on u128 limbs.
243233 w_value >>= limb_bits / 2;
......@@ -246,13 +236,7 @@ pub const Mutable = struct {
246236 }
247237 },
248238 .ComptimeInt => {
249 comptime var w_value = if (value < 0) -value else value;
250
251 const req_limbs = @divFloor(math.log2(w_value), limb_bits) + 1;
252 assert(req_limbs <= self.limbs.len); // value too big
253
254 self.len = req_limbs;
255 self.positive = value >= 0;
239 comptime var w_value = std.math.absCast(value);
256240
257241 if (w_value <= maxInt(Limb)) {
258242 self.limbs[0] = w_value;
......@@ -835,6 +819,75 @@ pub const Mutable = struct {
835819 r.positive = a.positive;
836820 }
837821
822 /// r = a <<| shift with 2s-complement saturating semantics.
823 ///
824 /// r and a may alias.
825 ///
826 /// Asserts there is enough memory to fit the result. The upper bound Limb count is
827 /// r is `calcTwosCompLimbCount(bit_count)`.
828 pub fn shiftLeftSat(r: *Mutable, a: Const, shift: usize, signedness: std.builtin.Signedness, bit_count: usize) void {
829 // Special case: When the argument is negative, but the result is supposed to be unsigned,
830 // return 0 in all cases.
831 if (!a.positive and signedness == .unsigned) {
832 r.set(0);
833 return;
834 }
835
836 // Check whether the shift is going to overflow. This is the case
837 // when (in 2s complement) any bit above `bit_count - shift` is set in the unshifted value.
838 // Note, the sign bit is not counted here.
839
840 // Handle shifts larger than the target type. This also deals with
841 // 0-bit integers.
842 if (bit_count <= shift) {
843 // In this case, there is only no overflow if `a` is zero.
844 if (a.eqZero()) {
845 r.set(0);
846 } else {
847 r.setTwosCompIntLimit(if (a.positive) .max else .min, signedness, bit_count);
848 }
849 return;
850 }
851
852 const checkbit = bit_count - shift - @boolToInt(signedness == .signed);
853 // If `checkbit` and more significant bits are zero, no overflow will take place.
854
855 if (checkbit >= a.limbs.len * limb_bits) {
856 // `checkbit` is outside the range of a, so definitely no overflow will take place. We
857 // can defer to a normal shift.
858 // Note that if `a` is normalized (which we assume), this checks for set bits in the upper limbs.
859
860 // Note, in this case r should already have enough limbs required to perform the normal shift.
861 // In this case the shift of the most significant limb may still overflow.
862 r.shiftLeft(a, shift);
863 return;
864 } else if (checkbit < (a.limbs.len - 1) * limb_bits) {
865 // `checkbit` is not in the most significant limb. If `a` is normalized the most significant
866 // limb will not be zero, so in this case we need to saturate. Note that `a.limbs.len` must be
867 // at least one according to normalization rules.
868
869 r.setTwosCompIntLimit(if (a.positive) .max else .min, signedness, bit_count);
870 return;
871 }
872
873 // Generate a mask with the bits to check in the most signficant limb. We'll need to check
874 // all bits with equal or more significance than checkbit.
875 // const msb = @truncate(Log2Limb, checkbit);
876 // const checkmask = (@as(Limb, 1) << msb) -% 1;
877
878 if (a.limbs[a.limbs.len - 1] >> @truncate(Log2Limb, checkbit) != 0) {
879 // Need to saturate.
880 r.setTwosCompIntLimit(if (a.positive) .max else .min, signedness, bit_count);
881 return;
882 }
883
884 // This shift should not be able to overflow, so invoke llshl and normalize manually
885 // to avoid the extra required limb.
886 llshl(r.limbs[0..], a.limbs[0..a.limbs.len], shift);
887 r.normalize(a.limbs.len + (shift / limb_bits));
888 r.positive = a.positive;
889 }
890
838891 /// r = a >> shift
839892 /// r and a may alias.
840893 ///
......@@ -2401,6 +2454,14 @@ pub const Managed = struct {
24012454 r.setMetadata(m.positive, m.len);
24022455 }
24032456
2457 /// r = a <<| shift with 2s-complement saturating semantics.
2458 pub fn shiftLeftSat(r: *Managed, a: Managed, shift: usize, signedness: std.builtin.Signedness, bit_count: usize) !void {
2459 try r.ensureTwosCompCapacity(bit_count);
2460 var m = r.toMutable();
2461 m.shiftLeftSat(a.toConst(), shift, signedness, bit_count);
2462 r.setMetadata(m.positive, m.len);
2463 }
2464
24042465 /// r = a >> shift
24052466 pub fn shiftRight(r: *Managed, a: Managed, shift: usize) !void {
24062467 if (a.len() <= shift / limb_bits) {
......@@ -2949,10 +3010,18 @@ fn lldiv1(quo: []Limb, rem: *Limb, a: []const Limb, b: Limb) void {
29493010fn llshl(r: []Limb, a: []const Limb, shift: usize) void {
29503011 @setRuntimeSafety(debug_safety);
29513012 assert(a.len >= 1);
2952 assert(r.len >= a.len + (shift / limb_bits) + 1);
3013
3014 const interior_limb_shift = @truncate(Log2Limb, shift);
3015
3016 // We only need the extra limb if the shift of the last element overflows.
3017 // This is useful for the implementation of `shiftLeftSat`.
3018 if (a[a.len - 1] << interior_limb_shift >> interior_limb_shift != a[a.len - 1]) {
3019 assert(r.len >= a.len + (shift / limb_bits) + 1);
3020 } else {
3021 assert(r.len >= a.len + (shift / limb_bits));
3022 }
29533023
29543024 const limb_shift = shift / limb_bits + 1;
2955 const interior_limb_shift = @intCast(Log2Limb, shift % limb_bits);
29563025
29573026 var carry: Limb = 0;
29583027 var i: usize = 0;
......@@ -2979,7 +3048,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {
29793048 assert(r.len >= a.len - (shift / limb_bits));
29803049
29813050 const limb_shift = shift / limb_bits;
2982 const interior_limb_shift = @intCast(Log2Limb, shift % limb_bits);
3051 const interior_limb_shift = @truncate(Log2Limb, shift);
29833052
29843053 var carry: Limb = 0;
29853054 var i: usize = 0;
lib/std/math/big/int_test.zig+93
......@@ -61,6 +61,13 @@ test "big.int sub-limb to" {
6161 try testing.expect((try a.to(u8)) == 10);
6262}
6363
64test "big.int set negative minimum" {
65 var a = try Managed.initSet(testing.allocator, @as(i64, minInt(i64)));
66 defer a.deinit();
67
68 try testing.expect((try a.to(i64)) == minInt(i64));
69}
70
6471test "big.int to target too small error" {
6572 var a = try Managed.initSet(testing.allocator, 0xffffffff);
6673 defer a.deinit();
......@@ -1773,6 +1780,92 @@ test "big.int shift-left negative" {
17731780 try testing.expect((try a.to(i32)) == -10 >> 1232);
17741781}
17751782
1783test "big.int sat shift-left simple unsigned" {
1784 var a = try Managed.initSet(testing.allocator, 0xffff);
1785 defer a.deinit();
1786 try a.shiftLeftSat(a, 16, .unsigned, 21);
1787
1788 try testing.expect((try a.to(u64)) == 0x1fffff);
1789}
1790
1791test "big.int sat shift-left simple unsigned no sat" {
1792 var a = try Managed.initSet(testing.allocator, 1);
1793 defer a.deinit();
1794 try a.shiftLeftSat(a, 16, .unsigned, 21);
1795
1796 try testing.expect((try a.to(u64)) == 0x10000);
1797}
1798
1799test "big.int sat shift-left multi unsigned" {
1800 var a = try Managed.initSet(testing.allocator, 16);
1801 defer a.deinit();
1802 try a.shiftLeftSat(a, @bitSizeOf(DoubleLimb) - 3, .unsigned, @bitSizeOf(DoubleLimb) - 1);
1803
1804 try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) >> 1);
1805}
1806
1807test "big.int sat shift-left unsigned shift > bitcount" {
1808 var a = try Managed.initSet(testing.allocator, 1);
1809 defer a.deinit();
1810 try a.shiftLeftSat(a, 10, .unsigned, 10);
1811
1812 try testing.expect((try a.to(u10)) == maxInt(u10));
1813}
1814
1815test "big.int sat shift-left unsigned zero" {
1816 var a = try Managed.initSet(testing.allocator, 0);
1817 defer a.deinit();
1818 try a.shiftLeftSat(a, 1, .unsigned, 0);
1819
1820 try testing.expect((try a.to(u64)) == 0);
1821}
1822
1823test "big.int sat shift-left unsigned negative" {
1824 var a = try Managed.initSet(testing.allocator, -100);
1825 defer a.deinit();
1826 try a.shiftLeftSat(a, 0, .unsigned, 0);
1827
1828 try testing.expect((try a.to(u64)) == 0);
1829}
1830
1831test "big.int sat shift-left signed simple negative" {
1832 var a = try Managed.initSet(testing.allocator, -100);
1833 defer a.deinit();
1834 try a.shiftLeftSat(a, 3, .signed, 10);
1835
1836 try testing.expect((try a.to(i10)) == minInt(i10));
1837}
1838
1839test "big.int sat shift-left signed simple positive" {
1840 var a = try Managed.initSet(testing.allocator, 100);
1841 defer a.deinit();
1842 try a.shiftLeftSat(a, 3, .signed, 10);
1843
1844 try testing.expect((try a.to(i10)) == maxInt(i10));
1845}
1846
1847test "big.int sat shift-left signed multi positive" {
1848 const x = 1;
1849 const shift = @bitSizeOf(SignedDoubleLimb) - 1;
1850
1851 var a = try Managed.initSet(testing.allocator, x);
1852 defer a.deinit();
1853 try a.shiftLeftSat(a, shift, .signed, @bitSizeOf(SignedDoubleLimb));
1854
1855 try testing.expect((try a.to(SignedDoubleLimb)) == @as(SignedDoubleLimb, x) <<| shift);
1856}
1857
1858test "big.int sat shift-left signed multi negative" {
1859 const x = -1;
1860 const shift = @bitSizeOf(SignedDoubleLimb) - 1;
1861
1862 var a = try Managed.initSet(testing.allocator, x);
1863 defer a.deinit();
1864 try a.shiftLeftSat(a, shift, .signed, @bitSizeOf(SignedDoubleLimb));
1865
1866 try testing.expect((try a.to(SignedDoubleLimb)) == @as(SignedDoubleLimb, x) <<| shift);
1867}
1868
17761869test "big.int bitwise and simple" {
17771870 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
17781871 defer a.deinit();
src/Sema.zig+5-5
......@@ -6502,13 +6502,13 @@ fn zirShl(
65026502 if (rhs_val.compareWithZero(.eq)) {
65036503 return sema.addConstant(lhs_ty, lhs_val);
65046504 }
6505 const val = try lhs_val.shl(rhs_val, sema.arena);
6506 switch (air_tag) {
6505 const val = switch (air_tag) {
65076506 .shl_exact => return sema.fail(block, lhs_src, "TODO implement Sema for comptime shl_exact", .{}),
6508 .shl_sat => return sema.fail(block, lhs_src, "TODO implement Sema for comptime shl_sat", .{}),
6509 .shl => {},
6507 .shl_sat => try lhs_val.shlSat(rhs_val, lhs_ty, sema.arena, sema.mod.getTarget()),
6508 .shl => try lhs_val.shl(rhs_val, sema.arena),
65106509 else => unreachable,
6511 }
6510 };
6511
65126512 return sema.addConstant(lhs_ty, val);
65136513 } else rs: {
65146514 if (maybe_rhs_val) |rhs_val| {
src/value.zig+33
......@@ -2404,6 +2404,39 @@ pub const Value = extern union {
24042404 }
24052405 }
24062406
2407 pub fn shlSat(
2408 lhs: Value,
2409 rhs: Value,
2410 ty: Type,
2411 arena: *Allocator,
2412 target: Target,
2413 ) !Value {
2414 // TODO is this a performance issue? maybe we should try the operation without
2415 // resorting to BigInt first.
2416 const info = ty.intInfo(target);
2417
2418 var lhs_space: Value.BigIntSpace = undefined;
2419 const lhs_bigint = lhs.toBigInt(&lhs_space);
2420 const shift = rhs.toUnsignedInt();
2421 const limbs = try arena.alloc(
2422 std.math.big.Limb,
2423 std.math.big.int.calcTwosCompLimbCount(info.bits),
2424 );
2425 var result_bigint = BigIntMutable{
2426 .limbs = limbs,
2427 .positive = undefined,
2428 .len = undefined,
2429 };
2430 result_bigint.shiftLeftSat(lhs_bigint, shift, info.signedness, info.bits);
2431 const result_limbs = result_bigint.limbs[0..result_bigint.len];
2432
2433 if (result_bigint.positive) {
2434 return Value.Tag.int_big_positive.create(arena, result_limbs);
2435 } else {
2436 return Value.Tag.int_big_negative.create(arena, result_limbs);
2437 }
2438 }
2439
24072440 pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
24082441 // TODO is this a performance issue? maybe we should try the operation without
24092442 // resorting to BigInt first.
test/behavior.zig-1
......@@ -146,7 +146,6 @@ test {
146146 {
147147 // Checklist for getting saturating_arithmetic.zig passing for stage2:
148148 // * add __muloti4 to compiler-rt
149 // * implement comptime saturating shift-left
150149 _ = @import("behavior/saturating_arithmetic.zig");
151150 }
152151 _ = @import("behavior/shuffle.zig");