blob: c4cec22b539671440ef491adbf7eaf315f67b699 [file] [log] [blame] [edit]
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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 {
namespace bigint {
void BitwiseAnd_PosPos(RWDigits Z, Digits X, Digits Y) {
int pairs = std::min(X.len(), Y.len());
DCHECK(Z.len() >= pairs);
int i = 0;
for (; i < pairs; i++) Z[i] = X[i] & Y[i];
for (; i < Z.len(); i++) Z[i] = 0;
}
void BitwiseAnd_NegNeg(RWDigits Z, Digits X, Digits Y) {
// (-x) & (-y) == ~(x-1) & ~(y-1)
// == ~((x-1) | (y-1))
// == -(((x-1) | (y-1)) + 1)
int pairs = std::min(X.len(), Y.len());
digit_t x_borrow = 1;
digit_t y_borrow = 1;
int i = 0;
for (; i < pairs; i++) {
Z[i] = digit_sub(X[i], x_borrow, &x_borrow) |
digit_sub(Y[i], y_borrow, &y_borrow);
}
// (At least) one of the next two loops will perform zero iterations:
for (; i < X.len(); i++) Z[i] = digit_sub(X[i], x_borrow, &x_borrow);
for (; i < Y.len(); i++) Z[i] = digit_sub(Y[i], y_borrow, &y_borrow);
DCHECK(x_borrow == 0);
DCHECK(y_borrow == 0);
for (; i < Z.len(); i++) Z[i] = 0;
Add(Z, 1);
}
void BitwiseAnd_PosNeg(RWDigits Z, Digits X, Digits Y) {
// x & (-y) == x & ~(y-1)
int pairs = std::min(X.len(), Y.len());
digit_t borrow = 1;
int i = 0;
for (; i < pairs; i++) Z[i] = X[i] & ~digit_sub(Y[i], borrow, &borrow);
for (; i < X.len(); i++) Z[i] = X[i];
for (; i < Z.len(); i++) Z[i] = 0;
}
void BitwiseOr_PosPos(RWDigits Z, Digits X, Digits Y) {
int pairs = std::min(X.len(), Y.len());
int i = 0;
for (; i < pairs; i++) Z[i] = X[i] | Y[i];
// (At least) one of the next two loops will perform zero iterations:
for (; i < X.len(); i++) Z[i] = X[i];
for (; i < Y.len(); i++) Z[i] = Y[i];
for (; i < Z.len(); i++) Z[i] = 0;
}
void BitwiseOr_NegNeg(RWDigits Z, Digits X, Digits Y) {
// (-x) | (-y) == ~(x-1) | ~(y-1)
// == ~((x-1) & (y-1))
// == -(((x-1) & (y-1)) + 1)
int pairs = std::min(X.len(), Y.len());
digit_t x_borrow = 1;
digit_t y_borrow = 1;
int i = 0;
for (; i < pairs; i++) {
Z[i] = digit_sub(X[i], x_borrow, &x_borrow) &
digit_sub(Y[i], y_borrow, &y_borrow);
}
// Any leftover borrows don't matter, the '&' would drop them anyway.
for (; i < Z.len(); i++) Z[i] = 0;
Add(Z, 1);
}
void BitwiseOr_PosNeg(RWDigits Z, Digits X, Digits Y) {
// x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) &~ x) + 1)
int pairs = std::min(X.len(), Y.len());
digit_t borrow = 1;
int i = 0;
for (; i < pairs; i++) Z[i] = digit_sub(Y[i], borrow, &borrow) & ~X[i];
for (; i < Y.len(); i++) Z[i] = digit_sub(Y[i], borrow, &borrow);
DCHECK(borrow == 0);
for (; i < Z.len(); i++) Z[i] = 0;
Add(Z, 1);
}
void BitwiseXor_PosPos(RWDigits Z, Digits X, Digits Y) {
int pairs = X.len();
if (Y.len() < X.len()) {
std::swap(X, Y);
pairs = X.len();
}
DCHECK(X.len() <= Y.len());
int i = 0;
for (; i < pairs; i++) Z[i] = X[i] ^ Y[i];
for (; i < Y.len(); i++) Z[i] = Y[i];
for (; i < Z.len(); i++) Z[i] = 0;
}
void BitwiseXor_NegNeg(RWDigits Z, Digits X, Digits Y) {
// (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1)
int pairs = std::min(X.len(), Y.len());
digit_t x_borrow = 1;
digit_t y_borrow = 1;
int i = 0;
for (; i < pairs; i++) {
Z[i] = digit_sub(X[i], x_borrow, &x_borrow) ^
digit_sub(Y[i], y_borrow, &y_borrow);
}
// (At least) one of the next two loops will perform zero iterations:
for (; i < X.len(); i++) Z[i] = digit_sub(X[i], x_borrow, &x_borrow);
for (; i < Y.len(); i++) Z[i] = digit_sub(Y[i], y_borrow, &y_borrow);
DCHECK(x_borrow == 0);
DCHECK(y_borrow == 0);
for (; i < Z.len(); i++) Z[i] = 0;
}
void BitwiseXor_PosNeg(RWDigits Z, Digits X, Digits Y) {
// x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1)
int pairs = std::min(X.len(), Y.len());
digit_t borrow = 1;
int i = 0;
for (; i < pairs; i++) Z[i] = X[i] ^ digit_sub(Y[i], borrow, &borrow);
// (At least) one of the next two loops will perform zero iterations:
for (; i < X.len(); i++) Z[i] = X[i];
for (; i < Y.len(); i++) Z[i] = digit_sub(Y[i], borrow, &borrow);
DCHECK(borrow == 0);
for (; i < Z.len(); i++) Z[i] = 0;
Add(Z, 1);
}
void LeftShift(RWDigits Z, Digits X, digit_t shift) {
int digit_shift = static_cast<int>(shift / kDigitBits);
int bits_shift = static_cast<int>(shift % kDigitBits);
int i = 0;
for (; i < digit_shift; ++i) Z[i] = 0;
if (bits_shift == 0) {
for (; i < X.len() + digit_shift; ++i) Z[i] = X[i - digit_shift];
for (; i < Z.len(); ++i) Z[i] = 0;
} else {
digit_t carry = 0;
for (; i < X.len() + digit_shift; ++i) {
digit_t d = X[i - digit_shift];
Z[i] = (d << bits_shift) | carry;
carry = d >> (kDigitBits - bits_shift);
}
if (carry != 0) Z[i++] = carry;
for (; i < Z.len(); ++i) Z[i] = 0;
}
}
int RightShift_ResultLength(Digits X, bool x_sign, digit_t shift,
RightShiftState* state) {
int digit_shift = static_cast<int>(shift / kDigitBits);
int bits_shift = static_cast<int>(shift % kDigitBits);
int result_length = X.len() - digit_shift;
if (result_length <= 0) return 0;
// For negative numbers, round down if any bit was shifted out (so that e.g.
// -5n >> 1n == -3n and not -2n). Check now whether this will happen and
// whether it can cause overflow into a new digit.
bool must_round_down = false;
if (x_sign) {
const digit_t mask = (static_cast<digit_t>(1) << bits_shift) - 1;
if ((X[digit_shift] & mask) != 0) {
must_round_down = true;
} else {
for (int i = 0; i < digit_shift; i++) {
if (X[i] != 0) {
must_round_down = true;
break;
}
}
}
}
// If bits_shift is non-zero, it frees up bits, preventing overflow.
if (must_round_down && bits_shift == 0) {
// Overflow cannot happen if the most significant digit has unset bits.
const bool rounding_can_overflow = digit_ismax(X.msd());
if (rounding_can_overflow) ++result_length;
}
if (state) {
DCHECK(!must_round_down || x_sign);
state->must_round_down = must_round_down;
}
return result_length;
}
void RightShift(RWDigits Z, Digits X, digit_t shift,
const RightShiftState& state) {
int digit_shift = static_cast<int>(shift / kDigitBits);
int bits_shift = static_cast<int>(shift % kDigitBits);
int i = 0;
if (bits_shift == 0) {
for (; i < X.len() - digit_shift; ++i) Z[i] = X[i + digit_shift];
} else {
digit_t carry = X[digit_shift] >> bits_shift;
for (; i < X.len() - digit_shift - 1; ++i) {
digit_t d = X[i + digit_shift + 1];
Z[i] = (d << (kDigitBits - bits_shift)) | carry;
carry = d >> bits_shift;
}
Z[i++] = carry;
}
for (; i < Z.len(); ++i) Z[i] = 0;
if (state.must_round_down) {
// Rounding down (a negative value) means adding one to
// its absolute value. This cannot overflow.
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.
// 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);
DCHECK(n > 0);
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);
TruncateToNBits(Z, X, n);
}
void AsUintN_Neg(RWDigits Z, Digits X, int n) {
TruncateAndSubFromPowerOfTwo(Z, X, n);
}
} // namespace bigint
} // namespace v8