Skip to content

fix(code-splitting): fold the runtime chunk back after order lowering#10414

Open
hyfdev wants to merge 2 commits into
mainfrom
fold-runtime-chunk-after-order-lowering
Open

fix(code-splitting): fold the runtime chunk back after order lowering#10414
hyfdev wants to merge 2 commits into
mainfrom
fold-runtime-chunk-after-order-lowering

Conversation

@hyfdev

@hyfdev hyfdev commented Jul 23, 2026

Copy link
Copy Markdown
Member

Problem

First P1 item of #10294: ensure_runtime_module_for_order_wraps unconditionally evicts a co-hosted runtime module onto a standalone rolldown-runtime chunk (and mints one when the runtime was unplaced), even when every helper consumer lives in a single chunk. issues/9463 ships a rolldown-runtime.js whose only content is two helper re-exports consumed exclusively by common~a~b~shared.js — one extra request per page load for nothing, in both wrap modes.

The eviction itself is justified: the baseline merge proof (try_merge_runtime_chunk, called from generate_chunks and the chunk optimizer) runs before order analysis, so nothing it proved covers the helper demand order lowering adds (__esm/__esmMin per wrapper, __toCommonJS/__reExport per lowered overlay). What was missing is re-running the proof afterwards: by the end of lowering the full demand is materialized in OrderWrapState, so the complete post-lowering consumer set is knowable.

Fix

ensure_runtime_module_for_order_wraps keeps its normalize-to-standalone behavior unchanged, then re-runs the merge proof with the post-lowering consumer set:

  • New OrderWrapState::runtime_helper_consumer_chunks collects the order-introduced consumers: each wrapper's synthetic init_* statement renders in its assigned chunk, and each overlay's lowered import/require glue renders in the importer's chunk. Pre-lowering demand is re-collected by collect_runtime_consumer_chunks's own scan (chunk helper bits, interop wrappers, statement-level runtime symbol references), and stale/tombstoned chunks are filtered by the existing post_chunk_optimization_operations guard.
  • try_merge_runtime_chunk gains a RuntimeMergeCascade parameter. The two pre-existing call sites use Full (byte-identical cascade). The post-lowering fold uses SingleConsumerOnly, and two deliberate restrictions apply:
    • Sole-consumer hosts only. A dominator merge turns transitive reachability into a direct import whose exec-order sort position can hoist the host past sibling imports, and a bitset host can gain a brand-new consumer edge — either way the chunk-evaluation order deviates from what the on-demand analysis proved against the pre-fold edge set. A sole-consumer merge only removes that consumer's edge to an inert runtime-only chunk, so evaluation order is preserved by construction (this also makes the TLA/signature/cycle guards trivially satisfied, and keeps the existing sole-consumer allowance for manual group chunks).
    • Esm output only. Under cjs output, compute_cross_chunk_links later gives every ESM-exports entry chunk — including the zero-module order facades — a __toCommonJS demand that is invisible at fold time; folding would hand an entry chunk with no visible demand a brand-new require edge into a user chunk. Every other post-fold demand source was audited to be either Esm-excluded or already represented in the fold-time consumer set.
  • In SingleConsumerOnly mode the merge tail skips target_chunk.bits.union(...): the sole consumer's own bits are already exact (the runtime becomes internal to it), while the order-time standalone chunk carries synthetic all-live-union bits whose widening would leak into bits-derived chunk names.

Non-esm formats and strictExecutionOrder: false builds are byte-for-byte untouched; the fold only runs from the order-wrap path (non-empty plan).

Output shape

issues/9463 (acceptance): rolldown-runtime.js disappears and the helpers inline into common~a~b~shared.js in both cells; same for issues/9463_plain_group, issues/9463_three_entries, and strict_execution_order/concatenate_basic (wrap-all cells fold into main2.js). 40 snapshots change, all strict-mode fixtures, all the same shape: the standalone runtime chunk is gone and its (actually demanded) helpers render inside the sole consumer — issues/5870 and mixed_static_dynamic_same_target also drop a previously exported-but-unconsumed __exportAll, since the host renders only demanded statements.

One snapshot-cosmetic note: in fixtures where the runtime region folds ahead of a synthetic wrapper rendered without its own region markers (pre-existing quirk), the HIDDEN [\0rolldown/runtime.js] replacement visually swallows the following wrapper in the snapshot (top_level_await_syntax). The raw emitted chunk was dumped and verified correct — init_foo is present; only the snapshot display collapses it.

Validation

  • Full integration suite + workspace tests: failure list byte-identical to unmodified main (verified by stash-and-rerun on the same machine; the only local failures are the pre-existing css/legal-comments/esbuild-diff environment set).
  • Execution-order fuzzer (rolldown-order-fuzzer) against this build, 300 cases across three cells: mixed 112/120, pure-ESM 83/100, wrap-all 71/80. The same three campaigns (same seeds and flags) were then run against an unmodified-main build of this worktree: the failing seed sets are identical and every one of the 34 failures carries a byte-identical verdict signature (the known Family-A / event-reorder arms tracked in strictExecutionOrder / onDemandWrapping: follow-up ledger #10294) — zero failure signatures introduced or altered by this change.

Part of #10294.

@netlify

netlify Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploy Preview for rolldown-rs ready!

Name Link
🔨 Latest commit eae550d
🔍 Latest deploy log https://app.netlify.com/projects/rolldown-rs/deploys/6a6465dd6eaf310008736e38
😎 Deploy Preview https://deploy-preview-10414--rolldown-rs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@hyfdev
hyfdev marked this pull request as ready for review July 24, 2026 03:36
Copilot AI review requested due to automatic review settings July 24, 2026 03:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codspeed-hq

codspeed-hq Bot commented Jul 24, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 7 untouched benchmarks
⏩ 10 skipped benchmarks1


Comparing fold-runtime-chunk-after-order-lowering (eae550d) with main (87b0239)2

Open in CodSpeed

Footnotes

  1. 10 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (927a328) during the generation of this report, so 87b0239 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Comment thread crates/rolldown/src/stages/generate_stage/order_wrapping.rs
@hyfdev
hyfdev marked this pull request as draft July 24, 2026 04:14
@hyfdev
hyfdev force-pushed the fold-runtime-chunk-after-order-lowering branch from 8548a60 to 6265551 Compare July 24, 2026 07:18
@hyfdev
hyfdev marked this pull request as ready for review July 25, 2026 07:29
hyfdev added 2 commits July 25, 2026 15:29
ensure_runtime_module_for_order_wraps unconditionally left the runtime on a
standalone chunk because the baseline merge proof runs before order analysis
and never saw the helper demand lowering adds. By the end of lowering that
demand is fully materialized in OrderWrapState, so re-run the merge proof with
the post-lowering consumer set: sole-consumer hosts only (any other host
creates or reorders chunk-evaluation edges the order analysis never modeled),
esm output only (cjs output later mints an invisible __toCommonJS demand on
ESM-exports entry chunks), and without widening the host's bits (the order-time
standalone chunk carries synthetic all-live-union bits).

Part of #10294.
@hyfdev
hyfdev force-pushed the fold-runtime-chunk-after-order-lowering branch from 58e262b to eae550d Compare July 25, 2026 07:29
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.

3 participants