Reland "[bigint] Karatsuba multiplication"
This is a reland of 59eff3bfaa
Original change's description:
> [bigint] Karatsuba multiplication
>
> The Karatsuba algorithm is used for BigInts with 34 or more internal
> digits, and thanks to better asymptotic complexity provides greater
> speedups the bigger the inputs.
>
> Bug: v8:11515
> Change-Id: I5ab0e318173ea4a02ced3f156d3c17e0259c5036
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2782283
> Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
> Reviewed-by: Michael Achenbach <machenbach@chromium.org>
> Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#74916}
Bug: v8:11515
Change-Id: I5ece2ff29ef11ea304980c053887d9746cfc80bc
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2933497
Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#74922}
This commit is contained in:
parent
3dd195240b
commit
81dd3f42be
2
BUILD.gn
2
BUILD.gn
@ -4898,7 +4898,9 @@ v8_source_set("v8_bigint") {
|
||||
"src/bigint/bigint-internal.h",
|
||||
"src/bigint/bigint.h",
|
||||
"src/bigint/digit-arithmetic.h",
|
||||
"src/bigint/mul-karatsuba.cc",
|
||||
"src/bigint/mul-schoolbook.cc",
|
||||
"src/bigint/util.h",
|
||||
"src/bigint/vector-arithmetic.cc",
|
||||
"src/bigint/vector-arithmetic.h",
|
||||
]
|
||||
|
@ -30,7 +30,8 @@ void ProcessorImpl::Multiply(RWDigits Z, Digits X, Digits Y) {
|
||||
if (X.len() == 0 || Y.len() == 0) return Z.Clear();
|
||||
if (X.len() < Y.len()) std::swap(X, Y);
|
||||
if (Y.len() == 1) return MultiplySingle(Z, X, Y[0]);
|
||||
return MultiplySchoolbook(Z, X, Y);
|
||||
if (Y.len() < kKaratsubaThreshold) return MultiplySchoolbook(Z, X, Y);
|
||||
return MultiplyKaratsuba(Z, X, Y);
|
||||
}
|
||||
|
||||
Status Processor::Multiply(RWDigits Z, Digits X, Digits Y) {
|
||||
|
@ -10,6 +10,8 @@
|
||||
namespace v8 {
|
||||
namespace bigint {
|
||||
|
||||
constexpr int kKaratsubaThreshold = 34;
|
||||
|
||||
class ProcessorImpl : public Processor {
|
||||
public:
|
||||
explicit ProcessorImpl(Platform* platform);
|
||||
@ -21,6 +23,11 @@ class ProcessorImpl : public Processor {
|
||||
void MultiplySingle(RWDigits Z, Digits X, digit_t y);
|
||||
void MultiplySchoolbook(RWDigits Z, Digits X, Digits Y);
|
||||
|
||||
void MultiplyKaratsuba(RWDigits Z, Digits X, Digits Y);
|
||||
void KaratsubaStart(RWDigits Z, Digits X, Digits Y, RWDigits scratch, int k);
|
||||
void KaratsubaChunk(RWDigits Z, Digits X, Digits Y, RWDigits scratch);
|
||||
void KaratsubaMain(RWDigits Z, Digits X, Digits Y, RWDigits scratch, int n);
|
||||
|
||||
private:
|
||||
// Each unit is supposed to represent approximately one CPU {mul} instruction.
|
||||
// Doesn't need to be accurate; we just want to make sure to check for
|
||||
@ -59,6 +66,28 @@ class ProcessorImpl : public Processor {
|
||||
#define DCHECK(cond) (void(0))
|
||||
#endif
|
||||
|
||||
// RAII memory for a Digits array.
|
||||
class Storage {
|
||||
public:
|
||||
explicit Storage(int count) : ptr_(new digit_t[count]) {}
|
||||
|
||||
digit_t* get() { return ptr_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<digit_t[]> ptr_;
|
||||
};
|
||||
|
||||
// A writable Digits array with attached storage.
|
||||
class ScratchDigits : public RWDigits {
|
||||
public:
|
||||
explicit ScratchDigits(int len) : RWDigits(nullptr, len), storage_(len) {
|
||||
digits_ = storage_.get();
|
||||
}
|
||||
|
||||
private:
|
||||
Storage storage_;
|
||||
};
|
||||
|
||||
} // namespace bigint
|
||||
} // namespace v8
|
||||
|
||||
|
@ -45,6 +45,36 @@ inline digit_t digit_add3(digit_t a, digit_t b, digit_t c, digit_t* carry) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// {borrow} will be set to 0 or 1.
|
||||
inline digit_t digit_sub(digit_t a, digit_t b, digit_t* borrow) {
|
||||
#if HAVE_TWODIGIT_T
|
||||
twodigit_t result = twodigit_t{a} - b;
|
||||
*borrow = (result >> kDigitBits) & 1;
|
||||
return static_cast<digit_t>(result);
|
||||
#else
|
||||
digit_t result = a - b;
|
||||
*borrow = (result > a) ? 1 : 0;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
// {borrow_out} will be set to 0 or 1.
|
||||
inline digit_t digit_sub2(digit_t a, digit_t b, digit_t borrow_in,
|
||||
digit_t* borrow_out) {
|
||||
#if HAVE_TWODIGIT_T
|
||||
twodigit_t subtrahend = twodigit_t{b} + borrow_in;
|
||||
twodigit_t result = twodigit_t{a} - subtrahend;
|
||||
*borrow_out = (result >> kDigitBits) & 1;
|
||||
return static_cast<digit_t>(result);
|
||||
#else
|
||||
digit_t result = a - b;
|
||||
*borrow_out = (result > a) ? 1 : 0;
|
||||
if (result < borrow_in) *borrow_out += 1;
|
||||
result -= borrow_in;
|
||||
return result;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns the low half of the result. High half is in {high}.
|
||||
inline digit_t digit_mul(digit_t a, digit_t b, digit_t* high) {
|
||||
#if HAVE_TWODIGIT_T
|
||||
|
189
src/bigint/mul-karatsuba.cc
Normal file
189
src/bigint/mul-karatsuba.cc
Normal file
@ -0,0 +1,189 @@
|
||||
// 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.
|
||||
|
||||
// Karatsuba multiplication. This is loosely based on Go's implementation
|
||||
// found at https://golang.org/src/math/big/nat.go, licensed as follows:
|
||||
//
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file [1].
|
||||
//
|
||||
// [1] https://golang.org/LICENSE
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#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 {
|
||||
|
||||
namespace {
|
||||
|
||||
// The Karatsuba algorithm sometimes finishes more quickly when the
|
||||
// input length is rounded up a bit. This method encodes some heuristics
|
||||
// to accomplish this. The details have been determined experimentally.
|
||||
int RoundUpLen(int len) {
|
||||
if (len <= 36) return RoundUp(len, 2);
|
||||
// Keep the 4 or 5 most significant non-zero bits.
|
||||
int shift = BitLength(len) - 5;
|
||||
if ((len >> shift) >= 0x18) {
|
||||
shift++;
|
||||
}
|
||||
// Round up, unless we're only just above the threshold. This smoothes
|
||||
// the steps by which time goes up as input size increases.
|
||||
int additive = ((1 << shift) - 1);
|
||||
if (shift >= 2 && (len & additive) < (1 << (shift - 2))) {
|
||||
return len;
|
||||
}
|
||||
return ((len + additive) >> shift) << shift;
|
||||
}
|
||||
|
||||
// This method makes the final decision how much to bump up the input size.
|
||||
int KaratsubaLength(int n) {
|
||||
n = RoundUpLen(n);
|
||||
int i = 0;
|
||||
while (n > kKaratsubaThreshold) {
|
||||
n >>= 1;
|
||||
i++;
|
||||
}
|
||||
return n << i;
|
||||
}
|
||||
|
||||
// Performs the specific subtraction required by {KaratsubaMain} below.
|
||||
void KaratsubaSubtractionHelper(RWDigits result, Digits X, Digits Y,
|
||||
int* sign) {
|
||||
X.Normalize();
|
||||
Y.Normalize();
|
||||
digit_t borrow = 0;
|
||||
int i = 0;
|
||||
if (!GreaterThanOrEqual(X, Y)) {
|
||||
*sign = -(*sign);
|
||||
std::swap(X, Y);
|
||||
}
|
||||
for (; i < Y.len(); i++) {
|
||||
result[i] = digit_sub2(X[i], Y[i], borrow, &borrow);
|
||||
}
|
||||
for (; i < X.len(); i++) {
|
||||
result[i] = digit_sub(X[i], borrow, &borrow);
|
||||
}
|
||||
DCHECK(borrow == 0); // NOLINT(readability/check)
|
||||
for (; i < result.len(); i++) result[i] = 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ProcessorImpl::MultiplyKaratsuba(RWDigits Z, Digits X, Digits Y) {
|
||||
DCHECK(X.len() >= Y.len());
|
||||
DCHECK(Y.len() >= kKaratsubaThreshold);
|
||||
DCHECK(Z.len() >= X.len() + Y.len());
|
||||
int k = KaratsubaLength(Y.len());
|
||||
int scratch_len = 4 * k;
|
||||
ScratchDigits scratch(scratch_len);
|
||||
KaratsubaStart(Z, X, Y, scratch, k);
|
||||
}
|
||||
|
||||
// Entry point for Karatsuba-based multiplication, takes care of inputs
|
||||
// with unequal lengths by chopping the larger into chunks.
|
||||
void ProcessorImpl::KaratsubaStart(RWDigits Z, Digits X, Digits Y,
|
||||
RWDigits scratch, int k) {
|
||||
KaratsubaMain(Z, X, Y, scratch, k);
|
||||
if (should_terminate()) return;
|
||||
for (int i = 2 * k; i < Z.len(); i++) Z[i] = 0;
|
||||
if (k < Y.len() || X.len() != Y.len()) {
|
||||
ScratchDigits T(2 * k);
|
||||
// Add X0 * Y1 * b.
|
||||
Digits X0(X, 0, k);
|
||||
Digits Y1 = Y + std::min(k, Y.len());
|
||||
if (Y1.len() > 0) {
|
||||
KaratsubaChunk(T, X0, Y1, scratch);
|
||||
if (should_terminate()) return;
|
||||
AddAt(Z + k, T);
|
||||
}
|
||||
|
||||
// Add Xi * Y0 << i and Xi * Y1 * b << (i + k).
|
||||
Digits Y0(Y, 0, k);
|
||||
for (int i = k; i < X.len(); i += k) {
|
||||
Digits Xi(X, i, k);
|
||||
KaratsubaChunk(T, Xi, Y0, scratch);
|
||||
if (should_terminate()) return;
|
||||
AddAt(Z + i, T);
|
||||
if (Y1.len() > 0) {
|
||||
KaratsubaChunk(T, Xi, Y1, scratch);
|
||||
if (should_terminate()) return;
|
||||
AddAt(Z + (i + k), T);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Entry point for chunk-wise multiplications, selects an appropriate
|
||||
// algorithm for the inputs based on their sizes.
|
||||
void ProcessorImpl::KaratsubaChunk(RWDigits Z, Digits X, Digits Y,
|
||||
RWDigits scratch) {
|
||||
X.Normalize();
|
||||
Y.Normalize();
|
||||
if (X.len() == 0 || Y.len() == 0) return Z.Clear();
|
||||
if (X.len() < Y.len()) std::swap(X, Y);
|
||||
if (Y.len() == 1) return MultiplySingle(Z, X, Y[0]);
|
||||
if (Y.len() < kKaratsubaThreshold) return MultiplySchoolbook(Z, X, Y);
|
||||
int k = KaratsubaLength(Y.len());
|
||||
DCHECK(scratch.len() >= 4 * k);
|
||||
return KaratsubaStart(Z, X, Y, scratch, k);
|
||||
}
|
||||
|
||||
// The main recursive Karatsuba method.
|
||||
void ProcessorImpl::KaratsubaMain(RWDigits Z, Digits X, Digits Y,
|
||||
RWDigits scratch, int n) {
|
||||
if (n < kKaratsubaThreshold) {
|
||||
X.Normalize();
|
||||
Y.Normalize();
|
||||
if (X.len() >= Y.len()) {
|
||||
return MultiplySchoolbook(RWDigits(Z, 0, 2 * n), X, Y);
|
||||
} else {
|
||||
return MultiplySchoolbook(RWDigits(Z, 0, 2 * n), Y, X);
|
||||
}
|
||||
}
|
||||
DCHECK(scratch.len() >= 4 * n);
|
||||
DCHECK((n & 1) == 0); // NOLINT(readability/check)
|
||||
int n2 = n >> 1;
|
||||
Digits X0(X, 0, n2);
|
||||
Digits X1(X, n2, n2);
|
||||
Digits Y0(Y, 0, n2);
|
||||
Digits Y1(Y, n2, n2);
|
||||
RWDigits scratch_for_recursion(scratch, 2 * n, 2 * n);
|
||||
RWDigits P0(scratch, 0, n);
|
||||
KaratsubaMain(P0, X0, Y0, scratch_for_recursion, n2);
|
||||
if (should_terminate()) return;
|
||||
for (int i = 0; i < n; i++) Z[i] = P0[i];
|
||||
RWDigits P2(scratch, n, n);
|
||||
KaratsubaMain(P2, X1, Y1, scratch_for_recursion, n2);
|
||||
if (should_terminate()) return;
|
||||
RWDigits Z1 = Z + n;
|
||||
int end = std::min(Z1.len(), P2.len());
|
||||
for (int i = 0; i < end; i++) Z1[i] = P2[i];
|
||||
for (int i = end; i < n; i++) {
|
||||
DCHECK(P2[i] == 0); // NOLINT(readability/check)
|
||||
}
|
||||
AddAt(Z + n2, P0);
|
||||
AddAt(Z + n2, P2);
|
||||
RWDigits X_diff(scratch, 0, n2);
|
||||
RWDigits Y_diff(scratch, n2, n2);
|
||||
int sign = 1;
|
||||
KaratsubaSubtractionHelper(X_diff, X1, X0, &sign);
|
||||
KaratsubaSubtractionHelper(Y_diff, Y0, Y1, &sign);
|
||||
RWDigits P1(scratch, n, n);
|
||||
KaratsubaMain(P1, X_diff, Y_diff, scratch_for_recursion, n2);
|
||||
if (sign > 0) {
|
||||
AddAt(Z + n2, P1);
|
||||
} else {
|
||||
SubAt(Z + n2, P1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace bigint
|
||||
} // namespace v8
|
@ -71,7 +71,6 @@ void ProcessorImpl::MultiplySchoolbook(RWDigits Z, Digits X, Digits Y) {
|
||||
next_carry = 0;
|
||||
BODY(0, i);
|
||||
AddWorkEstimate(i);
|
||||
if (should_terminate()) return;
|
||||
}
|
||||
// Last part: i exceeds Y now, we have to be careful about bounds.
|
||||
int loop_end = X.len() + Y.len() - 2;
|
||||
@ -85,7 +84,6 @@ void ProcessorImpl::MultiplySchoolbook(RWDigits Z, Digits X, Digits Y) {
|
||||
next_carry = 0;
|
||||
BODY(min_x_index, max_x_index);
|
||||
AddWorkEstimate(max_x_index - min_x_index);
|
||||
if (should_terminate()) return;
|
||||
}
|
||||
// Write the last digit, and zero out any extra space in Z.
|
||||
Z[i++] = digit_add2(next, carry, &carry);
|
||||
|
60
src/bigint/util.h
Normal file
60
src/bigint/util.h
Normal file
@ -0,0 +1,60 @@
|
||||
// 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.
|
||||
|
||||
// "Generic" helper functions (not specific to BigInts).
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h> // For _BitScanReverse.
|
||||
#endif
|
||||
|
||||
#ifndef V8_BIGINT_UTIL_H_
|
||||
#define V8_BIGINT_UTIL_H_
|
||||
|
||||
// Integer division, rounding up.
|
||||
#define DIV_CEIL(x, y) (((x)-1) / (y) + 1)
|
||||
|
||||
namespace v8 {
|
||||
namespace bigint {
|
||||
|
||||
// Rounds up x to a multiple of y.
|
||||
inline constexpr int RoundUp(int x, int y) { return (x + y - 1) & -y; }
|
||||
|
||||
// Different environments disagree on how 64-bit uintptr_t and uint64_t are
|
||||
// defined, so we have to use templates to be generic.
|
||||
template <typename T, typename = typename std::enable_if<
|
||||
std::is_unsigned<T>::value && sizeof(T) == 8>::type>
|
||||
constexpr int CountLeadingZeros(T value) {
|
||||
#if __GNUC__ || __clang__
|
||||
return value == 0 ? 64 : __builtin_clzll(value);
|
||||
#elif _MSC_VER
|
||||
unsigned long index = 0; // NOLINT(runtime/int). MSVC insists.
|
||||
return _BitScanReverse64(&index, value) ? 63 - index : 64;
|
||||
#else
|
||||
#error Unsupported compiler.
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr int CountLeadingZeros(uint32_t value) {
|
||||
#if __GNUC__ || __clang__
|
||||
return value == 0 ? 32 : __builtin_clz(value);
|
||||
#elif _MSC_VER
|
||||
unsigned long index = 0; // NOLINT(runtime/int). MSVC insists.
|
||||
return _BitScanReverse(&index, value) ? 31 - index : 32;
|
||||
#else
|
||||
#error Unsupported compiler.
|
||||
#endif
|
||||
}
|
||||
|
||||
inline constexpr int BitLength(int n) {
|
||||
return 32 - CountLeadingZeros(static_cast<uint32_t>(n));
|
||||
}
|
||||
|
||||
} // namespace bigint
|
||||
} // namespace v8
|
||||
|
||||
#endif // V8_BIGINT_UTIL_H_
|
@ -4,9 +4,36 @@
|
||||
|
||||
#include "src/bigint/vector-arithmetic.h"
|
||||
|
||||
#include "src/bigint/digit-arithmetic.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace bigint {
|
||||
|
||||
void AddAt(RWDigits Z, Digits X) {
|
||||
X.Normalize();
|
||||
if (X.len() == 0) return;
|
||||
digit_t carry = 0;
|
||||
int i = 0;
|
||||
for (; i < X.len(); i++) {
|
||||
Z[i] = digit_add3(Z[i], X[i], carry, &carry);
|
||||
}
|
||||
for (; carry != 0; i++) {
|
||||
Z[i] = digit_add2(Z[i], carry, &carry);
|
||||
}
|
||||
}
|
||||
|
||||
void SubAt(RWDigits Z, Digits X) {
|
||||
X.Normalize();
|
||||
digit_t borrow = 0;
|
||||
int i = 0;
|
||||
for (; i < X.len(); i++) {
|
||||
Z[i] = digit_sub2(Z[i], X[i], borrow, &borrow);
|
||||
}
|
||||
for (; borrow != 0; i++) {
|
||||
Z[i] = digit_sub(Z[i], borrow, &borrow);
|
||||
}
|
||||
}
|
||||
|
||||
int Compare(Digits A, Digits B) {
|
||||
A.Normalize();
|
||||
B.Normalize();
|
||||
|
@ -12,8 +12,18 @@
|
||||
namespace v8 {
|
||||
namespace bigint {
|
||||
|
||||
// Z += X.
|
||||
void AddAt(RWDigits Z, Digits X);
|
||||
|
||||
// Z -= X.
|
||||
void SubAt(RWDigits Z, Digits X);
|
||||
|
||||
inline bool IsDigitNormalized(Digits X) { return X.len() == 0 || X.msd() != 0; }
|
||||
|
||||
inline bool GreaterThanOrEqual(Digits A, Digits B) {
|
||||
return Compare(A, B) >= 0;
|
||||
}
|
||||
|
||||
} // namespace bigint
|
||||
} // namespace v8
|
||||
|
||||
|
@ -19,6 +19,7 @@ group("gn_all") {
|
||||
]
|
||||
|
||||
deps = [
|
||||
"bigint:bigint_shell",
|
||||
"inspector:inspector-test",
|
||||
"mkgrokdump:mkgrokdump",
|
||||
]
|
||||
|
25
test/bigint/BUILD.gn
Normal file
25
test/bigint/BUILD.gn
Normal file
@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
|
||||
import("../../gni/v8.gni")
|
||||
|
||||
v8_executable("bigint_shell") {
|
||||
testonly = true
|
||||
|
||||
deps = [
|
||||
"../..:v8_bigint",
|
||||
"//build/win:default_exe_manifest",
|
||||
]
|
||||
|
||||
data_deps = [ "../../tools:v8_testrunner" ]
|
||||
|
||||
data = [
|
||||
"testcfg.py",
|
||||
"bigint.status",
|
||||
]
|
||||
|
||||
configs = [ "../..:internal_config_base" ]
|
||||
|
||||
sources = [ "bigint-shell.cc" ]
|
||||
}
|
3
test/bigint/DEPS
Normal file
3
test/bigint/DEPS
Normal file
@ -0,0 +1,3 @@
|
||||
include_rules = [
|
||||
"+src/bigint",
|
||||
]
|
263
test/bigint/bigint-shell.cc
Normal file
263
test/bigint/bigint-shell.cc
Normal file
@ -0,0 +1,263 @@
|
||||
// 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/util.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace bigint {
|
||||
namespace test {
|
||||
|
||||
int PrintHelp(char** argv) {
|
||||
std::cerr << "Usage:\n"
|
||||
<< argv[0] << " --help\n"
|
||||
<< " Print this help and exit.\n"
|
||||
<< argv[0] << " --list\n"
|
||||
<< " List supported tests.\n"
|
||||
<< argv[0] << " <testname>\n"
|
||||
<< " Run the specified test (see --list for a list).\n"
|
||||
<< "\nOptions when running tests:\n"
|
||||
<< "--random-seed R\n"
|
||||
<< " Initialize the random number generator with this seed.\n"
|
||||
<< "--runs N\n"
|
||||
<< " Repeat the test N times.\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define TESTS(V) V(kKaratsuba, "karatsuba")
|
||||
|
||||
enum Operation { kNoOp, kList, kTest };
|
||||
|
||||
enum Test {
|
||||
#define TEST(kName, name) kName,
|
||||
TESTS(TEST)
|
||||
#undef TEST
|
||||
};
|
||||
|
||||
class RNG {
|
||||
public:
|
||||
RNG() = default;
|
||||
|
||||
void Initialize(int64_t seed) {
|
||||
state0_ = MurmurHash3(static_cast<uint64_t>(seed));
|
||||
state1_ = MurmurHash3(~state0_);
|
||||
CHECK(state0_ != 0 || state1_ != 0);
|
||||
}
|
||||
|
||||
uint64_t NextUint64() {
|
||||
XorShift128(&state0_, &state1_);
|
||||
return static_cast<uint64_t>(state0_ + state1_);
|
||||
}
|
||||
|
||||
static inline void XorShift128(uint64_t* state0, uint64_t* state1) {
|
||||
uint64_t s1 = *state0;
|
||||
uint64_t s0 = *state1;
|
||||
*state0 = s0;
|
||||
s1 ^= s1 << 23;
|
||||
s1 ^= s1 >> 17;
|
||||
s1 ^= s0;
|
||||
s1 ^= s0 >> 26;
|
||||
*state1 = s1;
|
||||
}
|
||||
|
||||
static uint64_t MurmurHash3(uint64_t h) {
|
||||
h ^= h >> 33;
|
||||
h *= uint64_t{0xFF51AFD7ED558CCD};
|
||||
h ^= h >> 33;
|
||||
h *= uint64_t{0xC4CEB9FE1A85EC53};
|
||||
h ^= h >> 33;
|
||||
return h;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t state0_;
|
||||
uint64_t state1_;
|
||||
};
|
||||
|
||||
static constexpr int kCharsPerDigit = kDigitBits / 4;
|
||||
|
||||
static const char kConversionChars[] = "0123456789abcdef";
|
||||
|
||||
std::string FormatHex(Digits X) {
|
||||
X.Normalize();
|
||||
if (X.len() == 0) return "0";
|
||||
digit_t msd = X.msd();
|
||||
const int msd_leading_zeros = CountLeadingZeros(msd);
|
||||
const size_t bit_length = X.len() * kDigitBits - msd_leading_zeros;
|
||||
const size_t chars = DIV_CEIL(bit_length, 4);
|
||||
|
||||
if (chars > 100000) {
|
||||
return std::string("<BigInt with ") + std::to_string(bit_length) +
|
||||
std::string(" bits>");
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> result(new char[chars]);
|
||||
for (size_t i = 0; i < chars; i++) result[i] = '?';
|
||||
// Print the number into the string, starting from the last position.
|
||||
int pos = static_cast<int>(chars - 1);
|
||||
for (int i = 0; i < X.len() - 1; i++) {
|
||||
digit_t d = X[i];
|
||||
for (int j = 0; j < kCharsPerDigit; j++) {
|
||||
result[pos--] = kConversionChars[d & 15];
|
||||
d = static_cast<digit_t>(d >> 4u);
|
||||
}
|
||||
}
|
||||
while (msd != 0) {
|
||||
result[pos--] = kConversionChars[msd & 15];
|
||||
msd = static_cast<digit_t>(msd >> 4u);
|
||||
}
|
||||
CHECK(pos == -1);
|
||||
return std::string(result.get(), chars);
|
||||
}
|
||||
|
||||
class Runner {
|
||||
public:
|
||||
Runner() = default;
|
||||
|
||||
void Initialize() {
|
||||
rng_.Initialize(random_seed_);
|
||||
processor_.reset(Processor::New(new Platform()));
|
||||
}
|
||||
|
||||
ProcessorImpl* processor() {
|
||||
return static_cast<ProcessorImpl*>(processor_.get());
|
||||
}
|
||||
|
||||
int Run() {
|
||||
if (op_ == kList) {
|
||||
ListTests();
|
||||
} else if (op_ == kTest) {
|
||||
RunTest();
|
||||
} else {
|
||||
DCHECK(false); // Unreachable.
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ListTests() {
|
||||
#define PRINT(kName, name) std::cout << name << "\n";
|
||||
TESTS(PRINT)
|
||||
#undef PRINT
|
||||
}
|
||||
|
||||
void AssertEquals(Digits input1, Digits input2, Digits expected,
|
||||
Digits actual) {
|
||||
if (Compare(expected, actual) == 0) return;
|
||||
std::cerr << "Input 1: " << FormatHex(input1) << "\n";
|
||||
std::cerr << "Input 2: " << FormatHex(input2) << "\n";
|
||||
std::cerr << "Expected: " << FormatHex(expected) << "\n";
|
||||
std::cerr << "Actual: " << FormatHex(actual) << "\n";
|
||||
error_ = true;
|
||||
}
|
||||
|
||||
int RunTest() {
|
||||
int count = 0;
|
||||
if (test_ == kKaratsuba) {
|
||||
for (int i = 0; i < runs_; i++) {
|
||||
TestKaratsuba(&count);
|
||||
}
|
||||
} else {
|
||||
DCHECK(false); // Unreachable.
|
||||
}
|
||||
if (error_) return 1;
|
||||
std::cout << count << " tests run, no error reported.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TestKaratsuba(int* count) {
|
||||
// Calling {MultiplyKaratsuba} directly is only valid if
|
||||
// left_size >= right_size and right_size >= kKaratsubaThreshold.
|
||||
for (int right_size = kKaratsubaThreshold;
|
||||
right_size <= 3 * kKaratsubaThreshold; right_size++) {
|
||||
for (int left_size = right_size; left_size <= 3 * kKaratsubaThreshold;
|
||||
left_size++) {
|
||||
ScratchDigits A(left_size);
|
||||
ScratchDigits B(right_size);
|
||||
int result_len = MultiplyResultLength(A, B);
|
||||
ScratchDigits result(result_len);
|
||||
ScratchDigits result_schoolbook(result_len);
|
||||
GenerateRandom(A);
|
||||
GenerateRandom(B);
|
||||
processor()->MultiplyKaratsuba(result, A, B);
|
||||
processor()->MultiplySchoolbook(result_schoolbook, A, B);
|
||||
AssertEquals(A, B, result_schoolbook, result);
|
||||
if (error_) return;
|
||||
(*count)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ParseOptions(int argc, char** argv) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--list") == 0) {
|
||||
op_ = kList;
|
||||
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||
PrintHelp(argv);
|
||||
return 0;
|
||||
} else if (strcmp(argv[i], "--random-seed") == 0 ||
|
||||
strcmp(argv[i], "--random_seed") == 0) {
|
||||
random_seed_ = std::stoi(argv[++i]);
|
||||
} else if (strncmp(argv[i], "--random-seed=", 14) == 0 ||
|
||||
strncmp(argv[i], "--random_seed=", 14) == 0) {
|
||||
random_seed_ = std::stoi(argv[i] + 14);
|
||||
} else if (strcmp(argv[i], "--runs") == 0) {
|
||||
runs_ = std::stoi(argv[++i]);
|
||||
} else if (strncmp(argv[i], "--runs=", 7) == 0) {
|
||||
runs_ = std::stoi(argv[i] + 7);
|
||||
}
|
||||
#define TEST(kName, name) \
|
||||
else if (strcmp(argv[i], name) == 0) { \
|
||||
op_ = kTest; \
|
||||
test_ = kName; \
|
||||
}
|
||||
TESTS(TEST)
|
||||
#undef TEST
|
||||
else {
|
||||
std::cerr << "Warning: ignored argument: " << argv[i] << "\n";
|
||||
}
|
||||
}
|
||||
if (op_ == kNoOp) return PrintHelp(argv); // op is mandatory.
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void GenerateRandom(RWDigits Z) {
|
||||
if (Z.len() == 0) return;
|
||||
if (sizeof(digit_t) == 8) {
|
||||
for (int i = 0; i < Z.len(); i++) {
|
||||
Z[i] = static_cast<digit_t>(rng_.NextUint64());
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < Z.len(); i += 2) {
|
||||
uint64_t random = rng_.NextUint64();
|
||||
Z[i] = static_cast<digit_t>(random);
|
||||
if (i + 1 < Z.len()) Z[i + 1] = static_cast<digit_t>(random >> 32);
|
||||
}
|
||||
}
|
||||
// Special case: we don't want the MSD to be zero.
|
||||
while (Z.msd() == 0) {
|
||||
Z[Z.len() - 1] = static_cast<digit_t>(rng_.NextUint64());
|
||||
}
|
||||
}
|
||||
|
||||
Operation op_{kNoOp};
|
||||
Test test_;
|
||||
bool error_{false};
|
||||
int runs_ = 1;
|
||||
int64_t random_seed_{314159265359};
|
||||
RNG rng_;
|
||||
std::unique_ptr<Processor, Processor::Destroyer> processor_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
} // namespace bigint
|
||||
} // namespace v8
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
v8::bigint::test::Runner runner;
|
||||
int ret = runner.ParseOptions(argc, argv);
|
||||
if (ret != 0) return ret;
|
||||
runner.Initialize();
|
||||
return runner.Run();
|
||||
}
|
5
test/bigint/bigint.status
Normal file
5
test/bigint/bigint.status
Normal file
@ -0,0 +1,5 @@
|
||||
# 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.
|
||||
|
||||
[]
|
63
test/bigint/testcfg.py
Normal file
63
test/bigint/testcfg.py
Normal file
@ -0,0 +1,63 @@
|
||||
# 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.
|
||||
|
||||
# for py2/py3 compatibility
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
|
||||
from testrunner.local import command
|
||||
from testrunner.local import utils
|
||||
from testrunner.local import testsuite
|
||||
from testrunner.objects import testcase
|
||||
|
||||
SHELL = 'bigint_shell'
|
||||
|
||||
class VariantsGenerator(testsuite.VariantsGenerator):
|
||||
def _get_variants(self, test):
|
||||
return self._standard_variant
|
||||
|
||||
|
||||
class TestLoader(testsuite.TestLoader):
|
||||
def _list_test_filenames(self):
|
||||
shell = os.path.abspath(
|
||||
os.path.join(self.test_config.shell_dir, SHELL))
|
||||
if utils.IsWindows():
|
||||
shell += ".exe"
|
||||
cmd = command.Command(
|
||||
cmd_prefix=self.test_config.command_prefix,
|
||||
shell=shell,
|
||||
args=['--list'] + self.test_config.extra_flags)
|
||||
output = cmd.execute()
|
||||
|
||||
if output.exit_code != 0:
|
||||
print("Test executable failed to list the tests.\n\nCmd:")
|
||||
print(cmd)
|
||||
print("\nStdout:")
|
||||
print(output.stdout)
|
||||
print("\nStderr:")
|
||||
print(output.stderr)
|
||||
print("\nExit code: %d" % output.exit_code)
|
||||
|
||||
return sorted(output.stdout.strip().split())
|
||||
|
||||
|
||||
class TestSuite(testsuite.TestSuite):
|
||||
def _test_loader_class(self):
|
||||
return TestLoader
|
||||
|
||||
def _test_class(self):
|
||||
return TestCase
|
||||
|
||||
|
||||
class TestCase(testcase.TestCase):
|
||||
def _get_files_params(self):
|
||||
return [self.path]
|
||||
|
||||
def get_shell(self):
|
||||
return SHELL
|
||||
|
||||
|
||||
def GetSuite(*args, **kwargs):
|
||||
return TestSuite(*args, **kwargs)
|
@ -36,8 +36,8 @@ USE_PTY = "linux" in sys.platform
|
||||
if USE_PTY:
|
||||
import pty
|
||||
|
||||
BUILD_TARGETS_TEST = ["d8", "cctest", "inspector-test", "unittests",
|
||||
"wasm_api_tests"]
|
||||
BUILD_TARGETS_TEST = ["d8", "bigint_shell", "cctest", "inspector-test",
|
||||
"unittests", "wasm_api_tests"]
|
||||
BUILD_TARGETS_ALL = ["all"]
|
||||
|
||||
# All arches that this script understands.
|
||||
@ -51,7 +51,8 @@ MODES = ["release", "debug", "optdebug"]
|
||||
DEFAULT_MODES = ["release", "debug"]
|
||||
# Build targets that can be manually specified.
|
||||
TARGETS = ["d8", "cctest", "unittests", "v8_fuzzers", "wasm_api_tests", "wee8",
|
||||
"mkgrokdump", "generate-bytecode-expectations", "inspector-test"]
|
||||
"mkgrokdump", "generate-bytecode-expectations", "inspector-test",
|
||||
"bigint_shell"]
|
||||
# Build targets that get built when you don't specify any (and specified tests
|
||||
# don't imply any other targets).
|
||||
DEFAULT_TARGETS = ["d8"]
|
||||
@ -81,6 +82,7 @@ HELP = """<arch> can be any of: %(arches)s
|
||||
"targets": ", ".join(TARGETS)}
|
||||
|
||||
TESTSUITES_TARGETS = {"benchmarks": "d8",
|
||||
"bigint": "bigint_shell",
|
||||
"cctest": "cctest",
|
||||
"debugger": "d8",
|
||||
"fuzzer": "v8_fuzzers",
|
||||
|
Loading…
Reference in New Issue
Block a user