Commit afed992b authored by Jakob Kummerow's avatar Jakob Kummerow Committed by V8 LUCI CQ

[bigint] Move asIntN/asUintN to src/bigint/

No algorithmic changes, just cleaning up.

Bug: v8:11515
Change-Id: Ib173713a1191d443faf2aebbcc31ff7608823436
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3151957Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#76920}
parent 159e440d
......@@ -254,6 +254,14 @@ void BitwiseXor_PosPos(RWDigits Z, Digits X, Digits Y);
void BitwiseXor_NegNeg(RWDigits Z, Digits X, Digits Y);
void BitwiseXor_PosNeg(RWDigits Z, Digits X, Digits Y);
// Z := (least significant n bits of X, interpreted as a signed n-bit integer).
// Returns true if the result is negative; Z will hold the absolute value.
bool AsIntN(RWDigits Z, Digits X, bool x_negative, int n);
// Z := (least significant n bits of X).
void AsUintN_Pos(RWDigits Z, Digits X, int n);
// Same, but X is the absolute value of a negative BigInt.
void AsUintN_Neg(RWDigits Z, Digits X, int n);
enum class Status { kOk, kInterrupted };
class FromStringAccumulator;
......@@ -345,6 +353,14 @@ inline int BitwiseXor_PosNeg_ResultLength(int x_length, int y_length) {
return std::max(x_length, y_length) + 1;
}
// Returns -1 if this "asIntN" operation would be a no-op.
int AsIntNResultLength(Digits X, bool x_negative, int n);
// Returns -1 if this "asUintN" operation would be a no-op.
int AsUintN_Pos_ResultLength(Digits X, int n);
inline int AsUintN_Neg_ResultLength(int n) {
return ((n - 1) / kDigitBits) + 1;
}
// Support for parsing BigInts from Strings, using an Accumulator object
// for intermediate state.
......
......@@ -4,6 +4,7 @@
#include "src/bigint/bigint-internal.h"
#include "src/bigint/digit-arithmetic.h"
#include "src/bigint/util.h"
#include "src/bigint/vector-arithmetic.h"
namespace v8 {
......@@ -132,5 +133,130 @@ void BitwiseXor_PosNeg(RWDigits Z, Digits X, Digits Y) {
Add(Z, 1);
}
namespace {
// Z := (least significant n bits of X).
void TruncateToNBits(RWDigits Z, Digits X, int n) {
int digits = DIV_CEIL(n, kDigitBits);
int bits = n % kDigitBits;
// Copy all digits except the MSD.
int last = digits - 1;
for (int i = 0; i < last; i++) {
Z[i] = X[i];
}
// The MSD might contain extra bits that we don't want.
digit_t msd = X[last];
if (bits != 0) {
int drop = kDigitBits - bits;
msd = (msd << drop) >> drop;
}
Z[last] = msd;
}
// Z := 2**n - (least significant n bits of X).
void TruncateAndSubFromPowerOfTwo(RWDigits Z, Digits X, int n) {
int digits = DIV_CEIL(n, kDigitBits);
int bits = n % kDigitBits;
// Process all digits except the MSD. Take X's digits, then simulate leading
// zeroes.
int last = digits - 1;
int have_x = std::min(last, X.len());
digit_t borrow = 0;
int i = 0;
for (; i < have_x; i++) Z[i] = digit_sub2(0, X[i], borrow, &borrow);
for (; i < last; i++) Z[i] = digit_sub(0, borrow, &borrow);
// The MSD might contain extra bits that we don't want.
digit_t msd = last < X.len() ? X[last] : 0;
if (bits == 0) {
Z[last] = digit_sub2(0, msd, borrow, &borrow);
} else {
int drop = kDigitBits - bits;
msd = (msd << drop) >> drop;
digit_t minuend_msd = static_cast<digit_t>(1) << bits;
digit_t result_msd = digit_sub2(minuend_msd, msd, borrow, &borrow);
DCHECK(borrow == 0); // result < 2^n. NOLINT(readability/check)
// If all subtracted bits were zero, we have to get rid of the
// materialized minuend_msd again.
Z[last] = result_msd & (minuend_msd - 1);
}
}
} // namespace
// Returns -1 when the operation would return X unchanged.
int AsIntNResultLength(Digits X, bool x_negative, int n) {
int needed_digits = DIV_CEIL(n, kDigitBits);
// Generally: decide based on number of digits, and bits in the top digit.
if (X.len() < needed_digits) return -1;
if (X.len() > needed_digits) return needed_digits;
digit_t top_digit = X[needed_digits - 1];
digit_t compare_digit = digit_t{1} << ((n - 1) % kDigitBits);
if (top_digit < compare_digit) return -1;
if (top_digit > compare_digit) return needed_digits;
// Special case: if X == -2**(n-1), truncation is a no-op.
if (!x_negative) return needed_digits;
for (int i = needed_digits - 2; i >= 0; i--) {
if (X[i] != 0) return needed_digits;
}
return -1;
}
bool AsIntN(RWDigits Z, Digits X, bool x_negative, int n) {
DCHECK(X.len() > 0); // NOLINT(readability/check)
DCHECK(n > 0); // NOLINT(readability/check)
// NOLINTNEXTLINE(readability/check)
DCHECK(AsIntNResultLength(X, x_negative, n) > 0);
int needed_digits = DIV_CEIL(n, kDigitBits);
digit_t top_digit = X[needed_digits - 1];
digit_t compare_digit = digit_t{1} << ((n - 1) % kDigitBits);
// The canonical algorithm would be: convert negative numbers to two's
// complement representation, truncate, convert back to sign+magnitude. To
// avoid the conversions, we predict what the result would be:
// When the (n-1)th bit is not set:
// - truncate the absolute value
// - preserve the sign.
// When the (n-1)th bit is set:
// - subtract the truncated absolute value from 2**n to simulate two's
// complement representation
// - flip the sign, unless it's the special case where the input is negative
// and the result is the minimum n-bit integer. E.g. asIntN(3, -12) => -4.
bool has_bit = (top_digit & compare_digit) == compare_digit;
if (!has_bit) {
TruncateToNBits(Z, X, n);
return x_negative;
}
TruncateAndSubFromPowerOfTwo(Z, X, n);
if (!x_negative) return true; // Result is negative.
// Scan for the special case (see above): if all bits below the (n-1)th
// digit are zero, the result is negative.
if ((top_digit & (compare_digit - 1)) != 0) return false;
for (int i = needed_digits - 2; i >= 0; i--) {
if (X[i] != 0) return false;
}
return true;
}
// Returns -1 when the operation would return X unchanged.
int AsUintN_Pos_ResultLength(Digits X, int n) {
int needed_digits = DIV_CEIL(n, kDigitBits);
if (X.len() < needed_digits) return -1;
if (X.len() > needed_digits) return needed_digits;
int bits_in_top_digit = n % kDigitBits;
if (bits_in_top_digit == 0) return -1;
digit_t top_digit = X[needed_digits - 1];
if ((top_digit >> bits_in_top_digit) == 0) return -1;
return needed_digits;
}
void AsUintN_Pos(RWDigits Z, Digits X, int n) {
DCHECK(AsUintN_Pos_ResultLength(X, n) > 0); // NOLINT(readability/check)
TruncateToNBits(Z, X, n);
}
void AsUintN_Neg(RWDigits Z, Digits X, int n) {
TruncateAndSubFromPowerOfTwo(Z, X, n);
}
} // namespace bigint
} // namespace v8
......@@ -81,12 +81,6 @@ class MutableBigInt : public FreshlyAllocatedBigInt {
}
// Internal helpers.
static Handle<BigInt> TruncateToNBits(Isolate* isolate, int n,
Handle<BigInt> x);
static Handle<BigInt> TruncateAndSubFromPowerOfTwo(Isolate* isolate, int n,
Handle<BigInt> x,
bool result_sign);
static MaybeHandle<MutableBigInt> AbsoluteAddOne(
Isolate* isolate, Handle<BigIntBase> x, bool sign,
MutableBigInt result_storage = MutableBigInt());
......@@ -112,8 +106,6 @@ class MutableBigInt : public FreshlyAllocatedBigInt {
// representation.
static uint64_t GetRawBits(BigIntBase x, bool* lossless);
// Digit arithmetic helpers.
static inline digit_t digit_sub(digit_t a, digit_t b, digit_t* borrow);
static inline bool digit_ismax(digit_t x) {
return static_cast<digit_t>(~x) == 0;
}
......@@ -1490,160 +1482,41 @@ MaybeHandle<BigInt> BigInt::FromSerializedDigits(
}
Handle<BigInt> BigInt::AsIntN(Isolate* isolate, uint64_t n, Handle<BigInt> x) {
if (x->is_zero()) return x;
if (x->is_zero() || n > kMaxLengthBits) return x;
if (n == 0) return MutableBigInt::Zero(isolate);
uint64_t needed_length = (n + kDigitBits - 1) / kDigitBits;
uint64_t x_length = static_cast<uint64_t>(x->length());
// If {x} has less than {n} bits, return it directly.
if (x_length < needed_length) return x;
DCHECK_LE(needed_length, kMaxInt);
digit_t top_digit = x->digit(static_cast<int>(needed_length) - 1);
digit_t compare_digit = static_cast<digit_t>(1) << ((n - 1) % kDigitBits);
if (x_length == needed_length && top_digit < compare_digit) return x;
// Otherwise we have to truncate (which is a no-op in the special case
// of x == -2^(n-1)), and determine the right sign. We also might have
// to subtract from 2^n to simulate having two's complement representation.
// In most cases, the result's sign is x->sign() xor "(n-1)th bit present".
// The only exception is when x is negative, has the (n-1)th bit, and all
// its bits below (n-1) are zero. In that case, the result is the minimum
// n-bit integer (example: asIntN(3, -12n) => -4n).
bool has_bit = (top_digit & compare_digit) == compare_digit;
DCHECK_LE(n, kMaxInt);
int N = static_cast<int>(n);
if (!has_bit) {
return MutableBigInt::TruncateToNBits(isolate, N, x);
}
if (!x->sign()) {
return MutableBigInt::TruncateAndSubFromPowerOfTwo(isolate, N, x, true);
}
// Negative numbers must subtract from 2^n, except for the special case
// described above.
if ((top_digit & (compare_digit - 1)) == 0) {
for (int i = static_cast<int>(needed_length) - 2; i >= 0; i--) {
if (x->digit(i) != 0) {
return MutableBigInt::TruncateAndSubFromPowerOfTwo(isolate, N, x,
false);
}
}
// Truncation is no-op if x == -2^(n-1).
if (x_length == needed_length && top_digit == compare_digit) return x;
return MutableBigInt::TruncateToNBits(isolate, N, x);
}
return MutableBigInt::TruncateAndSubFromPowerOfTwo(isolate, N, x, false);
int needed_length =
bigint::AsIntNResultLength(GetDigits(x), x->sign(), static_cast<int>(n));
if (needed_length == -1) return x;
Handle<MutableBigInt> result =
MutableBigInt::New(isolate, needed_length).ToHandleChecked();
bool negative = bigint::AsIntN(GetRWDigits(result), GetDigits(x), x->sign(),
static_cast<int>(n));
result->set_sign(negative);
return MutableBigInt::MakeImmutable(result);
}
MaybeHandle<BigInt> BigInt::AsUintN(Isolate* isolate, uint64_t n,
Handle<BigInt> x) {
if (x->is_zero()) return x;
if (n == 0) return MutableBigInt::Zero(isolate);
// If {x} is negative, simulate two's complement representation.
Handle<MutableBigInt> result;
if (x->sign()) {
if (n > kMaxLengthBits) {
return ThrowBigIntTooBig<BigInt>(isolate);
}
return MutableBigInt::TruncateAndSubFromPowerOfTwo(
isolate, static_cast<int>(n), x, false);
}
// If {x} is positive and has up to {n} bits, return it directly.
int result_length = bigint::AsUintN_Neg_ResultLength(static_cast<int>(n));
result = MutableBigInt::New(isolate, result_length).ToHandleChecked();
bigint::AsUintN_Neg(GetRWDigits(result), GetDigits(x), static_cast<int>(n));
} else {
if (n >= kMaxLengthBits) return x;
STATIC_ASSERT(kMaxLengthBits < kMaxInt - kDigitBits);
int needed_length = static_cast<int>((n + kDigitBits - 1) / kDigitBits);
if (x->length() < needed_length) return x;
int bits_in_top_digit = n % kDigitBits;
if (x->length() == needed_length) {
if (bits_in_top_digit == 0) return x;
digit_t top_digit = x->digit(needed_length - 1);
if ((top_digit >> bits_in_top_digit) == 0) return x;
}
// Otherwise, truncate.
DCHECK_LE(n, kMaxInt);
return MutableBigInt::TruncateToNBits(isolate, static_cast<int>(n), x);
}
Handle<BigInt> MutableBigInt::TruncateToNBits(Isolate* isolate, int n,
Handle<BigInt> x) {
// Only call this when there's something to do.
DCHECK_NE(n, 0);
DCHECK_GT(x->length(), n / kDigitBits);
int needed_digits = (n + (kDigitBits - 1)) / kDigitBits;
DCHECK_LE(needed_digits, x->length());
Handle<MutableBigInt> result = New(isolate, needed_digits).ToHandleChecked();
// Copy all digits except the MSD.
int last = needed_digits - 1;
for (int i = 0; i < last; i++) {
result->set_digit(i, x->digit(i));
}
// The MSD might contain extra bits that we don't want.
digit_t msd = x->digit(last);
if (n % kDigitBits != 0) {
int drop = kDigitBits - (n % kDigitBits);
msd = (msd << drop) >> drop;
int result_length =
bigint::AsUintN_Pos_ResultLength(GetDigits(x), static_cast<int>(n));
if (result_length < 0) return x;
result = MutableBigInt::New(isolate, result_length).ToHandleChecked();
bigint::AsUintN_Pos(GetRWDigits(result), GetDigits(x), static_cast<int>(n));
}
result->set_digit(last, msd);
result->set_sign(x->sign());
return MakeImmutable(result);
}
// Subtracts the least significant n bits of abs(x) from 2^n.
Handle<BigInt> MutableBigInt::TruncateAndSubFromPowerOfTwo(Isolate* isolate,
int n,
Handle<BigInt> x,
bool result_sign) {
DCHECK_NE(n, 0);
DCHECK_LE(n, kMaxLengthBits);
int needed_digits = (n + (kDigitBits - 1)) / kDigitBits;
DCHECK_LE(needed_digits, kMaxLength); // Follows from n <= kMaxLengthBits.
Handle<MutableBigInt> result = New(isolate, needed_digits).ToHandleChecked();
// Process all digits except the MSD.
int i = 0;
int last = needed_digits - 1;
int x_length = x->length();
digit_t borrow = 0;
// Take digits from {x} unless its length is exhausted.
int limit = std::min(last, x_length);
for (; i < limit; i++) {
digit_t new_borrow = 0;
digit_t difference = digit_sub(0, x->digit(i), &new_borrow);
difference = digit_sub(difference, borrow, &new_borrow);
result->set_digit(i, difference);
borrow = new_borrow;
}
// Then simulate leading zeroes in {x} as needed.
for (; i < last; i++) {
digit_t new_borrow = 0;
digit_t difference = digit_sub(0, borrow, &new_borrow);
result->set_digit(i, difference);
borrow = new_borrow;
}
// The MSD might contain extra bits that we don't want.
digit_t msd = last < x_length ? x->digit(last) : 0;
int msd_bits_consumed = n % kDigitBits;
digit_t result_msd;
if (msd_bits_consumed == 0) {
digit_t new_borrow = 0;
result_msd = digit_sub(0, msd, &new_borrow);
result_msd = digit_sub(result_msd, borrow, &new_borrow);
} else {
int drop = kDigitBits - msd_bits_consumed;
msd = (msd << drop) >> drop;
digit_t minuend_msd = static_cast<digit_t>(1) << (kDigitBits - drop);
digit_t new_borrow = 0;
result_msd = digit_sub(minuend_msd, msd, &new_borrow);
result_msd = digit_sub(result_msd, borrow, &new_borrow);
DCHECK_EQ(new_borrow, 0); // result < 2^n.
// If all subtracted bits were zero, we have to get rid of the
// materialized minuend_msd again.
result_msd &= (minuend_msd - 1);
}
result->set_digit(last, result_msd);
result->set_sign(result_sign);
return MakeImmutable(result);
DCHECK(!result->sign());
return MutableBigInt::MakeImmutable(result);
}
Handle<BigInt> BigInt::FromInt64(Isolate* isolate, int64_t n) {
......@@ -1769,34 +1642,6 @@ uint64_t BigInt::AsUint64(bool* lossless) {
return result;
}
// Digit arithmetic helpers.
#if V8_TARGET_ARCH_32_BIT
#define HAVE_TWODIGIT_T 1
using twodigit_t = uint64_t;
#elif defined(__SIZEOF_INT128__)
// Both Clang and GCC support this on x64.
#define HAVE_TWODIGIT_T 1
using twodigit_t = __uint128_t;
#endif
// {borrow} must point to an initialized digit_t and will either be incremented
// by one or left alone.
inline BigInt::digit_t MutableBigInt::digit_sub(digit_t a, digit_t b,
digit_t* borrow) {
#if HAVE_TWODIGIT_T
twodigit_t result = static_cast<twodigit_t>(a) - static_cast<twodigit_t>(b);
*borrow += (result >> kDigitBits) & 1;
return static_cast<digit_t>(result);
#else
digit_t result = a - b;
if (result > a) *borrow += 1;
return static_cast<digit_t>(result);
#endif
}
#undef HAVE_TWODIGIT_T
void MutableBigInt::set_64_bits(uint64_t bits) {
STATIC_ASSERT(kDigitBits == 64 || kDigitBits == 32);
if (kDigitBits == 64) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment