Repository navigation
Deoptimization loop ("wrong map") in big.js Big.prototype.div makes code ~6x slower on v26, not on v24 #66717
Description
Version
v26.11.1
Platform
Darwin L1845 25.6.0 Darwin Kernel Version 25.6.0: Tue Aug 18 17:49:13 PDT 2026; root:xnu-12377.161.15.700.19~2/RELEASE_ARM64_T6000 arm64
Subsystem
v8 engine
What steps will reproduce the bug?
mkdir repro && cd repro
npm init -y && npm pkg set type=module
npm install big.js@7.0.1repro.mjs:
import Big from 'big.js'
// Deterministic PRNG (mulberry32)
let seed = Number(process.argv[2] ?? 1) >>> 0
const random = () => {
seed = (seed + 0x6d2b79f5) >>> 0
let t = seed
t = Math.imul(t ^ (t >>> 15), t | 1)
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
let sink = 0
const start = performance.now()
for (let i = 0; i < 300000; i++) {
const a = Big((random() * 30).toFixed(2))
const b = Big((random() * 5 + 0.5).toFixed(i % 3))
sink += Number(a.times(b).div(i % 2 ? Big(1.255) : b.plus(1)).round(2))
}
console.log(`${process.version} took ${Math.round(performance.now() - start)} ms (${sink > 0})`)node repro.mjs 2
node --trace-deopt repro.mjs 2 2>&1 | grep -c "JSFunction P.div"How often does it reproduce? Is there a required condition?
Deterministic with seed 2 (6/6 runs on both v26.7.0 and v26.11.1). Whether the loop starts depends on
the input sequence: with seeds 1..12, seeds 2, 3, 7 and 9 trigger it on v26 and none trigger it on v24.
What is the expected behavior? Why is that the expected behavior?
P.div should deoptimize a few times, after which V8 should reoptimize it with updated feedback.
This is what happens on v24.21.0: 1-3 deopts of P.div for every seed, and every run takes ~1.0-1.1 s.
What do you see instead?
On v26, P.div is deoptimized thousands of times at the same bytecode offset with the same reason,
and the run is ~6x slower:
| v24.21.0 | v26.7.0 | v26.11.1 | |
|---|---|---|---|
node repro.mjs 1 |
~1,040 ms | ~1,030 ms | ~1,010 ms |
node repro.mjs 2 |
~1,075 ms | 6,620–8,640 ms | ~6,600 ms |
P.div deopts, seed 2 |
2 | ~5,150 | ~5,600 |
Every deopt has the same signature (v26.11.1):
[bailout (kind: deopt-eager, reason: wrong map): begin. deoptimizing 0x3e332d0efef9 <JSFunction P.div (sfi = 0x3e332d0ee969)>, 0x2cf238c84509 <Code MAGLEV>, opt id 2, bytecode offset 447, deopt exit 39, FP to SP delta 240, caller SP 0x00016fb98c20, pc 0x000115b8ba88]
Bytecode offset 447 is a Dec bytecode (--rl) in r[--rl] < bt[rl] in big.js div
(https://github.com/MikeMcl/big.js/blob/v7.0.1/big.mjs#L410):
10242 S> @ 445 : 0b ea Ldar r15
@ 447 : 5a 4f Dec FBV[79]
@ 449 : c3 Star15
10247 E> @ 450 : 35 eb 50 GetKeyedProperty r14, FBV[80]
The loop is not specific to Maglev. With --no-maglev, TurboFan code loops at the same offset with the same reason:
| Flags (v26.11.1, seed 2) | P.div deopts |
Deoptimized code | Time |
|---|---|---|---|
| (default) | 5,649 | Maglev | 6,614 ms |
--no-maglev |
855 | TurboFan, offset 447, wrong map |
6,850 ms |
--no-maglev-osr |
5,709 | Maglev | 6,621 ms |
--no-turbofan |
5,713 | Maglev | 6,640 ms |
Additional information
We noticed this through a performance smoke test in a calculation library that uses big.js
for all arithmetic. On v26 the test became ~30% slower and flaky in CI (GitHub Actions
ubuntu-latest, x64), while v22 and v24 were stable. In that test suite the same signature appears:
~1,250 "wrong map" deopts of P.div at bytecode offset 447.
The repro above was measured only on macOS arm64; the CI symptom suggests Linux x64 is also affected,
but I did not verify the deopt trace there.
Possibly related, but a different symptom: #66457 (Maglev OSR correctness bug, also v24.11/v26,
also repeated eager deopts of the same code). --no-maglev-osr does not help here.
Activity
lazerg commentedon Oct 14, 2026
It's a V8 bug, and it's already fixed on
main. Onv26.8.2I get the same thing with seed 2: about 6.5 s and ~5,800P.divdeopts. The latest nightly (v27.0.0-nightly20261014, V8 15.5) runs it in about 0.9 s with one deopt, and seeds 3, 7 and 9 are fine there too.Going through the nightlies, the
2026-10-04build (V8 14.6) still loops and the2026-10-07build (V8 15.2) doesn't, so the V8 15.2 update in #65161 fixed it. That PR is semver-major, so it lands inv27. Forv26, someone would need to find the V8 commit between 14.6 and 15.2 and backport it.inoway46 commentedon Oct 15, 2026
Backporting the
V8_ENABLE_UNDEFINED_DOUBLEconfiguration from nodejs/node@8394db37d800 to v26 eliminated the repeated wrong-map deoptimizations in the tested macOS arm64 builds, both with and without Maglev.Proposed backport: #66733
Verification: https://github.com/inoway46/node/actions/runs/38592855428
build: enable V8 undefined double support