mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 03:25:13 +00:00
LibJS+LibCrypto: Use a bitwise approach for BigInt's as*IntN methods
This speeds up expressions such as `BigInt.asIntN(0x4000000000000, 1n)` (#3615). And those involving very large bigints.
This commit is contained in:
parent
92d0cd3c7c
commit
12cbefbee7
Notes:
github-actions[bot]
2025-03-20 08:45:14 +00:00
Author: https://github.com/ttrssreal Commit: https://github.com/LadybirdBrowser/ladybird/commit/12cbefbee7b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3994 Reviewed-by: https://github.com/gmta ✅
9 changed files with 110 additions and 33 deletions
|
@ -131,7 +131,7 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_xor_without_allocation(
|
|||
/**
|
||||
* Complexity: O(N) where N is the number of words
|
||||
*/
|
||||
FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
|
||||
FLATTEN ErrorOr<void> UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(
|
||||
UnsignedBigInteger const& right,
|
||||
size_t index,
|
||||
UnsignedBigInteger& output)
|
||||
|
@ -139,16 +139,16 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w
|
|||
// If the value is invalid, the output value is invalid as well.
|
||||
if (right.is_invalid()) {
|
||||
output.invalidate();
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
output.set_to_0();
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
size_t size = (index + UnsignedBigInteger::BITS_IN_WORD - 1) / UnsignedBigInteger::BITS_IN_WORD;
|
||||
|
||||
output.m_words.resize_and_keep_capacity(size);
|
||||
TRY(output.m_words.try_resize_and_keep_capacity(size));
|
||||
VERIFY(size > 0);
|
||||
for (size_t i = 0; i < size - 1; ++i)
|
||||
output.m_words[i] = ~(i < right.length() ? right.words()[i] : 0);
|
||||
|
@ -158,6 +158,8 @@ FLATTEN void UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_w
|
|||
auto last_word = last_word_index < right.length() ? right.words()[last_word_index] : 0;
|
||||
|
||||
output.m_words[last_word_index] = (NumericLimits<UnsignedBigInteger::Word>::max() >> (UnsignedBigInteger::BITS_IN_WORD - index)) & ~last_word;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
FLATTEN void UnsignedBigIntegerAlgorithms::shift_left_without_allocation(
|
||||
|
|
|
@ -20,13 +20,13 @@ public:
|
|||
static void bitwise_or_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
|
||||
static void bitwise_and_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
|
||||
static void bitwise_xor_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& output);
|
||||
static void bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
|
||||
static void shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
|
||||
static void shift_right_without_allocation(UnsignedBigInteger const& number, size_t num_bits, UnsignedBigInteger& output);
|
||||
static void multiply_without_allocation(UnsignedBigInteger const& left, UnsignedBigInteger const& right, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& output);
|
||||
static void divide_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger const& denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
|
||||
static void divide_u16_without_allocation(UnsignedBigInteger const& numerator, UnsignedBigInteger::Word denominator, UnsignedBigInteger& quotient, UnsignedBigInteger& remainder);
|
||||
|
||||
static ErrorOr<void> bitwise_not_fill_to_one_based_index_without_allocation(UnsignedBigInteger const& left, size_t, UnsignedBigInteger& output);
|
||||
static ErrorOr<void> try_shift_left_without_allocation(UnsignedBigInteger const& number, size_t bits_to_shift_by, UnsignedBigInteger& temp_result, UnsignedBigInteger& temp_plus, UnsignedBigInteger& output);
|
||||
|
||||
static void extended_GCD_without_allocation(UnsignedBigInteger const& a, UnsignedBigInteger const& b, UnsignedBigInteger& x, UnsignedBigInteger& y, UnsignedBigInteger& gcd, UnsignedBigInteger& temp_quotient, UnsignedBigInteger& temp_1, UnsignedBigInteger& temp_2, UnsignedBigInteger& temp_shift_result, UnsignedBigInteger& temp_shift_plus, UnsignedBigInteger& temp_shift, UnsignedBigInteger& temp_r, UnsignedBigInteger& temp_s, UnsignedBigInteger& temp_t);
|
||||
|
|
|
@ -291,6 +291,17 @@ FLATTEN SignedBigInteger SignedBigInteger::shift_right(size_t num_bits) const
|
|||
return SignedBigInteger { m_unsigned_data.shift_right(num_bits), m_sign };
|
||||
}
|
||||
|
||||
FLATTEN ErrorOr<SignedBigInteger> SignedBigInteger::mod_power_of_two(size_t power_of_two) const
|
||||
{
|
||||
auto const lower_bits = m_unsigned_data.as_n_bits(power_of_two);
|
||||
|
||||
if (is_positive())
|
||||
return SignedBigInteger(lower_bits);
|
||||
|
||||
// twos encode lower bits
|
||||
return SignedBigInteger(TRY(lower_bits.try_bitwise_not_fill_to_one_based_index(power_of_two)).plus(1).as_n_bits(power_of_two));
|
||||
}
|
||||
|
||||
FLATTEN SignedBigInteger SignedBigInteger::multiplied_by(SignedBigInteger const& other) const
|
||||
{
|
||||
bool result_sign = m_sign ^ other.m_sign;
|
||||
|
|
|
@ -120,6 +120,7 @@ public:
|
|||
[[nodiscard]] SignedBigInteger multiplied_by(SignedBigInteger const& other) const;
|
||||
[[nodiscard]] SignedDivisionResult divided_by(SignedBigInteger const& divisor) const;
|
||||
|
||||
[[nodiscard]] ErrorOr<SignedBigInteger> mod_power_of_two(size_t power_of_two) const;
|
||||
[[nodiscard]] ErrorOr<SignedBigInteger> try_shift_left(size_t num_bits) const;
|
||||
|
||||
[[nodiscard]] SignedBigInteger plus(UnsignedBigInteger const& other) const;
|
||||
|
|
|
@ -473,10 +473,15 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_xor(UnsignedBigInteger co
|
|||
}
|
||||
|
||||
FLATTEN UnsignedBigInteger UnsignedBigInteger::bitwise_not_fill_to_one_based_index(size_t size) const
|
||||
{
|
||||
return MUST(try_bitwise_not_fill_to_one_based_index(size));
|
||||
}
|
||||
|
||||
FLATTEN ErrorOr<UnsignedBigInteger> UnsignedBigInteger::try_bitwise_not_fill_to_one_based_index(size_t size) const
|
||||
{
|
||||
UnsignedBigInteger result;
|
||||
|
||||
UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result);
|
||||
TRY(UnsignedBigIntegerAlgorithms::bitwise_not_fill_to_one_based_index_without_allocation(*this, size, result));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -506,6 +511,33 @@ FLATTEN UnsignedBigInteger UnsignedBigInteger::shift_right(size_t num_bits) cons
|
|||
return output;
|
||||
}
|
||||
|
||||
FLATTEN UnsignedBigInteger UnsignedBigInteger::as_n_bits(size_t n) const
|
||||
{
|
||||
if (auto const num_bits = one_based_index_of_highest_set_bit(); n >= num_bits)
|
||||
return *this;
|
||||
|
||||
UnsignedBigInteger output;
|
||||
output.set_to(*this);
|
||||
|
||||
auto const word_index = n / BITS_IN_WORD;
|
||||
|
||||
auto const bits_to_keep = n % BITS_IN_WORD;
|
||||
auto const bits_to_discard = BITS_IN_WORD - bits_to_keep;
|
||||
|
||||
output.m_words.resize(word_index + 1);
|
||||
|
||||
auto const last_word = output.m_words[word_index];
|
||||
Word new_last_word = 0;
|
||||
|
||||
// avoid UB from a 32 bit shift on a u32
|
||||
if (bits_to_keep != 0)
|
||||
new_last_word = last_word << bits_to_discard >> bits_to_discard;
|
||||
|
||||
output.m_words[word_index] = new_last_word;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
FLATTEN UnsignedBigInteger UnsignedBigInteger::multiplied_by(UnsignedBigInteger const& other) const
|
||||
{
|
||||
UnsignedBigInteger result;
|
||||
|
|
|
@ -117,9 +117,11 @@ public:
|
|||
[[nodiscard]] UnsignedBigInteger bitwise_not_fill_to_one_based_index(size_t) const;
|
||||
[[nodiscard]] UnsignedBigInteger shift_left(size_t num_bits) const;
|
||||
[[nodiscard]] UnsignedBigInteger shift_right(size_t num_bits) const;
|
||||
[[nodiscard]] UnsignedBigInteger as_n_bits(size_t n) const;
|
||||
[[nodiscard]] UnsignedBigInteger multiplied_by(UnsignedBigInteger const& other) const;
|
||||
[[nodiscard]] UnsignedDivisionResult divided_by(UnsignedBigInteger const& divisor) const;
|
||||
|
||||
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_bitwise_not_fill_to_one_based_index(size_t) const;
|
||||
[[nodiscard]] ErrorOr<UnsignedBigInteger> try_shift_left(size_t num_bits) const;
|
||||
|
||||
[[nodiscard]] u32 hash() const;
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace JS {
|
|||
GC_DEFINE_ALLOCATOR(BigIntConstructor);
|
||||
|
||||
static Crypto::SignedBigInteger const BIGINT_ONE { 1 };
|
||||
static Crypto::SignedBigInteger const BIGINT_ZERO { 0 };
|
||||
|
||||
BigIntConstructor::BigIntConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.BigInt.as_string(), realm.intrinsics().function_prototype())
|
||||
|
@ -72,20 +73,25 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_int_n)
|
|||
// 2. Set bigint to ? ToBigInt(bigint).
|
||||
auto bigint = TRY(vm.argument(1).to_bigint(vm));
|
||||
|
||||
// OPTIMIZATION: mod = bigint (mod 2^0) = 0 < 2^(0-1) = 0.5
|
||||
if (bits == 0)
|
||||
return BigInt::create(vm, BIGINT_ZERO);
|
||||
|
||||
// 3. Let mod be ℝ(bigint) modulo 2^bits.
|
||||
// FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to
|
||||
// drop the most significant bits.
|
||||
auto bits_shift_left = TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits));
|
||||
auto mod = modulo(bigint->big_integer(), bits_shift_left);
|
||||
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
|
||||
|
||||
// 4. If mod ≥ 2^(bits-1), return ℤ(mod - 2^bits); otherwise, return ℤ(mod).
|
||||
// NOTE: Some of the below conditionals are non-standard, but are to protect SignedBigInteger from
|
||||
// allocating an absurd amount of memory if `bits - 1` overflows to NumericLimits<size_t>::max.
|
||||
if ((bits == 0) && (mod >= BIGINT_ONE))
|
||||
return BigInt::create(vm, mod.minus(bits_shift_left));
|
||||
if ((bits > 0) && (mod >= BIGINT_ONE.shift_left(bits - 1)))
|
||||
return BigInt::create(vm, mod.minus(bits_shift_left));
|
||||
// OPTIMIZATION: mod < 2^(bits-1)
|
||||
if (mod.is_zero())
|
||||
return BigInt::create(vm, BIGINT_ZERO);
|
||||
|
||||
// 4. If mod ≥ 2^(bits-1), return ℤ(mod - 2^bits); ...
|
||||
if (auto top_bit_index = mod.unsigned_value().one_based_index_of_highest_set_bit(); top_bit_index >= bits) {
|
||||
// twos complement decode
|
||||
auto decoded = TRY_OR_THROW_OOM(vm, mod.unsigned_value().try_bitwise_not_fill_to_one_based_index(bits)).plus(1);
|
||||
return BigInt::create(vm, Crypto::SignedBigInteger { std::move(decoded), true });
|
||||
}
|
||||
|
||||
// ... otherwise, return ℤ(mod).
|
||||
return BigInt::create(vm, mod);
|
||||
}
|
||||
|
||||
|
@ -98,10 +104,10 @@ JS_DEFINE_NATIVE_FUNCTION(BigIntConstructor::as_uint_n)
|
|||
// 2. Set bigint to ? ToBigInt(bigint).
|
||||
auto bigint = TRY(vm.argument(1).to_bigint(vm));
|
||||
|
||||
// 3. Return the BigInt value that represents ℝ(bigint) modulo 2bits.
|
||||
// FIXME: For large values of `bits`, this can likely be improved with a SignedBigInteger API to
|
||||
// drop the most significant bits.
|
||||
return BigInt::create(vm, modulo(bigint->big_integer(), TRY_OR_THROW_OOM(vm, BIGINT_ONE.try_shift_left(bits))));
|
||||
// 3. Return the BigInt value that represents ℝ(bigint) modulo 2^bits.
|
||||
auto const mod = TRY_OR_THROW_OOM(vm, bigint->big_integer().mod_power_of_two(bits));
|
||||
|
||||
return BigInt::create(vm, mod);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,12 +22,6 @@ describe("errors", () => {
|
|||
BigInt.asIntN(1, "foo");
|
||||
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
|
||||
});
|
||||
|
||||
test("large allocation", () => {
|
||||
expect(() => {
|
||||
BigInt.asIntN(0x4000000000000, 1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
|
@ -82,4 +76,23 @@ describe("correct behavior", () => {
|
|||
expect(BigInt.asIntN(128, -extremelyBigInt)).toBe(99061374399389259395070030194384019691n);
|
||||
expect(BigInt.asIntN(256, -extremelyBigInt)).toBe(-extremelyBigInt);
|
||||
});
|
||||
|
||||
test("large bit values", () => {
|
||||
expect(BigInt.asIntN(0x4000000000000, 1n)).toBe(1n);
|
||||
expect(BigInt.asIntN(0x8ffffffffffff, 1n)).toBe(1n);
|
||||
expect(BigInt.asIntN(2 ** 53 - 1, 2n)).toBe(2n);
|
||||
|
||||
// These incur large intermediate values that 00M. For now, ensure they don't crash
|
||||
expect(() => {
|
||||
BigInt.asIntN(0x4000000000000, -1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
|
||||
expect(() => {
|
||||
BigInt.asIntN(0x8ffffffffffff, -1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
|
||||
expect(() => {
|
||||
BigInt.asIntN(2 ** 53 - 1, -2n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,12 +22,6 @@ describe("errors", () => {
|
|||
BigInt.asUintN(1, "foo");
|
||||
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
|
||||
});
|
||||
|
||||
test("large allocation", () => {
|
||||
expect(() => {
|
||||
BigInt.asUintN(0x4000000000000, 1n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
||||
describe("correct behavior", () => {
|
||||
|
@ -80,4 +74,20 @@ describe("correct behavior", () => {
|
|||
115792089237316195423570861551898784396480861208851440582668460551124006183147n
|
||||
);
|
||||
});
|
||||
|
||||
test("large bit values", () => {
|
||||
const INDEX_MAX = 2 ** 53 - 1;
|
||||
const LAST_8_DIGITS = 10n ** 8n;
|
||||
|
||||
expect(BigInt.asUintN(0x400000000, 1n)).toBe(1n);
|
||||
expect(BigInt.asUintN(0x4000, -1n) % LAST_8_DIGITS).toBe(64066815n);
|
||||
|
||||
expect(BigInt.asUintN(0x400000000, 2n)).toBe(2n);
|
||||
expect(BigInt.asUintN(0x4000, -2n) % LAST_8_DIGITS).toBe(64066814n);
|
||||
|
||||
expect(BigInt.asUintN(INDEX_MAX, 2n)).toBe(2n);
|
||||
expect(() => {
|
||||
BigInt.asUintN(INDEX_MAX, -2n);
|
||||
}).toThrowWithMessage(InternalError, "Out of memory");
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue