From 44c689d755b1291aec580cbad8014baadf9ca0d5 Mon Sep 17 00:00:00 2001 From: Colin McDonnell <3084745+colinhacks@users.noreply.github.com> Date: Fri, 4 Sep 2026 08:10:32 -0700 Subject: [PATCH] deps: V8: use a 128-bit multiply in rapidhash secret generation Since the seeded array index hash landed, HashSeed::InitializeRoots runs rapidhash_make_secret on every isolate start, including every worker_threads Worker. The generator runs a 12-base Miller-Rabin test over ~1,500 rejection-sampled candidates per secret, and its mul_mod is a 64-iteration shift-add loop with two 64-bit modulos per iteration, ~9,000 calls per generation: 12-14% of the samples in a perf profile of `node -e 0` on Linux x64. Use a 128-bit multiply and modulo where the compiler runtime provides one and keep the loop as the fallback. Clang on Windows defines __SIZEOF_INT128__ but its runtime has no __umodti3 (compiler-rt builds the 128-bit division helpers for LP64 targets only), so Windows keeps the loop. The generated secrets are bit-identical: a standalone copy of the generator agrees on 6,000 words over 2,000 seeds on x64 and arm64 Linux, macOS and Windows, and runs 15-22x faster where the new path is taken. Measured on this tree, min of 100 runs, paired against an unpatched build on the same machine: `node -e 0` 19.75 -> 18.09 ms on Linux x64 and 30.8 -> 29.3 ms on macOS arm64, a Worker spawn 3.9 -> 3.1 ms. The `parallel`, `sequential`, `message` and `es-module` suites show no failure the unpatched build does not have. Refs: https://chromium-review.googlesource.com/c/v8/v8/+/6733490 Refs: https://github.com/nodejs/node/commit/af5c144ebc Signed-off-by: Colin McDonnell <3084745+colinhacks@users.noreply.github.com> --- common.gypi | 2 +- deps/v8/third_party/rapidhash-v8/README.chromium | 2 ++ deps/v8/third_party/rapidhash-v8/secret.h | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index 5aef7768323c..e59a7ef95da4 100644 --- a/common.gypi +++ b/common.gypi @@ -42,7 +42,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.29', + 'v8_embedder_string': '-node.30', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/third_party/rapidhash-v8/README.chromium b/deps/v8/third_party/rapidhash-v8/README.chromium index da20cd8d2563..10d67d80c3ee 100644 --- a/deps/v8/third_party/rapidhash-v8/README.chromium +++ b/deps/v8/third_party/rapidhash-v8/README.chromium @@ -19,3 +19,5 @@ particular version is copied over from Chromium. Local Modifications: - Copied over from Chromium's third_party/rapidhash with all its changes. - Removed base/ includes and replaced with V8 versions. +- mul_mod in secret.h uses a 128-bit multiply and modulo where the compiler + runtime provides one. diff --git a/deps/v8/third_party/rapidhash-v8/secret.h b/deps/v8/third_party/rapidhash-v8/secret.h index 021125bc4ce8..f6243199a14f 100644 --- a/deps/v8/third_party/rapidhash-v8/secret.h +++ b/deps/v8/third_party/rapidhash-v8/secret.h @@ -81,6 +81,12 @@ static inline uint64_t wyrand(uint64_t* seed) { static inline unsigned long long mul_mod(unsigned long long a, unsigned long long b, unsigned long long m) { +#if defined(__SIZEOF_INT128__) && !defined(_WIN32) + // A 128-bit multiply and modulo lower to __umodti3 from compiler-rt, which + // clang's Windows runtime does not provide. + return static_cast( + (static_cast<__uint128_t>(a) * b) % m); +#else unsigned long long r = 0; while (b) { if (b & 1) { @@ -96,6 +102,7 @@ static inline unsigned long long mul_mod(unsigned long long a, } } return r; +#endif } static inline unsigned long long pow_mod(unsigned long long a,