Skip to content

Commit f0b2cc0

Browse files
authored
crypto: Optimize first iteration of multi-word multiplications (#1472)
Add mul(r, x, y) — multiply multi-word x by single word y, returning the carry. Use it in: - mul(r, x, y[]): first iteration of multi-word multiply, replacing addmul on zero-filled r and eliminating the fill entirely. - mul_amm: first CIOS iteration where t[] is zero, peeled out of the main loop.
1 parent fceb387 commit f0b2cc0

1 file changed

Lines changed: 42 additions & 12 deletions

File tree

lib/evmone_precompiles/modexp.cpp

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
using namespace intx;
1212

13+
namespace evmone::crypto
14+
{
1315
namespace
1416
{
1517
/// Adds y to x: x[] += y[]. The result is truncated to the size of x. Returns the carry bit.
@@ -37,12 +39,25 @@ constexpr void sub(std::span<uint64_t> x, std::span<const uint64_t> y) noexcept
3739
std::tie(x[i], borrow) = subc(x[i], uint64_t{0}, borrow);
3840
}
3941

42+
/// Multiplies multi-word x by single word y: r[] = x[] * y. Returns the carry word.
43+
constexpr uint64_t mul(std::span<uint64_t> r, std::span<const uint64_t> x, uint64_t y) noexcept
44+
{
45+
assert(r.size() == x.size());
46+
47+
uint64_t c = 0;
48+
#pragma GCC unroll 4
49+
for (size_t i = 0; i != x.size(); ++i)
50+
{
51+
const auto p = umul(x[i], y) + c;
52+
r[i] = p[0];
53+
c = p[1];
54+
}
55+
return c;
56+
}
57+
4058
/// Multiplies each word of x by y and adds the matching word of p, propagating a carry to the next
4159
/// word. Starts with initial carry c. Stores the result in r. Returns the final carry.
4260
/// r[] = p[] + x[] * y (+ c).
43-
/// TODO: Consider [[always_inline]].
44-
/// TODO: Consider template by the span extent.
45-
/// TODO: Consider using pointers for some spans.
4661
constexpr uint64_t addmul(std::span<uint64_t> r, std::span<const uint64_t> p,
4762
std::span<const uint64_t> x, uint64_t y, uint64_t c = 0) noexcept
4863
{
@@ -67,18 +82,24 @@ constexpr void mul(
6782
assert(!x.empty());
6883
assert(!y.empty());
6984
assert(r.size() >= std::max(x.size(), y.size()));
85+
assert(r.size() <= x.size() + y.size()); // No support for zeroing r tail.
7086

7187
// Ensure y is the shorter one to simplify the implementation and to have shorter outer loop.
7288
if (x.size() < y.size())
7389
std::swap(x, y);
7490

75-
// Iterations where we store high product words (above x/y size).
76-
const auto extra = std::min(y.size(), r.size() - x.size());
91+
// First iteration: use mul (not addmul) since r is uninitialized.
92+
const auto hi0 = mul(r.first(x.size()), x, y[0]);
93+
if (r.size() > x.size())
94+
r[x.size()] = hi0;
7795

78-
std::ranges::fill(r, 0);
79-
for (size_t j = 0; j < extra; ++j)
96+
// Growing phase: each iteration produces a new high word at r[j + x.size()].
97+
const auto hi_iters = std::min(y.size(), r.size() - x.size());
98+
for (size_t j = 1; j < hi_iters; ++j)
8099
r[j + x.size()] = addmul(r.subspan(j, x.size()), r.subspan(j, x.size()), x, y[j]);
81-
for (size_t j = extra; j < y.size(); ++j)
100+
101+
// Truncating phase: product is wider than r, discard high words.
102+
for (size_t j = std::max(hi_iters, size_t{1}); j < y.size(); ++j)
82103
addmul(r.subspan(j), r.subspan(j), x.first(r.size() - j), y[j]);
83104
}
84105

@@ -344,9 +365,20 @@ void mul_amm(std::span<uint64_t> r, std::span<const uint64_t> x, std::span<const
344365
const auto r_hi = r.subspan(1);
345366
const auto mod_hi = mod.subspan(1);
346367

347-
std::ranges::fill(r, uint64_t{0});
368+
// First iteration: r is uninitialized, so use mul instead of addmul.
348369
bool r_carry = false;
349-
for (size_t i = 0; i != n; ++i)
370+
{
371+
const auto c1 = mul(r, x, y[0]);
372+
373+
const auto m = r[0] * mod_inv;
374+
const auto c2 = (umul(mod[0], m) + r[0])[1];
375+
376+
const auto c3 = addmul(r_lo, r_hi, mod_hi, m, c2);
377+
std::tie(r[n - 1], r_carry) = intx::addc(c1, c3);
378+
}
379+
380+
// Remaining iterations.
381+
for (size_t i = 1; i != n; ++i)
350382
{
351383
const auto c1 = addmul(r, r, x, y[i]);
352384
const auto [sum1, d1] = intx::addc(c1, uint64_t{r_carry});
@@ -509,8 +541,6 @@ void modinv_pow2(
509541

510542
} // namespace
511543

512-
namespace evmone::crypto
513-
{
514544
void modexp(std::span<const uint8_t> base_bytes, std::span<const uint8_t> exp_bytes,
515545
std::span<const uint8_t> mod_bytes, uint8_t* output) noexcept
516546
{

0 commit comments

Comments
 (0)