[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: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Maya Lekova <mslekova@chromium.org>
Cr-Commit-Position: refs/heads/master@{#76022}
This commit is contained in:
Jakob Kummerow 2021-07-30 15:38:13 +02:00 committed by V8 LUCI CQ
parent 835a8b7de5
commit 152ecad8cd
11 changed files with 401 additions and 376 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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.

View File

@ -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_

42
src/bigint/fromstring.cc Normal file
View File

@ -0,0 +1,42 @@
// 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

View File

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

View File

@ -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>

View File

@ -12,6 +12,7 @@
#include "src/base/numbers/dtoa.h"
#include "src/base/numbers/strtod.h"
#include "src/base/platform/wrappers.h"
#include "src/bigint/bigint.h"
#include "src/common/assert-scope.h"
#include "src/handles/handles.h"
#include "src/heap/factory.h"
@ -310,38 +311,35 @@ class StringToIntHelper {
protected:
// Subclasses must implement these:
virtual void AllocateResult() = 0;
virtual void ResultMultiplyAdd(uint32_t multiplier, uint32_t part) = 0;
virtual void ParseOneByte(const uint8_t* start) = 0;
virtual void ParseTwoByte(const base::uc16* start) = 0;
// Subclasses must call this to do all the work.
void ParseInt();
// Subclasses may override this.
virtual bool CheckTermination() { return false; }
virtual void HandleSpecialCases() {}
// Subclass constructors should call these for configuration before calling
// ParseInt().
void set_allow_binary_and_octal_prefixes() {
allow_binary_and_octal_prefixes_ = true;
}
void set_disallow_trailing_junk() { allow_trailing_junk_ = false; }
bool allow_trailing_junk() { return allow_trailing_junk_; }
bool IsOneByte() const {
return raw_one_byte_subject_ != nullptr ||
String::IsOneByteRepresentationUnderneath(*subject_);
}
base::Vector<const uint8_t> GetOneByteVector() {
base::Vector<const uint8_t> GetOneByteVector(
const DisallowGarbageCollection& no_gc) {
if (raw_one_byte_subject_ != nullptr) {
return base::Vector<const uint8_t>(raw_one_byte_subject_, length_);
}
DisallowGarbageCollection no_gc;
return subject_->GetFlatContent(no_gc).ToOneByteVector();
}
base::Vector<const base::uc16> GetTwoByteVector() {
DisallowGarbageCollection no_gc;
base::Vector<const base::uc16> GetTwoByteVector(
const DisallowGarbageCollection& no_gc) {
return subject_->GetFlatContent(no_gc).ToUC16Vector();
}
@ -357,8 +355,6 @@ class StringToIntHelper {
private:
template <class Char>
void DetectRadixInternal(Char current, int length);
template <class Char>
bool ParseChunkInternal(Char start);
IsolateT* isolate_;
Handle<String> subject_;
@ -375,46 +371,18 @@ class StringToIntHelper {
template <typename IsolateT>
void StringToIntHelper<IsolateT>::ParseInt() {
{
DisallowGarbageCollection no_gc;
if (IsOneByte()) {
base::Vector<const uint8_t> vector = GetOneByteVector();
DetectRadixInternal(vector.begin(), vector.length());
} else {
base::Vector<const base::uc16> vector = GetTwoByteVector();
DetectRadixInternal(vector.begin(), vector.length());
}
DisallowGarbageCollection no_gc;
if (IsOneByte()) {
base::Vector<const uint8_t> vector = GetOneByteVector(no_gc);
DetectRadixInternal(vector.begin(), vector.length());
if (state_ != State::kRunning) return;
ParseOneByte(vector.begin());
} else {
base::Vector<const base::uc16> vector = GetTwoByteVector(no_gc);
DetectRadixInternal(vector.begin(), vector.length());
if (state_ != State::kRunning) return;
ParseTwoByte(vector.begin());
}
if (state_ != State::kRunning) return;
AllocateResult();
HandleSpecialCases();
if (state_ != State::kRunning) return;
do {
{
DisallowGarbageCollection no_gc;
if (IsOneByte()) {
base::Vector<const uint8_t> vector = GetOneByteVector();
DCHECK_EQ(length_, vector.length());
if (ParseChunkInternal(vector.begin())) {
break;
}
} else {
base::Vector<const base::uc16> vector = GetTwoByteVector();
DCHECK_EQ(length_, vector.length());
if (ParseChunkInternal(vector.begin())) {
break;
}
}
}
// The flat vector handle is temporarily released after parsing 10kb
// in order to invoke interrupts which may in turn invoke GC.
if (CheckTermination()) {
set_state(State::kError);
break;
}
} while (true);
DCHECK_NE(state_, State::kRunning);
}
template <typename IsolateT>
@ -497,22 +465,118 @@ void StringToIntHelper<IsolateT>::DetectRadixInternal(Char current,
cursor_ = static_cast<int>(current - start);
}
template <typename IsolateT>
template <class Char>
bool StringToIntHelper<IsolateT>::ParseChunkInternal(Char start) {
const int kChunkSize = 10240;
Char current = start + cursor_;
Char end = start + length_;
Char break_pos = current + kChunkSize;
class NumberParseIntHelper : public StringToIntHelper<Isolate> {
public:
NumberParseIntHelper(Isolate* isolate, Handle<String> string, int radix)
: StringToIntHelper(isolate, string, radix) {}
template <class Char>
void ParseInternal(Char start) {
Char current = start + cursor();
Char end = start + length();
if (radix() == 10) return HandleBaseTenCase(current, end);
if (base::bits::IsPowerOfTwo(radix())) {
result_ = HandlePowerOfTwoCase(current, end);
set_state(State::kDone);
return;
}
return HandleGenericCase(current, end);
}
void ParseOneByte(const uint8_t* start) final { return ParseInternal(start); }
void ParseTwoByte(const base::uc16* start) final {
return ParseInternal(start);
}
double GetResult() {
ParseInt();
switch (state()) {
case State::kJunk:
case State::kEmpty:
return JunkStringValue();
case State::kZero:
return SignedZero(negative());
case State::kDone:
return negative() ? -result_ : result_;
case State::kError:
case State::kRunning:
break;
}
UNREACHABLE();
}
private:
template <class Char>
void HandleGenericCase(Char current, Char end);
template <class Char>
double HandlePowerOfTwoCase(Char current, Char end) {
const bool allow_trailing_junk = true;
// GetResult() will take care of the sign bit, so ignore it for now.
const bool negative = false;
switch (radix()) {
case 2:
return InternalStringToIntDouble<1>(current, end, negative,
allow_trailing_junk);
case 4:
return InternalStringToIntDouble<2>(current, end, negative,
allow_trailing_junk);
case 8:
return InternalStringToIntDouble<3>(current, end, negative,
allow_trailing_junk);
case 16:
return InternalStringToIntDouble<4>(current, end, negative,
allow_trailing_junk);
case 32:
return InternalStringToIntDouble<5>(current, end, negative,
allow_trailing_junk);
default:
UNREACHABLE();
}
}
template <class Char>
void HandleBaseTenCase(Char current, Char end) {
// Parsing with strtod.
const int kMaxSignificantDigits = 309; // Doubles are less than 1.8e308.
// The buffer may contain up to kMaxSignificantDigits + 1 digits and a zero
// end.
const int kBufferSize = kMaxSignificantDigits + 2;
char buffer[kBufferSize];
int buffer_pos = 0;
while (*current >= '0' && *current <= '9') {
if (buffer_pos <= kMaxSignificantDigits) {
// If the number has more than kMaxSignificantDigits it will be parsed
// as infinity.
DCHECK_LT(buffer_pos, kBufferSize);
buffer[buffer_pos++] = static_cast<char>(*current);
}
++current;
if (current == end) break;
}
SLOW_DCHECK(buffer_pos < kBufferSize);
buffer[buffer_pos] = '\0';
base::Vector<const char> buffer_vector(buffer, buffer_pos);
result_ = Strtod(buffer_vector, 0);
set_state(State::kDone);
}
double result_ = 0;
};
template <class Char>
void NumberParseIntHelper::HandleGenericCase(Char current, Char end) {
// The following code causes accumulating rounding error for numbers greater
// than ~2^56. It's explicitly allowed in the spec: "if R is not 2, 4, 8, 10,
// 16, or 32, then mathInt may be an implementation-dependent approximation to
// the mathematical integer value" (15.1.2.2).
int lim_0 = '0' + (radix_ < 10 ? radix_ : 10);
int lim_a = 'a' + (radix_ - 10);
int lim_A = 'A' + (radix_ - 10);
int lim_0 = '0' + (radix() < 10 ? radix() : 10);
int lim_a = 'a' + (radix() - 10);
int lim_A = 'A' + (radix() - 10);
// NOTE: The code for computing the value may seem a bit complex at
// first glance. It is structured to use 32-bit multiply-and-add
@ -542,9 +606,9 @@ bool StringToIntHelper<IsolateT>::ParseChunkInternal(Char start) {
// will not overflow the multiplier, we stop parsing the part
// by leaving the loop.
const uint32_t kMaximumMultiplier = 0xFFFFFFFFU / 36;
uint32_t m = multiplier * static_cast<uint32_t>(radix_);
uint32_t m = multiplier * static_cast<uint32_t>(radix());
if (m > kMaximumMultiplier) break;
part = part * radix_ + d;
part = part * radix() + d;
multiplier = m;
DCHECK(multiplier > part);
@ -554,132 +618,14 @@ bool StringToIntHelper<IsolateT>::ParseChunkInternal(Char start) {
break;
}
}
// Update the value and skip the part in the string.
ResultMultiplyAdd(multiplier, part);
// Set final state
if (done) {
if (!allow_trailing_junk_ && AdvanceToNonspace(&current, end)) {
set_state(State::kJunk);
} else {
set_state(State::kDone);
}
return true;
}
} while (current < break_pos);
cursor_ = static_cast<int>(current - start);
return false;
}
class NumberParseIntHelper : public StringToIntHelper<Isolate> {
public:
NumberParseIntHelper(Isolate* isolate, Handle<String> string, int radix)
: StringToIntHelper(isolate, string, radix) {}
double GetResult() {
ParseInt();
switch (state()) {
case State::kJunk:
case State::kEmpty:
return JunkStringValue();
case State::kZero:
return SignedZero(negative());
case State::kDone:
return negative() ? -result_ : result_;
case State::kError:
case State::kRunning:
break;
}
UNREACHABLE();
}
protected:
void AllocateResult() override {}
void ResultMultiplyAdd(uint32_t multiplier, uint32_t part) override {
result_ = result_ * multiplier + part;
} while (!done);
if (!allow_trailing_junk() && AdvanceToNonspace(&current, end)) {
return set_state(State::kJunk);
}
private:
void HandleSpecialCases() override {
bool is_power_of_two = base::bits::IsPowerOfTwo(radix());
if (!is_power_of_two && radix() != 10) return;
DisallowGarbageCollection no_gc;
if (IsOneByte()) {
base::Vector<const uint8_t> vector = GetOneByteVector();
DCHECK_EQ(length(), vector.length());
result_ = is_power_of_two ? HandlePowerOfTwoCase(vector.begin())
: HandleBaseTenCase(vector.begin());
} else {
base::Vector<const base::uc16> vector = GetTwoByteVector();
DCHECK_EQ(length(), vector.length());
result_ = is_power_of_two ? HandlePowerOfTwoCase(vector.begin())
: HandleBaseTenCase(vector.begin());
}
set_state(State::kDone);
}
template <class Char>
double HandlePowerOfTwoCase(Char start) {
Char current = start + cursor();
Char end = start + length();
const bool allow_trailing_junk = true;
// GetResult() will take care of the sign bit, so ignore it for now.
const bool negative = false;
switch (radix()) {
case 2:
return InternalStringToIntDouble<1>(current, end, negative,
allow_trailing_junk);
case 4:
return InternalStringToIntDouble<2>(current, end, negative,
allow_trailing_junk);
case 8:
return InternalStringToIntDouble<3>(current, end, negative,
allow_trailing_junk);
case 16:
return InternalStringToIntDouble<4>(current, end, negative,
allow_trailing_junk);
case 32:
return InternalStringToIntDouble<5>(current, end, negative,
allow_trailing_junk);
default:
UNREACHABLE();
}
}
template <class Char>
double HandleBaseTenCase(Char start) {
// Parsing with strtod.
Char current = start + cursor();
Char end = start + length();
const int kMaxSignificantDigits = 309; // Doubles are less than 1.8e308.
// The buffer may contain up to kMaxSignificantDigits + 1 digits and a zero
// end.
const int kBufferSize = kMaxSignificantDigits + 2;
char buffer[kBufferSize];
int buffer_pos = 0;
while (*current >= '0' && *current <= '9') {
if (buffer_pos <= kMaxSignificantDigits) {
// If the number has more than kMaxSignificantDigits it will be parsed
// as infinity.
DCHECK_LT(buffer_pos, kBufferSize);
buffer[buffer_pos++] = static_cast<char>(*current);
}
++current;
if (current == end) break;
}
SLOW_DCHECK(buffer_pos < kBufferSize);
buffer[buffer_pos] = '\0';
base::Vector<const char> buffer_vector(buffer, buffer_pos);
return Strtod(buffer_vector, 0);
}
double result_ = 0;
};
return set_state(State::kDone);
}
// Converts a string to a double value. Assumes the Iterator supports
// the following operations:
@ -989,6 +935,11 @@ class StringToBigIntHelper : public StringToIntHelper<IsolateT> {
this->set_allow_binary_and_octal_prefixes();
}
void ParseOneByte(const uint8_t* start) final { return ParseInternal(start); }
void ParseTwoByte(const base::uc16* start) final {
return ParseInternal(start);
}
MaybeHandle<BigInt> GetResult() {
this->ParseInt();
if (behavior_ == Behavior::kStringToBigInt && this->sign() != Sign::kNone &&
@ -1009,7 +960,8 @@ class StringToBigIntHelper : public StringToIntHelper<IsolateT> {
case State::kZero:
return BigInt::Zero(this->isolate(), allocation_type());
case State::kDone:
return BigInt::Finalize<Isolate>(result_, this->negative());
return BigInt::Allocate(this->isolate(), accumulator_.get(),
this->negative(), allocation_type());
case State::kEmpty:
case State::kRunning:
break;
@ -1017,28 +969,35 @@ class StringToBigIntHelper : public StringToIntHelper<IsolateT> {
UNREACHABLE();
}
protected:
void AllocateResult() override {
// We have to allocate a BigInt that's big enough to fit the result.
// Conseratively assume that all remaining digits are significant.
// Optimization opportunity: Would it makes sense to scan for trailing
// junk before allocating the result?
int charcount = this->length() - this->cursor();
MaybeHandle<FreshlyAllocatedBigInt> maybe =
BigInt::AllocateFor(this->isolate(), this->radix(), charcount,
kDontThrow, allocation_type());
if (!maybe.ToHandle(&result_)) {
this->set_state(State::kError);
private:
template <class Char>
void ParseInternal(Char start) {
accumulator_.reset(
new bigint::FromStringAccumulator(this->radix(), BigInt::kMaxLength));
Char current = start + this->cursor();
Char end = start + this->length();
do {
using Result = bigint::FromStringAccumulator::Result;
Result result = accumulator_->ConsumeChar(*current);
if (result != Result::kOk) {
if (result == Result::kMaxSizeExceeded) {
this->set_state(State::kError);
} else {
DCHECK(result == Result::kInvalidChar);
}
break;
}
++current;
} while (current < end);
if (!this->allow_trailing_junk() && AdvanceToNonspace(&current, end)) {
return this->set_state(State::kJunk);
}
return this->set_state(State::kDone);
}
void ResultMultiplyAdd(uint32_t multiplier, uint32_t part) override {
BigInt::InplaceMultiplyAdd(*result_, static_cast<uintptr_t>(multiplier),
static_cast<uintptr_t>(part));
}
bool CheckTermination() override;
AllocationType allocation_type() {
// For literals, we pretenure the allocated BigInt, since it's about
// to be stored in the interpreter's constants array.
@ -1046,23 +1005,10 @@ class StringToBigIntHelper : public StringToIntHelper<IsolateT> {
: AllocationType::kYoung;
}
private:
Handle<FreshlyAllocatedBigInt> result_;
std::unique_ptr<bigint::FromStringAccumulator> accumulator_;
Behavior behavior_;
};
template <typename IsolateT>
bool StringToBigIntHelper<IsolateT>::CheckTermination() {
return false;
}
template <>
bool StringToBigIntHelper<Isolate>::CheckTermination() {
StackLimitCheck interrupt_check(isolate());
return interrupt_check.InterruptRequested() &&
isolate()->stack_guard()->HandleInterrupts().IsException(isolate());
}
MaybeHandle<BigInt> StringToBigInt(Isolate* isolate, Handle<String> string) {
string = String::Flatten(isolate, string);
StringToBigIntHelper<Isolate> helper(isolate, string);

View File

@ -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) {

View File

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

View File

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