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

[bigint] Move String-to-BigInt parsing to src/bigint/

No changes to the algorithm, approximately 4x performance
improvement thanks to reduced overhead.

Bug: v8:11515
Change-Id: Id3f6c91bd650f6ae47ac8f169dc780420091998e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3046185
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#76022}
parent 835a8b7d
......@@ -2685,6 +2685,7 @@ filegroup(
"src/bigint/div-helpers.cc",
"src/bigint/div-helpers.h",
"src/bigint/div-schoolbook.cc",
"src/bigint/fromstring.cc",
"src/bigint/mul-fft.cc",
"src/bigint/mul-karatsuba.cc",
"src/bigint/mul-schoolbook.cc",
......
......@@ -5005,6 +5005,7 @@ v8_source_set("v8_bigint") {
"src/bigint/div-helpers.cc",
"src/bigint/div-helpers.h",
"src/bigint/div-schoolbook.cc",
"src/bigint/fromstring.cc",
"src/bigint/mul-karatsuba.cc",
"src/bigint/mul-schoolbook.cc",
"src/bigint/tostring.cc",
......
......@@ -67,6 +67,9 @@ class ProcessorImpl : public Processor {
void ToStringImpl(char* out, int* out_length, Digits X, int radix, bool sign,
bool use_fast_algorithm);
void FromString(RWDigits Z, FromStringAccumulator* accumulator);
void FromStringClassic(RWDigits Z, FromStringAccumulator* accumulator);
bool should_terminate() { return status_ == Status::kInterrupted; }
// Each unit is supposed to represent approximately one CPU {mul} instruction.
......
......@@ -10,6 +10,7 @@
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
namespace v8 {
namespace bigint {
......@@ -235,6 +236,8 @@ bool SubtractSigned(RWDigits Z, Digits X, bool x_negative, Digits Y,
enum class Status { kOk, kInterrupted };
class FromStringAccumulator;
class Processor {
public:
// Takes ownership of {platform}.
......@@ -258,6 +261,8 @@ class Processor {
// {out_length} initially contains the allocated capacity of {out}, and
// upon return will be set to the actual length of the result string.
Status ToString(char* out, int* out_length, Digits X, int radix, bool sign);
Status FromString(RWDigits Z, FromStringAccumulator* accumulator);
};
inline int AddResultLength(int x_length, int y_length) {
......@@ -296,9 +301,130 @@ int ToStringResultLength(Digits X, int radix, bool sign);
// In DEBUG builds, the result of {ToString} will be initialized to this value.
constexpr char kStringZapValue = '?';
// Support for parsing BigInts from Strings, using an Accumulator object
// for intermediate state.
class ProcessorImpl;
#if defined(__GNUC__) || defined(__clang__)
// Clang supports this since 3.9, GCC since 5.x.
#define HAVE_BUILTIN_MUL_OVERFLOW 1
#else
#define HAVE_BUILTIN_MUL_OVERFLOW 0
#endif
// A container object for all metadata required for parsing a BigInt from
// a string.
// Aggressively optimized not to waste instructions for small cases, while
// also scaling transparently to huge cases.
// Defined here in the header so that {ConsumeChar} can be inlined.
class FromStringAccumulator {
public:
// {max_digits} is only used for refusing to grow beyond a given size
// (see "Step 1" below). Does not cause pre-allocation, so feel free to
// specify a large maximum.
// TODO(jkummerow): The limit applies to the number of intermediate chunks,
// whereas the final result will be slightly smaller (depending on {radix}).
// So setting max_digits=N here will, for sufficiently large N, not actually
// allow parsing BigInts with N digits. We can fix that if/when anyone cares.
FromStringAccumulator(int radix, int max_digits)
: radix_(radix),
#if !HAVE_BUILTIN_MUL_OVERFLOW
max_multiplier_((~digit_t{0}) / radix),
#endif
max_digits_(max_digits),
limit_digit_(radix < 10 ? radix : 10),
limit_alpha_(radix > 10 ? radix - 10 : 0) {
}
~FromStringAccumulator() {
delete parts_;
delete multipliers_;
}
// Step 1: Call this method repeatedly to read all characters.
// This method will return quickly; it does not perform heavy processing.
enum class Result { kOk, kInvalidChar, kMaxSizeExceeded };
Result ConsumeChar(uint32_t c) {
digit_t d;
if (c - '0' < limit_digit_) {
d = c - '0';
} else if ((c | 32u) - 'a' < limit_alpha_) {
d = (c | 32u) - 'a' + 10;
} else {
return Result::kInvalidChar;
}
#if HAVE_BUILTIN_MUL_OVERFLOW
digit_t m;
if (!__builtin_mul_overflow(multiplier_, radix_, &m)) {
multiplier_ = m;
part_ = part_ * radix_ + d;
}
#else
if (multiplier_ <= max_multiplier_) {
multiplier_ *= radix_;
part_ = part_ * radix_ + d;
}
#endif
else { // NOLINT(readability/braces)
if (!AddPart(multiplier_, part_)) return Result::kMaxSizeExceeded;
multiplier_ = radix_;
part_ = d;
}
return Result::kOk;
}
// Step 2: Call this method to determine the required size for the result.
int ResultLength() {
if (!parts_) return part_ > 0 ? 1 : 0;
if (multiplier_ > 1) {
multipliers_->push_back(multiplier_);
parts_->push_back(part_);
// {ResultLength} should be idempotent.
multiplier_ = 1;
part_ = 0;
}
return parts_size();
}
// Step 3: Use BigIntProcessor::FromString() to retrieve the result into an
// {RWDigits} struct allocated for the size returned by step 2.
private:
friend class ProcessorImpl;
int parts_size() { return static_cast<int>(parts_->size()); }
bool AddPart(digit_t multiplier, digit_t part) {
if (!parts_) {
parts_ = new std::vector<digit_t>;
multipliers_ = new std::vector<digit_t>;
} else if (parts_size() == max_digits_) {
return false;
}
multipliers_->push_back(multiplier);
parts_->push_back(part);
return true;
}
const digit_t radix_;
#if !HAVE_BUILTIN_MUL_OVERFLOW
const digit_t max_multiplier_;
#endif
// The next part to be added to {parts_}, or the only part when sufficient.
digit_t part_{0};
digit_t multiplier_{1};
const int max_digits_;
const uint32_t limit_digit_;
const uint32_t limit_alpha_;
// Avoid allocating these unless we actually need them.
std::vector<digit_t>* parts_{nullptr};
std::vector<digit_t>* multipliers_{nullptr};
};
} // namespace bigint
} // namespace v8
#undef BIGINT_H_DCHECK
#undef HAVE_BUILTIN_MUL_OVERFLOW
#endif // V8_BIGINT_BIGINT_H_
// 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/vector-arithmetic.h"
namespace v8 {
namespace bigint {
// The classic algorithm: for every part, multiply the accumulator with
// the appropriate multiplier, and add the part. O(n²) overall.
void ProcessorImpl::FromStringClassic(RWDigits Z,
FromStringAccumulator* accumulator) {
Z[0] = (*accumulator->parts_)[0];
RWDigits already_set(Z, 0, 1);
for (int i = 1; i < Z.len(); i++) Z[i] = 0;
for (int i = 1; i < accumulator->parts_size(); i++) {
MultiplySingle(Z, already_set, (*accumulator->multipliers_)[i]);
if (should_terminate()) return;
Add(Z, (*accumulator->parts_)[i]);
already_set.set_len(already_set.len() + 1);
}
}
void ProcessorImpl::FromString(RWDigits Z, FromStringAccumulator* accumulator) {
if (!accumulator->parts_) {
if (Z.len() > 0) Z[0] = accumulator->part_;
for (int i = 1; i < Z.len(); i++) Z[i] = 0;
} else {
FromStringClassic(Z, accumulator);
}
}
Status Processor::FromString(RWDigits Z, FromStringAccumulator* accumulator) {
ProcessorImpl* impl = static_cast<ProcessorImpl*>(this);
impl->FromString(Z, accumulator);
return impl->get_and_clear_status();
}
} // namespace bigint
} // namespace v8
......@@ -4,6 +4,7 @@
#include "src/execution/local-isolate.h"
#include "src/bigint/bigint.h"
#include "src/execution/isolate.h"
#include "src/execution/thread-id.h"
#include "src/handles/handles-inl.h"
......@@ -24,7 +25,9 @@ LocalIsolate::LocalIsolate(Isolate* isolate, ThreadKind kind,
: GetCurrentStackPosition() - FLAG_stack_size * KB),
runtime_call_stats_(runtime_call_stats) {}
LocalIsolate::~LocalIsolate() = default;
LocalIsolate::~LocalIsolate() {
if (bigint_processor_) bigint_processor_->Destroy();
}
void LocalIsolate::RegisterDeserializerStarted() {
return isolate_->RegisterDeserializerStarted();
......@@ -46,6 +49,12 @@ bool LocalIsolate::is_collecting_type_profile() const {
return isolate_->is_collecting_type_profile();
}
// Used for lazy initialization, based on an assumption that most
// LocalIsolates won't be used to parse any BigInt literals.
void LocalIsolate::InitializeBigIntProcessor() {
bigint_processor_ = bigint::Processor::New(new bigint::Platform());
}
// static
bool StackLimitCheck::HasOverflowed(LocalIsolate* local_isolate) {
return GetCurrentStackPosition() < local_isolate->stack_limit();
......
......@@ -15,6 +15,11 @@
#include "src/heap/local-heap.h"
namespace v8 {
namespace bigint {
class Processor;
}
namespace internal {
class Isolate;
......@@ -92,6 +97,10 @@ class V8_EXPORT_PRIVATE LocalIsolate final : private HiddenLocalFactory {
ThreadId thread_id() const { return thread_id_; }
Address stack_limit() const { return stack_limit_; }
RuntimeCallStats* runtime_call_stats() const { return runtime_call_stats_; }
bigint::Processor* bigint_processor() {
if (!bigint_processor_) InitializeBigIntProcessor();
return bigint_processor_;
}
bool is_main_thread() const { return heap_.is_main_thread(); }
......@@ -106,6 +115,8 @@ class V8_EXPORT_PRIVATE LocalIsolate final : private HiddenLocalFactory {
private:
friend class v8::internal::LocalFactory;
void InitializeBigIntProcessor();
LocalHeap heap_;
// TODO(leszeks): Extract out the fields of the Isolate we want and store
......@@ -117,6 +128,7 @@ class V8_EXPORT_PRIVATE LocalIsolate final : private HiddenLocalFactory {
Address const stack_limit_;
RuntimeCallStats* runtime_call_stats_;
bigint::Processor* bigint_processor_{nullptr};
};
template <base::MutexSharedType kIsShared>
......
This diff is collapsed.
......@@ -126,10 +126,6 @@ class MutableBigInt : public FreshlyAllocatedBigInt {
Isolate* isolate, Handle<BigIntBase> x, Handle<BigIntBase> y,
MutableBigInt result_storage = MutableBigInt());
static void InternalMultiplyAdd(BigIntBase source, digit_t factor,
digit_t summand, int n, MutableBigInt result);
void InplaceMultiplyAdd(uintptr_t factor, uintptr_t summand);
// Specialized helpers for shift operations.
static MaybeHandle<BigInt> LeftShiftByAbsolute(Isolate* isolate,
Handle<BigIntBase> x,
......@@ -152,7 +148,6 @@ class MutableBigInt : public FreshlyAllocatedBigInt {
// Digit arithmetic helpers.
static inline digit_t digit_add(digit_t a, digit_t b, digit_t* carry);
static inline digit_t digit_sub(digit_t a, digit_t b, digit_t* borrow);
static inline digit_t digit_mul(digit_t a, digit_t b, digit_t* high);
static inline bool digit_ismax(digit_t x) {
return static_cast<digit_t>(~x) == 0;
}
......@@ -1411,50 +1406,6 @@ Handle<MutableBigInt> MutableBigInt::AbsoluteXor(Isolate* isolate,
[](digit_t a, digit_t b) { return a ^ b; });
}
// Multiplies {source} with {factor} and adds {summand} to the result.
// {result} and {source} may be the same BigInt for inplace modification.
void MutableBigInt::InternalMultiplyAdd(BigIntBase source, digit_t factor,
digit_t summand, int n,
MutableBigInt result) {
DCHECK(source.length() >= n);
DCHECK(result.length() >= n);
digit_t carry = summand;
digit_t high = 0;
for (int i = 0; i < n; i++) {
digit_t current = source.digit(i);
digit_t new_carry = 0;
// Compute this round's multiplication.
digit_t new_high = 0;
current = digit_mul(current, factor, &new_high);
// Add last round's carryovers.
current = digit_add(current, high, &new_carry);
current = digit_add(current, carry, &new_carry);
// Store result and prepare for next round.
result.set_digit(i, current);
carry = new_carry;
high = new_high;
}
if (result.length() > n) {
result.set_digit(n++, carry + high);
// Current callers don't pass in such large results, but let's be robust.
while (n < result.length()) {
result.set_digit(n++, 0);
}
} else {
CHECK_EQ(carry + high, 0);
}
}
// Multiplies {x} with {factor} and then adds {summand} to it.
void BigInt::InplaceMultiplyAdd(FreshlyAllocatedBigInt x, uintptr_t factor,
uintptr_t summand) {
STATIC_ASSERT(sizeof(factor) == sizeof(digit_t));
STATIC_ASSERT(sizeof(summand) == sizeof(digit_t));
MutableBigInt bigint = MutableBigInt::cast(x);
MutableBigInt::InternalMultiplyAdd(bigint, factor, summand, bigint.length(),
bigint);
}
MaybeHandle<BigInt> MutableBigInt::LeftShiftByAbsolute(Isolate* isolate,
Handle<BigIntBase> x,
Handle<BigIntBase> y) {
......@@ -1591,71 +1542,33 @@ Maybe<BigInt::digit_t> MutableBigInt::ToShiftAmount(Handle<BigIntBase> x) {
return Just(value);
}
// Lookup table for the maximum number of bits required per character of a
// base-N string representation of a number. To increase accuracy, the array
// value is the actual value multiplied by 32. To generate this table:
// for (var i = 0; i <= 36; i++) { print(Math.ceil(Math.log2(i) * 32) + ","); }
constexpr uint8_t kMaxBitsPerChar[] = {
0, 0, 32, 51, 64, 75, 83, 90, 96, // 0..8
102, 107, 111, 115, 119, 122, 126, 128, // 9..16
131, 134, 136, 139, 141, 143, 145, 147, // 17..24
149, 151, 153, 154, 156, 158, 159, 160, // 25..32
162, 163, 165, 166, // 33..36
};
static const int kBitsPerCharTableShift = 5;
static const size_t kBitsPerCharTableMultiplier = 1u << kBitsPerCharTableShift;
void Terminate(Isolate* isolate) { isolate->TerminateExecution(); }
// {LocalIsolate} doesn't support interruption or termination.
void Terminate(LocalIsolate* isolate) { UNREACHABLE(); }
template <typename IsolateT>
MaybeHandle<FreshlyAllocatedBigInt> BigInt::AllocateFor(
IsolateT* isolate, int radix, int charcount, ShouldThrow should_throw,
AllocationType allocation) {
DCHECK(2 <= radix && radix <= 36);
DCHECK_GE(charcount, 0);
size_t bits_per_char = kMaxBitsPerChar[radix];
uint64_t chars = static_cast<uint64_t>(charcount);
const int roundup = kBitsPerCharTableMultiplier - 1;
if (chars <=
(std::numeric_limits<uint64_t>::max() - roundup) / bits_per_char) {
uint64_t bits_min = bits_per_char * chars;
// Divide by 32 (see table), rounding up.
bits_min = (bits_min + roundup) >> kBitsPerCharTableShift;
if (bits_min <= static_cast<uint64_t>(kMaxInt)) {
// Divide by kDigitsBits, rounding up.
int length = static_cast<int>((bits_min + kDigitBits - 1) / kDigitBits);
if (length <= kMaxLength) {
Handle<MutableBigInt> result =
MutableBigInt::New(isolate, length, allocation).ToHandleChecked();
result->InitializeDigits(length);
return result;
}
}
}
// All the overflow/maximum checks above fall through to here.
if (should_throw == kThrowOnError) {
return ThrowBigIntTooBig<FreshlyAllocatedBigInt>(isolate);
} else {
return MaybeHandle<FreshlyAllocatedBigInt>();
MaybeHandle<BigInt> BigInt::Allocate(IsolateT* isolate,
bigint::FromStringAccumulator* accumulator,
bool negative, AllocationType allocation) {
int digits = accumulator->ResultLength();
DCHECK_LE(digits, kMaxLength);
Handle<MutableBigInt> result =
MutableBigInt::New(isolate, digits, allocation).ToHandleChecked();
bigint::Status status =
isolate->bigint_processor()->FromString(GetRWDigits(result), accumulator);
if (status == bigint::Status::kInterrupted) {
Terminate(isolate);
return {};
}
result->set_sign(negative);
return MutableBigInt::MakeImmutable(result);
}
template MaybeHandle<FreshlyAllocatedBigInt> BigInt::AllocateFor(
Isolate* isolate, int radix, int charcount, ShouldThrow should_throw,
AllocationType allocation);
template MaybeHandle<FreshlyAllocatedBigInt> BigInt::AllocateFor(
LocalIsolate* isolate, int radix, int charcount, ShouldThrow should_throw,
AllocationType allocation);
template <typename IsolateT>
Handle<BigInt> BigInt::Finalize(Handle<FreshlyAllocatedBigInt> x, bool sign) {
Handle<MutableBigInt> bigint = Handle<MutableBigInt>::cast(x);
bigint->set_sign(sign);
return MutableBigInt::MakeImmutable<Isolate>(bigint);
}
template Handle<BigInt> BigInt::Finalize<Isolate>(
Handle<FreshlyAllocatedBigInt>, bool);
template Handle<BigInt> BigInt::Finalize<LocalIsolate>(
Handle<FreshlyAllocatedBigInt>, bool);
template MaybeHandle<BigInt> BigInt::Allocate(Isolate*,
bigint::FromStringAccumulator*,
bool, AllocationType);
template MaybeHandle<BigInt> BigInt::Allocate(LocalIsolate*,
bigint::FromStringAccumulator*,
bool, AllocationType);
// The serialization format MUST NOT CHANGE without updating the format
// version in value-serializer.cc!
......@@ -2056,43 +1969,6 @@ inline BigInt::digit_t MutableBigInt::digit_sub(digit_t a, digit_t b,
#endif
}
// Returns the low half of the result. High half is in {high}.
inline BigInt::digit_t MutableBigInt::digit_mul(digit_t a, digit_t b,
digit_t* high) {
#if HAVE_TWODIGIT_T
twodigit_t result = static_cast<twodigit_t>(a) * static_cast<twodigit_t>(b);
*high = result >> kDigitBits;
return static_cast<digit_t>(result);
#else
// Multiply in half-pointer-sized chunks.
// For inputs [AH AL]*[BH BL], the result is:
//
// [AL*BL] // r_low
// + [AL*BH] // r_mid1
// + [AH*BL] // r_mid2
// + [AH*BH] // r_high
// = [R4 R3 R2 R1] // high = [R4 R3], low = [R2 R1]
//
// Where of course we must be careful with carries between the columns.
digit_t a_low = a & kHalfDigitMask;
digit_t a_high = a >> kHalfDigitBits;
digit_t b_low = b & kHalfDigitMask;
digit_t b_high = b >> kHalfDigitBits;
digit_t r_low = a_low * b_low;
digit_t r_mid1 = a_low * b_high;
digit_t r_mid2 = a_high * b_low;
digit_t r_high = a_high * b_high;
digit_t carry = 0;
digit_t low = digit_add(r_low, r_mid1 << kHalfDigitBits, &carry);
low = digit_add(low, r_mid2 << kHalfDigitBits, &carry);
*high =
(r_mid1 >> kHalfDigitBits) + (r_mid2 >> kHalfDigitBits) + r_high + carry;
return low;
#endif
}
#undef HAVE_TWODIGIT_T
void MutableBigInt::set_64_bits(uint64_t bits) {
......
......@@ -14,6 +14,11 @@
#include "src/objects/object-macros.h"
namespace v8 {
namespace bigint {
class FromStringAccumulator;
} // namespace bigint
namespace internal {
void MutableBigInt_AbsoluteAddAndCanonicalize(Address result_addr,
......@@ -252,13 +257,9 @@ class BigInt : public BigIntBase {
static Handle<BigInt> Zero(
IsolateT* isolate, AllocationType allocation = AllocationType::kYoung);
template <typename IsolateT>
static MaybeHandle<FreshlyAllocatedBigInt> AllocateFor(
IsolateT* isolate, int radix, int charcount, ShouldThrow should_throw,
AllocationType allocation);
static void InplaceMultiplyAdd(FreshlyAllocatedBigInt x, uintptr_t factor,
uintptr_t summand);
template <typename IsolateT>
static Handle<BigInt> Finalize(Handle<FreshlyAllocatedBigInt> x, bool sign);
static MaybeHandle<BigInt> Allocate(
IsolateT* isolate, bigint::FromStringAccumulator* accumulator,
bool negative, AllocationType allocation);
// Special functions for ValueSerializer/ValueDeserializer:
uint32_t GetBitfieldForSerialization() const;
......
......@@ -239,6 +239,14 @@ TEST(TerminateBigIntToString) {
"fail();");
}
TEST(TerminateBigIntFromString) {
TestTerminatingSlowOperation(
"var a = '12344567890'.repeat(10000);\n"
"terminate();\n"
"BigInt(a);\n"
"fail();\n");
}
int call_count = 0;
......
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