Fix compiled chained-label for-loop hang (#580) and optional-chain string-method await over-eval (#627)#715
Merged
Merged
Conversation
…erLabel> (compiled) A chain of labels on a loop (a: b: for) used to hand only the innermost label to the loop; the outer label fell into the legacy 'mark continue before the loop' path, so 'continue <outer>' re-ran the for initializer forever (compiled hang). Loop scopes now carry a set of label names: EmitLabeledStatement flattens the chain and parks every label, and the loop drains them all so continue/break to any of them runs the loop's own step. Mirrors the interpreter's _pendingLoopLabels. Adds both-modes tests (continue outer/inner, break outer, triple chain, for-of chain). Async state-machine chained labels tracked in #704.
…uit (compiled await parity) For an optional-chain call to a dispatchable-string-method name (substring, charAt, ...) on a non-string receiver lacking it, with an awaited argument, the compiled await-safe path spilled the args before the isinst-string split, so the await ran even though the chain short-circuits to undefined. The interpreter short-circuits without evaluating the arg. Now a dedicated await-safe helper resolves the dispatch and null-checks the generic fn BEFORE evaluating the arg (short-circuiting first), then evaluates it once in a shared block both live dispatches reach, re-testing isinst string after (recv survives the suspension). Restores interpreter/compiled parity; keeps the pre-existing HasOptionalInChain quirk (undefined rather than TypeError) in both modes.
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.
Fixes two compiled-mode parity bugs and documents a third as a deliberate deferral.
#580 — chained label on a
forre-ran the initializer oncontinue <outerLabel>(compiled hang)a: b: for (…)withcontinueto the outer label of the chain hung in compiled mode: only the innermost label was handed to the loop, so the outer one fell into the legacy "mark continue target before the loop" path, ahead of theforinitializer, which then re-ran forever. The interpreter already handled it.Fix: a compiled loop scope now carries a set of label names (
IReadOnlyList<string> LabelNames) instead of a singlestring?.EmitLabeledStatementflattens the label chain (newUnwrapLabelChainhelper) and, when it bottoms out in a loop, parks every label via a pending list that the loop drains atEnterLoop— mirroring the interpreter's_pendingLoopLabels.FindLabeledLoopmatches by membership. Threaded throughCompilationContext,StatementEmitterBase,ILEmitter, and the sync/async generator emitters (for-of uses a drain-early-pass-manyIReadOnlyList<string>EnterLoopoverload; while/do-while/for-in adopt via the pending default).Tests: converted the interpreter-only
ChainedLabels_For_ContinueOuterto both-modes and added continue-inner, break-outer, triple-chain, and for-of-chain (all both-modes).#627 — optional-chain string-method over-evaluated an awaited argument (compiled)
For
o?.substring(await x, n)whereois a non-string object lackingsubstring, the compiled await-safe path spilled the argument before theisinst stringsplit (the #614 emit-once position), so theawaitran even though the chain short-circuits toundefined. The interpreter short-circuits without evaluating the argument.Fix: a dedicated await-safe helper (
EmitOptionalChainStringMethodAwaitSafe) resolves the dispatch and null-checks the generic fn before evaluating the argument (short-circuiting first), then evaluates it once in a shared block both live dispatches reach — re-testingisinst stringafter the spill (the receiver local survives the suspension; a plain bool flag would reset on MoveNext re-entry). Preserves the #614 emit-once invariant and the pre-existingHasOptionalInChainquirk (returnsundefinedrather than throwing — that's shared by both modes). The non-await path keeps the baseline, which already short-circuits before its inline args.Tests: new
RunOptionalChainObservehelper (top-level flag-setter folded into the awaited arg — compiled supports neither a nested function nor a nested async function inside the async body) covering missing-on-non-string (ran=false), theslicesibling (ran=false), and a string receiver (ran=true).#650 — for(let) mutate-and-restore capture — assessed and deferred (no code change)
for (let i …) { i=i+10; g.push(()=>i); i=i-10 }yields10,11,12compiled vs0,1,2interp/node. Compiled snapshots the loop var at closure-creation time; the spec wants the per-iteration binding's end-of-body value. The correct fix is per-iteration reference-capture cells (the compiled analog ofCreatePerIterationEnvironment) — a 5-area rework across all three variable resolvers + the for-loop emitters + closure capture + the analyzer, and it reintroduces boxing. Disproportionate and regression-prone for this exotic, low-priority edge, so it stays deferred (with an actionable design note added to the issue). The interpreted-only regression test is retained.Follow-up issues filed
continue <outerLabel>to the break target #704 — the async state-machine's separate labeled-loop subsystem has the same chained-label bug (wrong value, not a hang); out of scope for the sync Compiled: chained label on aforre-runs initializer oncontinue <outerLabel>(interpreter handles it) #580 fix.yield-arg to an optional-chain string-method on a non-string object in a generator still diverges (astronomically rare; the non-string-method sibling is fine).Testing
Full suite green except
ClusterModuleTests.Fork_WorkersShareHttpPort, a known pre-existing flake (HTTP-port race under parallel load, unrelated to these changes) that passes in isolation.