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
signaturebadge-question-mark 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;...@@ -18,18 +18,12 @@ const debug_safety = false;
18/// Returns the number of limbs needed to store `scalar`, which must be a18/// Returns the number of limbs needed to store `scalar`, which must be a
19/// primitive integer value.19/// primitive integer value.
20pub fn calcLimbLen(scalar: anytype) usize {20pub fn calcLimbLen(scalar: anytype) usize {
21 const T = @TypeOf(scalar);21 if (scalar == 0) {
22 switch (@typeInfo(T)) {22 return 1;
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"),
32 }23 }
24
25 const w_value = std.math.absCast(scalar);
26 return @divFloor(@intCast(Limb, math.log2(w_value)), limb_bits) + 1;
33}27}
3428
35pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {29pub fn calcToStringLimbsBufferLen(a_len: usize, base: u8) usize {
...@@ -218,26 +212,22 @@ pub const Mutable = struct {...@@ -218,26 +212,22 @@ pub const Mutable = struct {
218 /// needs to be to store a specific value.212 /// needs to be to store a specific value.
219 pub fn set(self: *Mutable, value: anytype) void {213 pub fn set(self: *Mutable, value: anytype) void {
220 const T = @TypeOf(value);214 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
222 switch (@typeInfo(T)) {221 switch (@typeInfo(T)) {
223 .Int => |info| {222 .Int => |info| {
224 const UT = if (info.signedness == .signed) std.meta.Int(.unsigned, info.bits - 1) else T;223 var w_value = std.math.absCast(value);
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);
232224
233 if (info.bits <= limb_bits) {225 if (info.bits <= limb_bits) {
234 self.limbs[0] = @as(Limb, w_value);226 self.limbs[0] = w_value;
235 self.len += 1;
236 } else {227 } else {
237 var i: usize = 0;228 var i: usize = 0;
238 while (w_value != 0) : (i += 1) {229 while (w_value != 0) : (i += 1) {
239 self.limbs[i] = @truncate(Limb, w_value);230 self.limbs[i] = @truncate(Limb, w_value);
240 self.len += 1;
241231
242 // TODO: shift == 64 at compile-time fails. Fails on u128 limbs.232 // TODO: shift == 64 at compile-time fails. Fails on u128 limbs.
243 w_value >>= limb_bits / 2;233 w_value >>= limb_bits / 2;
...@@ -246,13 +236,7 @@ pub const Mutable = struct {...@@ -246,13 +236,7 @@ pub const Mutable = struct {
246 }236 }
247 },237 },
248 .ComptimeInt => {238 .ComptimeInt => {
249 comptime var w_value = if (value < 0) -value else value;239 comptime var w_value = std.math.absCast(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;
256240
257 if (w_value <= maxInt(Limb)) {241 if (w_value <= maxInt(Limb)) {
258 self.limbs[0] = w_value;242 self.limbs[0] = w_value;
...@@ -835,6 +819,75 @@ pub const Mutable = struct {...@@ -835,6 +819,75 @@ pub const Mutable = struct {
835 r.positive = a.positive;819 r.positive = a.positive;
836 }820 }
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
838 /// r = a >> shift891 /// r = a >> shift
839 /// r and a may alias.892 /// r and a may alias.
840 ///893 ///
...@@ -2401,6 +2454,14 @@ pub const Managed = struct {...@@ -2401,6 +2454,14 @@ pub const Managed = struct {
2401 r.setMetadata(m.positive, m.len);2454 r.setMetadata(m.positive, m.len);
2402 }2455 }
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
2404 /// r = a >> shift2465 /// r = a >> shift
2405 pub fn shiftRight(r: *Managed, a: Managed, shift: usize) !void {2466 pub fn shiftRight(r: *Managed, a: Managed, shift: usize) !void {
2406 if (a.len() <= shift / limb_bits) {2467 if (a.len() <= shift / limb_bits) {
...@@ -2949,10 +3010,18 @@ fn lldiv1(quo: []Limb, rem: *Limb, a: []const Limb, b: Limb) void {...@@ -2949,10 +3010,18 @@ fn lldiv1(quo: []Limb, rem: *Limb, a: []const Limb, b: Limb) void {
2949fn llshl(r: []Limb, a: []const Limb, shift: usize) void {3010fn llshl(r: []Limb, a: []const Limb, shift: usize) void {
2950 @setRuntimeSafety(debug_safety);3011 @setRuntimeSafety(debug_safety);
2951 assert(a.len >= 1);3012 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
2954 const limb_shift = shift / limb_bits + 1;3024 const limb_shift = shift / limb_bits + 1;
2955 const interior_limb_shift = @intCast(Log2Limb, shift % limb_bits);
29563025
2957 var carry: Limb = 0;3026 var carry: Limb = 0;
2958 var i: usize = 0;3027 var i: usize = 0;
...@@ -2979,7 +3048,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {...@@ -2979,7 +3048,7 @@ fn llshr(r: []Limb, a: []const Limb, shift: usize) void {
2979 assert(r.len >= a.len - (shift / limb_bits));3048 assert(r.len >= a.len - (shift / limb_bits));
29803049
2981 const limb_shift = shift / limb_bits;3050 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
2984 var carry: Limb = 0;3053 var carry: Limb = 0;
2985 var i: usize = 0;3054 var i: usize = 0;
lib/std/math/big/int_test.zig+93
...@@ -61,6 +61,13 @@ test "big.int sub-limb to" {...@@ -61,6 +61,13 @@ test "big.int sub-limb to" {
61 try testing.expect((try a.to(u8)) == 10);61 try testing.expect((try a.to(u8)) == 10);
62}62}
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
64test "big.int to target too small error" {71test "big.int to target too small error" {
65 var a = try Managed.initSet(testing.allocator, 0xffffffff);72 var a = try Managed.initSet(testing.allocator, 0xffffffff);
66 defer a.deinit();73 defer a.deinit();
...@@ -1773,6 +1780,92 @@ test "big.int shift-left negative" {...@@ -1773,6 +1780,92 @@ test "big.int shift-left negative" {
1773 try testing.expect((try a.to(i32)) == -10 >> 1232);1780 try testing.expect((try a.to(i32)) == -10 >> 1232);
1774}1781}
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
1776test "big.int bitwise and simple" {1869test "big.int bitwise and simple" {
1777 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);1870 var a = try Managed.initSet(testing.allocator, 0xffffffff11111111);
1778 defer a.deinit();1871 defer a.deinit();
src/Sema.zig+5-5
...@@ -6502,13 +6502,13 @@ fn zirShl(...@@ -6502,13 +6502,13 @@ fn zirShl(
6502 if (rhs_val.compareWithZero(.eq)) {6502 if (rhs_val.compareWithZero(.eq)) {
6503 return sema.addConstant(lhs_ty, lhs_val);6503 return sema.addConstant(lhs_ty, lhs_val);
6504 }6504 }
6505 const val = try lhs_val.shl(rhs_val, sema.arena);6505 const val = switch (air_tag) {
6506 switch (air_tag) {
6507 .shl_exact => return sema.fail(block, lhs_src, "TODO implement Sema for comptime shl_exact", .{}),6506 .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", .{}),6507 .shl_sat => try lhs_val.shlSat(rhs_val, lhs_ty, sema.arena, sema.mod.getTarget()),
6509 .shl => {},6508 .shl => try lhs_val.shl(rhs_val, sema.arena),
6510 else => unreachable,6509 else => unreachable,
6511 }6510 };
6511
6512 return sema.addConstant(lhs_ty, val);6512 return sema.addConstant(lhs_ty, val);
6513 } else rs: {6513 } else rs: {
6514 if (maybe_rhs_val) |rhs_val| {6514 if (maybe_rhs_val) |rhs_val| {
src/value.zig+33
...@@ -2404,6 +2404,39 @@ pub const Value = extern union {...@@ -2404,6 +2404,39 @@ pub const Value = extern union {
2404 }2404 }
2405 }2405 }
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
2407 pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {2440 pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
2408 // TODO is this a performance issue? maybe we should try the operation without2441 // TODO is this a performance issue? maybe we should try the operation without
2409 // resorting to BigInt first.2442 // resorting to BigInt first.
test/behavior.zig-1
...@@ -146,7 +146,6 @@ test {...@@ -146,7 +146,6 @@ test {
146 {146 {
147 // Checklist for getting saturating_arithmetic.zig passing for stage2:147 // Checklist for getting saturating_arithmetic.zig passing for stage2:
148 // * add __muloti4 to compiler-rt148 // * add __muloti4 to compiler-rt
149 // * implement comptime saturating shift-left
150 _ = @import("behavior/saturating_arithmetic.zig");149 _ = @import("behavior/saturating_arithmetic.zig");
151 }150 }
152 _ = @import("behavior/shuffle.zig");151 _ = @import("behavior/shuffle.zig");