Slot lane for plain assignment to local bindings#2577
Merged
Conversation
lahma
enabled auto-merge (squash)
July 4, 2026 19:26
Plain assignment to a local (b = a, x = f()) always ran AssignToIdentifier''s uncached environment walk plus the by-name SetMutableBinding scan; only the id = a op b numeric shape had a fast path. The loop-cost decomposition showed this costing more per iteration (+20 ns) than the compound lanes next to it. SimpleAssignmentExpression gains a general slot lane: the target resolves once through the shadow-aware SlotLocationCache, the right-hand side evaluates with AssignToIdentifier''s exact semantics (anonymous function/class naming, abrupt and generator-abort completions, completion.Clone()), and the store goes straight to the slot with the binding re-validated after the right-hand side runs. Const and TDZ targets bail before evaluating anything so the slow path produces the spec error ordering. Globals skip the lane on a single field test - a populated global-binding cache means the cached-global arm is the fast path - which removed the +3% probe tax the first A/B round showed on global assignment loops. Same-window stash A/B: GlobalAccess LocalVarLoop -14.5% (52.2 -> 44.7 ms, StdDev 0.3%), LoopDispatch LocalCopy -3.5%, EvalHot -3.4%, global rows and stopwatch driver flat. New PlainAssignmentTests (6) pass identically on the code before and after the lane (verified via stash cycles). Jint.Tests 3206/3144, PublicInterface 82/82, CommonScripts 28/28, all-TFM build; Test262 99,256 passed with the four known annexB RegExp-escape-BMP tests timing out only under full-suite parallel load on a long-running benchmark host (all four pass in 6 s when run in isolation on this change; the identical signature appeared and resolved on prior unrelated changes). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lahma
force-pushed
the
ws5-plain-assign-slot
branch
from
July 4, 2026 20:00
cee3699 to
a41cbaf
Compare
This was referenced Jul 6, 2026
This was referenced Jul 13, 2026
This was referenced Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The loop-cost decomposition from #2574 left one shape on the slow path: plain assignment to a local (
b = a,x = f()) always ranAssignToIdentifier's uncached environment walk plus the by-nameSetMutableBindingscan — costing more per iteration (+20 ns) than the compound lanes it sits next to. Only theid = a op bnumeric shape had a fast path.What this does
SimpleAssignmentExpressiongains a general slot lane: the target resolves once through the (shadow-aware)SlotLocationCache, the right-hand side evaluates withAssignToIdentifier's exact semantics — anonymous function/class naming, abrupt and generator-abort completions,completion.Clone()— and the store goes straight to the slot, with the binding re-validated after the RHS runs (it may have been rewritten during evaluation). Const/TDZ targets bail before evaluating anything so the slow path produces the spec error ordering. Globals skip the lane on a single field test (_cachedGlobalEnv is not nullmeans the cached-global arm is the fast path) — added after the first A/B round showed a +3% probe tax onGlobalVarLoop, now eliminated.Measurements (same-window stash A/B, BenchmarkDotNet default job)
Gates
PlainAssignmentTests(6) verified to pass identically on the code before and after the lane via stash cycles: anonymous fn/class naming (g|C|realName|arrow), chained/value positions, throwing RHS doesn't assign, RHS rewriting the target is overwritten, const TypeError ordering after RHS, TDZ ReferenceError.Jint.Tests,PublicInterface,CommonScripts, Test262 99,256 passed with the four known annexBRegExp-*-escape-BMPload-flakes timing out under full-suite parallelism only (all four pass in 6 s isolated on this change), all-TFM build.Measurement note: an unrelated observation from these A/Bs —
EvalExecutionBenchmarks.NewFunctionHotReusedEngine(repeatednew Function()on one engine) reads ~960 µs on pre-#2568 main and ~1,470 µs on current main on both sides of this A/B, i.e. unrelated to this change; reported separately.🤖 Generated with Claude Code