fix(code-splitting): gate entry-level transitive init registration on strict order#10323
Draft
hyfdev wants to merge 5 commits into
Draft
fix(code-splitting): gate entry-level transitive init registration on strict order#10323hyfdev wants to merge 5 commits into
hyfdev wants to merge 5 commits into
Conversation
…nistic and constant-time Two independent fixes to the strict-execution-order cross-chunk link pass: - add_transitive_esm_init_depended_symbols iterated an FxHashMap via values(), whose bucket order follows FxHash layout. FxHash is unseeded but hashes differently on 32-bit vs 64-bit, so that order (which flows into the chunk's depended_symbols FxIndexSet and thence into imported-symbol $1/$2 rename order in deconflict_chunk_symbols) could diverge between native and wasm32/WASI builds. Iterate sorted by the owning StmtInfoIdx to pin one order everywhere. - module_is_in_live_chunk did a linear chunk.modules.contains scan, called for every canonical referenced symbol of every included statement under strict, making the pass quadratic on large chunks. Replace with an O(1) module_to_chunk + liveness check. Sound because add_module_to_chunk sets both together and every module-removal site pairs the removal with clearing module_to_chunk, reassigning it to the destination chunk, or marking the chunk Removed; no site leaves module_to_chunk pointed at a still-live chunk that no longer contains the module, so no removed-module set is needed.
…der strict order The emergent-cycle projector's soundness rests on mirroring the linker's three init registration paths. One consumption disjunct (referenced_symbols_by_entry_point_chunk) has no projection counterpart, so a future projector hole would surface only as a fuzzer-caught `init_* is not a function` runtime crash. Add a debug-only, strict-gated check that re-runs Tarjan over the FINAL imports_from_other_chunks chunk graph and asserts the exact obligation close_cyclic_chunk_members discharges: every module hosted in a nontrivial SCC (>=2 chunks, or a self-loop) that is both order-sensitive AND order-wrap-eligible is order-wrapped in the plan. This turns any future under-projection into a deterministic debug/CI build failure instead of a runtime crash. It is a structural invariant like the existing empty-facade zero-static-indegree assert, not the internal semantic verifier design.md rejects. The obligation is deliberately is_order_sensitive && is_order_wrap_eligible, not raw is_order_wrap_eligible: a side-effect-free, import-free eligible leaf (e.g. `export const x = 1`) is never wrapped by the projector, yet can be co-hosted in a chunk that lands in a cycle because of its siblings' cross-chunk imports, and leaving it unwrapped is correct. Reuses is_order_sensitive and is_order_wrap_eligible (visibility widened to pub(super)) instead of duplicating their conditions. Zero fixtures trip the assert; zero snapshot changes.
…r-sensitive
is_order_sensitive() collects GlobalVarAccess/PureAnnotation reasons only in the
expression arms the analyzer handles itself. Three oxc-delegate arms returned a
bare tree-shaking boolean and dropped the reasons: the analyze_decl catch-all
(class declarations), export default class, and the analyze_expr catch-all
(class expressions). So `var x = Object.freeze({})` was order-sensitive while
`class C { static x = Object.freeze({}) }` was not — a module whose only
eval-time work is a whitelisted-global read in a class position could be silently
reordered across a module that patches that global (polyfill-then-use hazard).
On-demand wrapping widened the blast radius from import-free leaves to every
planless module.
When oxc reports no side effect for a class declaration/expression, additionally
collect order-sensitive reasons from the class's definition-time-evaluated
positions: heritage (extends), class/element decorators, computed keys, static
field/accessor initializers, and static blocks — routed through the existing
analyze_expr/analyze_stmt machinery. Instance (non-static) field initializers and
method bodies run at construction/call time, not definition time, so they are
excluded.
The tree-shaking channel is byte-for-byte unaffected: base facts stay
from_tree_shaking_side_effect(<same node>.may_have_side_effects), and reasons are
harvested (order-sensitive channel only) exclusively when oxc already certified
the class side-effect-free. Adds unit tests for both the newly-sensitive
positions and the excluded construction-time ones. Zero existing fixture
snapshots change; the hazard was latent.
… strict order The entry/facade-chunk loop called add_transitive_esm_init_depended_symbols for the entry module with no strict gate, but legacy interop transitive_esm_init_targets exist flag-off too. For an interop-wrapped entry rendered behind a facade chunk, with an excluded re-export whose wrapped targets share the entry's host chunk, the facade would gain a dead cross-chunk init_* import that the pre-#10104 base never emitted. Gate this entry-level call on is_strict_execution_order_enabled(). Strict still needs it (it feeds order-wrapped entries' prologue init imports). The per-module call in add_module_esm_init_depended_symbols stays ungated: it is provably inert flag-off, since legacy targets are same-chunk-only and same-chunk targets never become cross-chunk imports. Behavior/snapshot impact: none. Full suite runs with zero snapshot changes vs the ungated base — for a non-facade entry the entry chunk equals the host chunk, so the registered targets are same-chunk and never turn into cross-chunk imports; only a facade entry (entry module hosted elsewhere) would differ.
hyfdev
force-pushed
the
codex/strict-class-def-order-sensitivity
branch
from
July 16, 2026 15:14
4709a6b to
4f804f3
Compare
hyfdev
force-pushed
the
codex/strict-entry-transitive-init-gate
branch
from
July 16, 2026 15:14
4306dd7 to
3bbc10b
Compare
hyfdev
force-pushed
the
codex/strict-class-def-order-sensitivity
branch
3 times, most recently
from
July 20, 2026 14:02
b8df48d to
3b7f8b7
Compare
graphite-app
Bot
force-pushed
the
codex/strict-class-def-order-sensitivity
branch
from
July 21, 2026 01:50
3b7f8b7 to
ff32edb
Compare
Base automatically changed from
codex/strict-class-def-order-sensitivity
to
main
July 21, 2026 01:55
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.
Summary
Addresses review Major 2a. The entry/facade-chunk loop in
compute_cross_chunk_linkscalledadd_transitive_esm_init_depended_symbolsfor the entry module with no strict gate, but legacy interoptransitive_esm_init_targetsexist flag-off too. For an interop-wrapped entry rendered behind a facade chunk, with an excluded re-export whose wrapped targets share the entry's host chunk, the facade would gain a dead cross-chunkinit_*import that the pre-#10104 base never emitted.Gate this entry-level call on
is_strict_execution_order_enabled(). Strict still needs it (it feeds order-wrapped entries' prologue init imports). The per-module call inadd_module_esm_init_depended_symbolsstays ungated — it is provably inert flag-off, since legacy targets are same-chunk-only and same-chunk targets never become cross-chunk imports.Behavior / snapshot impact
None. Full suite runs with zero snapshot changes vs the ungated base. For a non-facade entry the entry chunk is the host chunk, so the registered targets are same-chunk and never turn into cross-chunk imports; only a facade entry (entry module hosted elsewhere) could differ. I verified this symmetrically: the gated build matches the committed (ungated) snapshots on every existing fixture.
On the flag-off regression fixture
The gate lands as hardening; I could not construct the exact deviation flag-off after four genuine attempts, and the reason is structural:
analyze_execution_orderreturnsNonewhen strict is off (order_analysis.rs:107), socreate_order_wrap_entry_facadesnever runs. Capturing an entry into a group flag-off merges the entry into that chunk (confirmed empirically) rather than creating a facade.init_*for the entry, and no transitive registration.require) or a cycle materializes / co-locates it in a way that either un-excludes the re-export or removes the facade.Attempts tried (all empirically built and inspected): (1) TLA entry captured into a group → concatenated, no wrapper; (2) require-cycle single entry → wrapped but no facade (merged); (3) two-entry CJS-driven shared host → merged, re-export materialized; (4) dynamic-entry facade over a shared host → concatenated. The strict-mode facade-creation machinery is exactly what breaks this tension, and it does not run flag-off — so the deviation is a latent shape the gate defends against rather than one reachable today.
Validation
cargo test -p rolldown— 1868 passed, snapshot-stable, zero deltas. (Same 5 pre-existing environmental failures:cjs_module_lexer_compat×3 /npm_packagesneedcjs-module-lexer;test262needs the submodule.)cargo fmt --all --check— clean.cargo clippy -p rolldown --all-targets -- -D warnings— clean.Follow-up to #10104; addresses Major 2a from the review discussion: #10104 (comment)
Draft, stacked on
codex/strict-class-def-order-sensitivity.