Skip to content

test(code-splitting): assert final-topology SCC wrapping invariant under strict order#10321

Draft
hyfdev wants to merge 3 commits into
mainfrom
codex/strict-scc-final-topology-assert
Draft

test(code-splitting): assert final-topology SCC wrapping invariant under strict order#10321
hyfdev wants to merge 3 commits into
mainfrom
codex/strict-scc-final-topology-assert

Conversation

@hyfdev

@hyfdev hyfdev commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Adds the structural assert the review recommended ("One structural assert I'd add"). The emergent-cycle projector's soundness rests on mirroring the linker's three init-registration paths, and one consumption disjunct (referenced_symbols_by_entry_point_chunk in collect_frozen_reexport_usage) has no projection counterpart. No concrete break was constructed, but under-projection is the crash direction this machinery exists to prevent — a future hole would surface only as a fuzzer-caught init_* is not a function runtime crash.

This adds a #[cfg(debug_assertions)], strict-gated check that, after the final cross-chunk edges are assembled, re-runs petgraph tarjan_scc over the final imports_from_other_chunks chunk graph and asserts the exact obligation the projector 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. It converts any future projector hole from a fuzzer-only runtime crash into a deterministic debug/CI build failure. It is a structural invariant like the existing empty-facade zero-static-indegree assert — not the internal semantic verifier design.md rightly rejects.

The obligation is deliberately is_order_sensitive && is_order_wrap_eligible, matching close_cyclic_chunk_members (order_analysis.rs), 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 — leaving it unwrapped is correct, so a raw-eligibility assert would spuriously trip on valid on-demand builds. Reuses the existing is_order_sensitive and is_order_wrap_eligible predicates from order_analysis.rs (visibility widened to pub(super)) instead of duplicating their conditions.

Behavior / snapshot impact

None. Debug-only assert, no runtime/output change. Zero fixtures trip the assert across the full suite, and zero snapshot changes.

Validation

  • cargo test -p rolldown (debug build, so the assert runs against every fixture) — 1868 passed, 0 assert trips, snapshot-stable. (Same 5 pre-existing environmental failures as the base: cjs_module_lexer_compat ×3 / npm_packages need cjs-module-lexer installed; test262 needs the submodule.)
  • cargo fmt --all --check — clean.
  • cargo clippy -p rolldown --all-targets -- -D warnings — clean.

Follow-up to #10104; addresses the "One structural assert I'd add" recommendation from the review discussion: #10104 (comment)

Draft, stacked on codex/strict-transitive-init-determinism.

hyfdev added 3 commits July 15, 2026 20:29
…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.
@hyfdev
hyfdev force-pushed the codex/strict-scc-final-topology-assert branch from ede4fd5 to bc71bc1 Compare July 16, 2026 15:14
@hyfdev
hyfdev force-pushed the codex/strict-transitive-init-determinism branch 2 times, most recently from d427f05 to 4039bf3 Compare July 22, 2026 03:38
@graphite-app
graphite-app Bot force-pushed the codex/strict-transitive-init-determinism branch from 4039bf3 to 1ed58f4 Compare July 22, 2026 06:58
Base automatically changed from codex/strict-transitive-init-determinism to main July 22, 2026 07:04
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