gfx opened PR #14211 from wado-lang:gfx/alias-analysis-rpo-worklist to bytecodealliance:main:
Fixes #14210.
AliasAnalysis::compute_block_input_statesdrains its worklist LIFO, so on a
branch it runs the entire tail of the function off of one successor before it
looks at the other, and then redoes that tail once the second predecessor
updates the join point. A module whose globals all allocate compiles to one
synthesized initializer that is a long chain of diamonds, where that means each
block is visited O(N) times and each visit walks O(N) alias regions. Ordering
the worklist by reverse postorder converges in a single pass instead.Compiling a module of N
struct.newglobal initializers under the copying
collector; compile times for ordinary modules are unchanged:
N before after 250 0.17 s 0.01 s 500 1.01 s 0.03 s 1000 8.6 s 0.08 s 2000 67.0 s 0.22 s Note that visit order also affects the precision of the fixpoint, so this
changes generated code:tests/disas/gc/array-copy-with-fuel.watnow forwards
two loads it previously reloaded, and its expectation is updated here.This does not make the pass linear.
LastStoreskeeps a denseSecondaryMap
over alias regions, so it remains quadratic in the number of regions, which
starts to show again around N=8000.
gfx requested cfallin for a review on PR #14211.
gfx requested wasmtime-compiler-reviewers for a review on PR #14211.
gfx requested wasmtime-core-reviewers for a review on PR #14211.
cfallin commented on PR #14211:
Note that visit order also affects the precision of the fixpoint
This is pretty deeply concerning. The fixpoint should converge to exactly the same answer no matter the visit order, because we have defined a lattice and we reprocess blocks when their input states change. Can you detail your thoughts here?
Sorry, that sentence was misleading.
The disagreement arm of
LastStores::meet_fromyieldsSome(loc)-- the
successor's own first instruction -- rather than a bottom element. Once a
slot holds that value, the meet yields it again against every other input,
and reports no change.I instrumented
compute_block_input_statesto dump the converged
block_inputand rantests/disas/gc/array-copy-with-fuel.watunder both
orders. Foru0:0they converge to different states.block6has exactly
one predecessor,block5, yet under the current LIFO order its last store
for regions 6 and 7 isinst102,block6's own first instruction; under RPO
it isinst154,block5's.The two loads in the expectation follow from that. They read
region6 v162
andregion7 v163, exactly whatblock5already loaded, solast_storeis
the only part of theMemoryLockey that differs.I also ran a variant that keeps per-block out-states and recomputes
in[B]
from all predecessors' current ones instead of meeting in place.block6
then agrees under both orders, butblock1,block7,block8andblock9
still differ.Three of those four configurations -- LIFO or RPO, meet-in-place or
recompute -- produce the new expectation for this test; only today's
LIFO/meet-in-place produces the two extra loads.observed_storesfor
u0:0is identical under both orders.I'll reword the description accordingly: the fixpoint isn't unique, and the
old order picked a worse one.
gfx edited a comment on PR #14211:
Sorry, that sentence was misleading.
The disagreement arm of
LastStores::meet_fromyieldsSome(loc)-- the
successor's own first instruction -- rather than a bottom element. Once a
slot holds that value, the meet yields it again against every other input,
and reports no change.I instrumented
compute_block_input_statesto dump the converged
block_inputand rantests/disas/gc/array-copy-with-fuel.watunder both
orders. Foru0:0they converge to different states.block6has exactly
one predecessor,block5, yet under the current LIFO order its last store
for regions 6 and 7 isinst102,block6's own first instruction; under RPO
it isinst154,block5's.The two loads in the expectation follow from that. They read
region6 v162
andregion7 v163, exactly whatblock5already loaded, solast_storeis
the only part of theMemoryLockey that differs.I also ran a variant that keeps per-block out-states and recomputes
in[B]
from all predecessors' current ones instead of meeting in place.block6
then agrees under both orders, butblock1,block7,block8andblock9
still differ.Three of those four configurations -- LIFO or RPO, meet-in-place or
recompute -- produce the new expectation for this test; only today's
LIFO/meet-in-place produces the two extra loads.observed_storesfor
u0:0is identical under both orders.What the description should have said: the fixpoint isn't unique, and the old order picked a worse one.
github-actions[bot] added the label cranelift on PR #14211.
Last updated: Aug 30 2026 at 09:07 UTC