| // 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/digit-arithmetic.h" |
| #include "src/bigint/vector-arithmetic.h" |
| |
| namespace v8 { |
| namespace bigint { |
| |
| // Z := X * y, where y is a single digit. |
| void ProcessorImpl::MultiplySingle(RWDigits Z, Digits X, digit_t y) { |
| DCHECK(y != 0); |
| digit_t carry = 0; |
| digit_t high = 0; |
| for (int i = 0; i < X.len(); i++) { |
| digit_t new_high; |
| digit_t low = digit_mul(X[i], y, &new_high); |
| Z[i] = digit_add3(low, high, carry, &carry); |
| high = new_high; |
| } |
| AddWorkEstimate(X.len()); |
| Z[X.len()] = carry + high; |
| for (int i = X.len() + 1; i < Z.len(); i++) Z[i] = 0; |
| } |
| |
| #define BODY(min, max) \ |
| for (int j = min; j <= max; j++) { \ |
| digit_t high; \ |
| digit_t low = digit_mul(X[j], Y[i - j], &high); \ |
| digit_t carrybit; \ |
| zi = digit_add2(zi, low, &carrybit); \ |
| carry += carrybit; \ |
| next = digit_add2(next, high, &carrybit); \ |
| next_carry += carrybit; \ |
| } \ |
| Z[i] = zi |
| |
| // Z := X * Y. |
| // O(n²) "schoolbook" multiplication algorithm. Optimized to minimize |
| // bounds and overflow checks: rather than looping over X for every digit |
| // of Y (or vice versa), we loop over Z. The {BODY} macro above is what |
| // computes one of Z's digits as a sum of the products of relevant digits |
| // of X and Y. This yields a nearly 2x improvement compared to more obvious |
| // implementations. |
| // This method is *highly* performance sensitive even for the advanced |
| // algorithms, which use this as the base case of their recursive calls. |
| void ProcessorImpl::MultiplySchoolbook(RWDigits Z, Digits X, Digits Y) { |
| DCHECK(IsDigitNormalized(X)); |
| DCHECK(IsDigitNormalized(Y)); |
| DCHECK(X.len() >= Y.len()); |
| DCHECK(Z.len() >= X.len() + Y.len()); |
| if (X.len() == 0 || Y.len() == 0) return Z.Clear(); |
| digit_t next, next_carry = 0, carry = 0; |
| // Unrolled first iteration: it's trivial. |
| Z[0] = digit_mul(X[0], Y[0], &next); |
| int i = 1; |
| // Unrolled second iteration: a little less setup. |
| if (i < Y.len()) { |
| digit_t zi = next; |
| next = 0; |
| BODY(0, 1); |
| i++; |
| } |
| // Main part: since X.len() >= Y.len() > i, no bounds checks are needed. |
| for (; i < Y.len(); i++) { |
| digit_t zi = digit_add2(next, carry, &carry); |
| next = next_carry + carry; |
| carry = 0; |
| next_carry = 0; |
| BODY(0, i); |
| AddWorkEstimate(i); |
| } |
| // Last part: i exceeds Y now, we have to be careful about bounds. |
| int loop_end = X.len() + Y.len() - 2; |
| for (; i <= loop_end; i++) { |
| int max_x_index = std::min(i, X.len() - 1); |
| int max_y_index = Y.len() - 1; |
| int min_x_index = i - max_y_index; |
| digit_t zi = digit_add2(next, carry, &carry); |
| next = next_carry + carry; |
| carry = 0; |
| next_carry = 0; |
| BODY(min_x_index, max_x_index); |
| AddWorkEstimate(max_x_index - min_x_index); |
| } |
| // Write the last digit, and zero out any extra space in Z. |
| Z[i++] = digit_add2(next, carry, &carry); |
| DCHECK(carry == 0); |
| for (; i < Z.len(); i++) Z[i] = 0; |
| } |
| |
| #undef BODY |
| |
| } // namespace bigint |
| } // namespace v8 |