Extend for-loop tight-body lane to if/else chains and variable declarations#2623
Merged
Conversation
…ations The tight loop previously admitted only expression-statement bodies, so the stopwatch-shaped body (var decl + 4-way else-if chain calling tiny closures + two member-read var decls) paid full per-statement ceremony: PrepareFor, Completion materialization, statement-list bookkeeping and the nested if suspension probes, 391k times per benchmark op. The body shape predicate now admits expression statements, var/let/const declarations (using/await-using excluded) and if/else chains over such statements, including declaration-free brace blocks. Control flow that routes completions (break/continue/return/labels/loops/switch/try), other declarations and AnnexB function-declaration branches still disqualify structurally, so every admitted completion is Normal by construction and the lane needs no completion routing. Dispatch happens through a new virtual JintStatement.ExecuteDiscarded (default: full Execute) with lean overrides for expression statements, if statements (unboxed GetBooleanValue test, no suspension-range checks, no UpdateEmpty), variable declarations (slot lane / cached-global route already end in Empty completions) and declaration-free nested blocks. Deferred errors keep the existing per-statement Engine._error poll protocol; statements after a deferred error do not run. Bodies with lexical declarations engage only when sebastienros#2610 flattening is active: the pooled loop env carries the body slots and the tight loop re-establishes their TDZ per iteration exactly like the generic flattened arm. Rider: JintConditionalExpression now takes the unboxed GetBooleanValue path for its test in non-suspendable frames. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BY8HoKam5H8vTPn1bMY2Hv
lahma
enabled auto-merge (squash)
July 10, 2026 18:22
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.
Extends the for-loop tight-body lane (#2605) beyond expression-statement-only bodies to the shape real hot loops actually have — the stopwatch benchmark's body being the canonical case: a
var/constdeclaration, a 4-wayelse ifmodulo chain whose branches call tiny closures, and trailing member-read declarations, executed 391k times per benchmark op with full per-statement ceremony (PrepareFor, Completion materialization, statement-list bookkeeping, nested-if suspension probes).Shape predicate. The body may now contain expression statements,
var/let/constdeclarations (using/await usingexcluded — dispose tracking), and if/else chains over such statements, including declaration-free brace blocks. Anything that routes completions — break/continue/return/labels/loops/switch/try — and AnnexB function-declaration branches still disqualify structurally, so every admitted completion is Normal by construction and the lane needs no completion routing.Dispatch. A new virtual
JintStatement.ExecuteDiscarded(default: fullExecute) with lean overrides:JintIfStatement: unboxedGetBooleanValuetest → recurse into the branch; drops PrepareFor, suspension-range checks,UpdateEmptyand the Completion struct; polls the deferred-error slot after the test so a signalled error suppresses the branch.JintVariableDeclaration: tracks the node for error locations, then runs the existing slot-lane/cached-global initialization (all its paths already end in Empty completions). Skipping PrepareFor is safe:EvaluationContext.Completionis written nowhere else (verified by search), and a staleTargetis only consumed under Break/Continue completions, which the shape excludes.Engine._errorpoll (a statement after a deferred error must not run).Lexical bodies. Bodies with let/const engage only when #2610 flattening is active: the pooled loop env carries the body slots and the tight loop re-establishes their TDZ per iteration exactly like the generic flattened arm; a lexical body that failed to flatten falls back to the generic path.
Rider.
JintConditionalExpressionnow takes the unboxedGetBooleanValuepath for its test in non-suspendable frames (was: materialize JsValue →TypeConverter.ToBoolean).Nine new pins in
TightLoopTests(tight-vs-generic parity for the full if-chain shape, throw-in-branch state/location, statement-after-deferred-error, break/continue/return exclusion semantics, per-iteration TDZ in flattened+tight bodies including iteration ≥2,usingdispose ordering, MaxStatements enforcement) plus two new LoopDispatch rows (IfChainLoop= the full stopwatch body shape,VarDeclBody).Benchmarks (before/after, same machine, sequential; allocations byte-identical on every row)
The newly-eligible if-test rows — the lane firing is the delta:
The target rows:
Canary rows not touched by this change moved within the documented single-launch process-mode bands, in both directions (EmptyLoop −5.4%, VariableBoundLoop +17.5% on an empty-body loop the change cannot affect, CounterAdd/LocalCopy/StringAppend +7..11%) with byte-identical allocations; launchCount-5 medians available on request if any row needs adjudication.
Gates: Jint.Tests 3,297+3,234 pass (9 new pins), PublicInterface 82×2*, Test262 99,426 pass / 0 fail (exact baseline).
* the net472
TimeSystemTests.CanUseTimeProviderwall-clock-tolerance test currently fails on this machine on unmodified main as well (verified 3/3, ~7 ms past its 100 ms window, post-sleep timer state) — environmental, not attributable to the change; the net10 arm and all other net472 tests pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01BY8HoKam5H8vTPn1bMY2Hv