@@ -354,43 +354,23 @@ StaticAdStackAnalysisResult analyze_adstack_static_bounds(OffloadedStmt *task_ir
354354 }
355355 return false ;
356356 };
357- // For each iterating axis, walk back to its root source: the underlying `LoopIndexStmt` (StructFor case)
358- // or the `AdStackAllocaStmt` whose `AdStackLoadTopStmt` replays it (autodiff reverse task). Multi-axis
359- // gates whose iterating axes all derive from the SAME source (`field[i % 2, i % 2]`) are non-injective
360- // even though they have multiple iterating axes; requiring `distinct_iterating_sources == n_iterating`
361- // rejects those while admitting the canonical sparse-grid shape where each ndrange axis is replayed via
362- // a distinct per-axis adstack. `BinaryOpStmt` / `UnaryOpStmt` operands are walked recursively to find the
363- // leaf source.
364- std::function<Stmt *(Stmt *, int )> root_source = [&](Stmt *s, int depth) -> Stmt * {
365- if (s == nullptr || depth > 8 )
366- return nullptr ;
367- if (s->is <LoopIndexStmt>())
368- return s;
369- if (auto *load_top = s->cast <AdStackLoadTopStmt>()) {
370- return load_top->stack ;
371- }
372- if (auto *bin = s->cast <BinaryOpStmt>()) {
373- if (Stmt *r = root_source (bin->lhs , depth + 1 ))
374- return r;
375- return root_source (bin->rhs , depth + 1 );
376- }
377- if (auto *un = s->cast <UnaryOpStmt>()) {
378- return root_source (un->operand , depth + 1 );
379- }
380- return nullptr ;
381- };
357+ // Reject fold-attack shapes like `field[i % 2, i % 2]` where two iterating axes are the same IR
358+ // statement (CSE collapses both occurrences to a single `BinaryOpStmt`), causing multiple pushes per
359+ // outer-loop iteration to alias onto the same SNode cell and undersize the heap. The canonical
360+ // `qd.ndrange(*shape)` decomposition produces structurally distinct `floordiv` / `mod` / `sub`
361+ // statements per axis even though every axis is rooted at the same `LoopIndexStmt`, so identity
362+ // distinctness is the right gate: it rejects the fold attack while admitting the joint-bijective
363+ // ndrange shape uniformly across LLVM and SPIR-V backends.
382364 int n_iterating = 0 ;
383365 int n_bare_iterating = 0 ;
384- std::unordered_set<Stmt *> distinct_iterating_sources ;
366+ std::unordered_set<Stmt *> distinct_iterating_axes ;
385367 for (Stmt *axis : axes) {
386368 if (contains_loop_index (axis, 0 )) {
387369 n_iterating++;
388370 if (axis->is <LoopIndexStmt>()) {
389371 n_bare_iterating++;
390372 }
391- if (Stmt *src = root_source (axis, 0 )) {
392- distinct_iterating_sources.insert (src);
393- }
373+ distinct_iterating_axes.insert (axis);
394374 }
395375 }
396376 if (n_iterating == 0 ) {
@@ -399,7 +379,7 @@ StaticAdStackAnalysisResult analyze_adstack_static_bounds(OffloadedStmt *task_ir
399379 if (n_iterating == 1 && n_bare_iterating == 0 ) {
400380 return false ;
401381 }
402- if (static_cast <int >(distinct_iterating_sources .size ()) < n_iterating) {
382+ if (static_cast <int >(distinct_iterating_axes .size ()) < n_iterating) {
403383 return false ;
404384 }
405385 out.field_source_kind = StaticAdStackBoundExpr::FieldSourceKind::SNode;
0 commit comments