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:
Jess 2025-03-19 10:31:39 +13:00 committed by Jelle Raaijmakers
commit 12cbefbee7
Notes: github-actions[bot] 2025-03-20 08:45:14 +00:00
9 changed files with 110 additions and 33 deletions

View file

@ -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);
}
}