Skip to content

Slot lane for plain assignment to local bindings#2577

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:ws5-plain-assign-slot
Jul 4, 2026
Merged

Slot lane for plain assignment to local bindings#2577
lahma merged 1 commit into
sebastienros:mainfrom
lahma:ws5-plain-assign-slot

Conversation

@lahma

@lahma lahma commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

The loop-cost decomposition from #2574 left one shape on the slow path: plain assignment to a local (b = a, x = f()) always ran AssignToIdentifier's uncached environment walk plus the by-name SetMutableBinding scan — costing more per iteration (+20 ns) than the compound lanes it sits next to. Only the id = a op b numeric shape had a fast path.

What this does

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 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 null means the cached-global arm is the fast path) — added after the first A/B round showed a +3% probe tax on GlobalVarLoop, now eliminated.

Measurements (same-window stash A/B, BenchmarkDotNet default job)

Benchmark before after Δ
GlobalAccess LocalVarLoop 52.23 ms 44.68 ms −14.5% (StdDev ±0.3%)
GlobalAccess GlobalVarLoop / GlobalUpdateLoop 44.70 / 90.74 ms 44.66 / 90.83 ms flat (probe tax removed)
LoopDispatch LocalCopy 5.46 ms 5.27 ms −3.5%
EvalExecution EvalHot 757.6 µs 731.7 µs −3.4%
stopwatch driver (canary) 179.0 ms/iter 176.3 ms/iter within noise

Gates

  • New 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 annexB RegExp-*-escape-BMP load-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 (repeated new 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

@lahma
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant