From 81dd3f42bee9a6b3abb06cef4d47e209a3499844 Mon Sep 17 00:00:00 2001 From: Jakob Kummerow Date: Wed, 2 Jun 2021 15:20:29 +0200 Subject: [PATCH] Reland "[bigint] Karatsuba multiplication" This is a reland of 59eff3bfaa27eb183ac0b259d5c8ff3c19ce8534 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 > Reviewed-by: Michael Achenbach > Reviewed-by: Thibaud Michaud > 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 Commit-Queue: Jakob Kummerow Cr-Commit-Position: refs/heads/master@{#74922} --- BUILD.gn | 2 + src/bigint/bigint-internal.cc | 3 +- src/bigint/bigint-internal.h | 29 ++++ src/bigint/digit-arithmetic.h | 30 ++++ src/bigint/mul-karatsuba.cc | 189 +++++++++++++++++++++++ src/bigint/mul-schoolbook.cc | 2 - src/bigint/util.h | 60 ++++++++ src/bigint/vector-arithmetic.cc | 27 ++++ src/bigint/vector-arithmetic.h | 10 ++ test/BUILD.gn | 1 + test/bigint/BUILD.gn | 25 +++ test/bigint/DEPS | 3 + test/bigint/bigint-shell.cc | 263 ++++++++++++++++++++++++++++++++ test/bigint/bigint.status | 5 + test/bigint/testcfg.py | 63 ++++++++ tools/dev/gm.py | 8 +- 16 files changed, 714 insertions(+), 6 deletions(-) create mode 100644 src/bigint/mul-karatsuba.cc create mode 100644 src/bigint/util.h create mode 100644 test/bigint/BUILD.gn create mode 100644 test/bigint/DEPS create mode 100644 test/bigint/bigint-shell.cc create mode 100644 test/bigint/bigint.status create mode 100644 test/bigint/testcfg.py diff --git a/BUILD.gn b/BUILD.gn index da2e26a739..f2ae58edc4 100644 --- a/BUILD.gn +++ b/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", ] diff --git a/src/bigint/bigint-internal.cc b/src/bigint/bigint-internal.cc index 6630c6c4c9..37c71f301e 100644 --- a/src/bigint/bigint-internal.cc +++ b/src/bigint/bigint-internal.cc @@ -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) { diff --git a/src/bigint/bigint-internal.h b/src/bigint/bigint-internal.h index efe63a06a5..7fc5047097 100644 --- a/src/bigint/bigint-internal.h +++ b/src/bigint/bigint-internal.h @@ -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 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 diff --git a/src/bigint/digit-arithmetic.h b/src/bigint/digit-arithmetic.h index 1c5c93c035..a0d7372e46 100644 --- a/src/bigint/digit-arithmetic.h +++ b/src/bigint/digit-arithmetic.h @@ -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(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(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 diff --git a/src/bigint/mul-karatsuba.cc b/src/bigint/mul-karatsuba.cc new file mode 100644 index 0000000000..894f42b9cc --- /dev/null +++ b/src/bigint/mul-karatsuba.cc @@ -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 +#include + +#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 diff --git a/src/bigint/mul-schoolbook.cc b/src/bigint/mul-schoolbook.cc index 8e10685018..9222e1e675 100644 --- a/src/bigint/mul-schoolbook.cc +++ b/src/bigint/mul-schoolbook.cc @@ -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); diff --git a/src/bigint/util.h b/src/bigint/util.h new file mode 100644 index 0000000000..3c1f1cf9f5 --- /dev/null +++ b/src/bigint/util.h @@ -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 + +#include + +#ifdef _MSC_VER +#include // 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 ::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(n)); +} + +} // namespace bigint +} // namespace v8 + +#endif // V8_BIGINT_UTIL_H_ diff --git a/src/bigint/vector-arithmetic.cc b/src/bigint/vector-arithmetic.cc index 734b443911..8dad112ef0 100644 --- a/src/bigint/vector-arithmetic.cc +++ b/src/bigint/vector-arithmetic.cc @@ -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(); diff --git a/src/bigint/vector-arithmetic.h b/src/bigint/vector-arithmetic.h index 617cb20b55..5a168b7cb3 100644 --- a/src/bigint/vector-arithmetic.h +++ b/src/bigint/vector-arithmetic.h @@ -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 diff --git a/test/BUILD.gn b/test/BUILD.gn index d90a4c670f..241554e31b 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -19,6 +19,7 @@ group("gn_all") { ] deps = [ + "bigint:bigint_shell", "inspector:inspector-test", "mkgrokdump:mkgrokdump", ] diff --git a/test/bigint/BUILD.gn b/test/bigint/BUILD.gn new file mode 100644 index 0000000000..d3fbb76345 --- /dev/null +++ b/test/bigint/BUILD.gn @@ -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" ] +} diff --git a/test/bigint/DEPS b/test/bigint/DEPS new file mode 100644 index 0000000000..2fa1eb76b1 --- /dev/null +++ b/test/bigint/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+src/bigint", +] diff --git a/test/bigint/bigint-shell.cc b/test/bigint/bigint-shell.cc new file mode 100644 index 0000000000..490fbf0de6 --- /dev/null +++ b/test/bigint/bigint-shell.cc @@ -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] << " \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(seed)); + state1_ = MurmurHash3(~state0_); + CHECK(state0_ != 0 || state1_ != 0); + } + + uint64_t NextUint64() { + XorShift128(&state0_, &state1_); + return static_cast(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(""); + } + + std::unique_ptr 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(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(d >> 4u); + } + } + while (msd != 0) { + result[pos--] = kConversionChars[msd & 15]; + msd = static_cast(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(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(rng_.NextUint64()); + } + } else { + for (int i = 0; i < Z.len(); i += 2) { + uint64_t random = rng_.NextUint64(); + Z[i] = static_cast(random); + if (i + 1 < Z.len()) Z[i + 1] = static_cast(random >> 32); + } + } + // Special case: we don't want the MSD to be zero. + while (Z.msd() == 0) { + Z[Z.len() - 1] = static_cast(rng_.NextUint64()); + } + } + + Operation op_{kNoOp}; + Test test_; + bool error_{false}; + int runs_ = 1; + int64_t random_seed_{314159265359}; + RNG rng_; + std::unique_ptr 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(); +} diff --git a/test/bigint/bigint.status b/test/bigint/bigint.status new file mode 100644 index 0000000000..a96dcedcba --- /dev/null +++ b/test/bigint/bigint.status @@ -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. + +[] diff --git a/test/bigint/testcfg.py b/test/bigint/testcfg.py new file mode 100644 index 0000000000..edf42d81ed --- /dev/null +++ b/test/bigint/testcfg.py @@ -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) diff --git a/tools/dev/gm.py b/tools/dev/gm.py index 8a05da3cc9..e163311c65 100755 --- a/tools/dev/gm.py +++ b/tools/dev/gm.py @@ -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 = """ can be any of: %(arches)s "targets": ", ".join(TARGETS)} TESTSUITES_TARGETS = {"benchmarks": "d8", + "bigint": "bigint_shell", "cctest": "cctest", "debugger": "d8", "fuzzer": "v8_fuzzers",