From d550f7aacbc7fcc7b453cc0a341d3966b07073f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Mon, 31 Aug 2026 10:34:10 +0200 Subject: [PATCH] docs(changelog): record the packed-loop throw fast path shipped in v0.5.1519 #9215, #9230 and #9235 merged without changelog fragments, so the v0.5.1519 notes carry no mention of the change. Only one entry is actually missing: #9215 and #9230 fix a regression from #9185 that was introduced and repaired entirely within the unreleased window, so no released version ever exhibited it and describing it as a fix would tell readers their current version is affected when none ever was. The fragment states the shipping release explicitly, so folding it into the next set of notes reads as a correction rather than as a new change. Docs only; no code, and no effect on the frozen v0.5.1519 candidate. --- .../9235-packed-loop-throw-fast-path.md | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 changelog.d/9235-packed-loop-throw-fast-path.md diff --git a/changelog.d/9235-packed-loop-throw-fast-path.md b/changelog.d/9235-packed-loop-throw-fast-path.md new file mode 100644 index 0000000000..66aebe750e --- /dev/null +++ b/changelog.d/9235-packed-loop-throw-fast-path.md @@ -0,0 +1,31 @@ +### Performance + +- **A `throw` inside a counted loop no longer forces the loop off its fast + path.** `for (let i = 0; i < arr.length; i++) { if (bad) throw new Error(…); + sum += arr[i]; }` is the ordinary shape of a validating loop, and the throw + — even one that never fires — used to cost the whole packed-array + specialization. Measured on a quiet machine over a 512-element `number[]`: + + | loop body | before | after | node | + |---|---|---|---| + | `throw new Error("bad")` | 7.99 ns/op | **0.95** | 7.70 | + | `throw new Error("bad " + i)` | 7.99 | **0.95** | 7.75 | + | `throw new Error()` | 7.99 | **0.95** | 7.75 | + | `throw "bad " + i` | 7.99 | **0.95** | 7.74 | + | `throw ` | 7.99 | **0.95** | 1.11 | + + An 8.1× improvement on the constructing forms, which are the common ones. + Perry now runs every shape in that benchmark faster than node, including + the loop with no `throw` in it at all (0.95 against 1.11). + + The specialization keeps a loop's accumulators in registers, so admitting a + `throw` required writing them back on the unwind edge — an exception leaves + through a landing pad rather than the loop's exit block, and a `catch` can + read anything the loop wrote. Operands are admitted only when evaluating + them cannot itself unwind: throwing a value coerces nothing, but building an + `Error` message or concatenating a string can dispatch to a user `toString` + or `valueOf`, and those can throw before the writeback runs. + + These landed in **v0.5.1519** (#9235, with #9230 and #9215). This fragment + was written after that release was cut, so it appears here rather than in + the notes for the release that carries the change.