From 4559e2ae854fb002f6d506c8ccb90233d5c73b11 Mon Sep 17 00:00:00 2001 From: Andrew Adams Date: Fri, 22 May 2026 12:41:16 -0700 Subject: [PATCH 1/2] Rewrite sliding window bounds let at the correct loop level Sliding window populates a replacements map keyed by .min/.max let names and rewrites the first matching LetStmt encountered on the way out from the producer. That happens to hit the right let today because bounds inference places the lets driving production at the producer's loop nest level, but other lets with the same names can appear at deeper loop nest levels on the consume side. Gate replacement on enclosing_loops.size() matching the depth recorded when the producer was visited, so only lets at the producer's loop level are rewritten. Co-Authored-By: Claude Opus 4.7 --- src/SlidingWindow.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/SlidingWindow.cpp b/src/SlidingWindow.cpp index f9122f572496..a7db10245afc 100644 --- a/src/SlidingWindow.cpp +++ b/src/SlidingWindow.cpp @@ -224,11 +224,20 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { set &slid_dimensions; Scope scope; - // Loops between the loop being slid over and the produce node + // For loops strictly between the loop being slid over and the current + // node (not including the loop being slid over itself). Scope<> enclosing_loops; map replacements; + // Loop-nest depth (size of enclosing_loops) at the moment we visited the + // target producer. Bounds inference places the lets that drive the + // producer at this same loop level, so replacements must only be applied + // to LetStmts at that same level — same-named lets at deeper for-loop + // nesting (e.g. inside the consume side of the func) serve other + // purposes and must not be rewritten. + size_t producer_loop_depth = 0; + using IRMutator::visit; // Check if the dimension at index 'dim_idx' is always pure (i.e. equal to 'dim') @@ -500,6 +509,7 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { replacements[n + ".min"] = Variable::make(Int(32), prefix + dim + ".min"); replacements[n + ".max"] = Variable::make(Int(32), prefix + dim + ".max"); } + producer_loop_depth = enclosing_loops.size(); // Ok, we have a new min/max required and we're going to // rewrite all the lets that define bounds required. Now @@ -591,7 +601,7 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { Expr value = op->value; map::iterator iter = replacements.find(op->name); - if (iter != replacements.end()) { + if (iter != replacements.end() && enclosing_loops.size() == producer_loop_depth) { value = iter->second; replacements.erase(iter); } From 9c2fe4a8a41c4d428454422d7c4bc1a3d1bc7ebf Mon Sep 17 00:00:00 2001 From: Andrew Adams Date: Fri, 22 May 2026 12:44:42 -0700 Subject: [PATCH 2/2] Comment tweak --- src/SlidingWindow.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/SlidingWindow.cpp b/src/SlidingWindow.cpp index a7db10245afc..7791c6ac2f1d 100644 --- a/src/SlidingWindow.cpp +++ b/src/SlidingWindow.cpp @@ -230,13 +230,11 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { map replacements; - // Loop-nest depth (size of enclosing_loops) at the moment we visited the - // target producer. Bounds inference places the lets that drive the - // producer at this same loop level, so replacements must only be applied - // to LetStmts at that same level — same-named lets at deeper for-loop - // nesting (e.g. inside the consume side of the func) serve other - // purposes and must not be rewritten. - size_t producer_loop_depth = 0; + // The immediately-enclosing For node, and the one enclosing the target + // producer. Replacements are only applied to LetStmts directly inside + // producer_for. + const For *current_for = nullptr; + Stmt producer_for; using IRMutator::visit; @@ -509,7 +507,7 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { replacements[n + ".min"] = Variable::make(Int(32), prefix + dim + ".min"); replacements[n + ".max"] = Variable::make(Int(32), prefix + dim + ".max"); } - producer_loop_depth = enclosing_loops.size(); + producer_for = Stmt(current_for); // Ok, we have a new min/max required and we're going to // rewrite all the lets that define bounds required. Now @@ -575,6 +573,7 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { Expr min = expand_expr(op->min, scope); Expr max = expand_expr(op->max, scope); ScopedBinding<> bind(enclosing_loops, op->name); + ScopedValue bind_for(current_for, op); if (equal(min, max)) { // Just treat it like a let Stmt s = LetStmt::make(op->name, min, op->body); @@ -601,7 +600,7 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { Expr value = op->value; map::iterator iter = replacements.find(op->name); - if (iter != replacements.end() && enclosing_loops.size() == producer_loop_depth) { + if (iter != replacements.end() && current_for == producer_for.get()) { value = iter->second; replacements.erase(iter); }