From 7f3b643a0b5ee0ed6388359c4ca73ecb66bcfa34 Mon Sep 17 00:00:00 2001 From: Steven Raphael Date: Tue, 2 Jun 2026 12:16:26 -0400 Subject: [PATCH 1/2] Implement bounds propagation for sliding window optimization. Co-Authored-By: Claude Sonnet 4.6 --- src/SlidingWindow.cpp | 34 ++++++++++++++++++++++++----- test/correctness/sliding_window.cpp | 24 ++++++++++++++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/SlidingWindow.cpp b/src/SlidingWindow.cpp index b745484270e9..3cca00972d44 100644 --- a/src/SlidingWindow.cpp +++ b/src/SlidingWindow.cpp @@ -223,6 +223,7 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { Expr loop_min; set &slid_dimensions; Scope scope; + Scope &bounds_scope; // For loops strictly between the loop being slid over and the current // node (not including the loop being slid over itself). @@ -282,8 +283,8 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { internal_assert(min_val && max_val); Expr min_req = *min_val; Expr max_req = *max_val; - min_req = expand_expr(min_req, scope); - max_req = expand_expr(max_req, scope); + min_req = simplify(expand_expr(min_req, scope), bounds_scope); + max_req = simplify(expand_expr(max_req, scope), bounds_scope); debug(3) << func_args[i] << ":" << min_req << ", " << max_req << "\n"; if (expr_depends_on_var(min_req, loop_var) || @@ -594,7 +595,10 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { } Stmt visit(const LetStmt *op) override { - ScopedBinding bind(scope, op->name, simplify(expand_expr(op->value, scope))); + ScopedBinding bind_bounds(bounds_scope, op->name, + bounds_of_expr_in_scope(op->value, bounds_scope)); + ScopedBinding bind(scope, op->name, simplify(expand_expr(op->value, scope), bounds_scope)); + Stmt new_body = mutate(op->body); Expr value = op->value; @@ -613,8 +617,10 @@ class SlidingWindowOnFunctionAndLoop : public IRMutator { } public: - SlidingWindowOnFunctionAndLoop(Function f, string v, Expr v_min, set &slid_dimensions) - : func(std::move(f)), loop_var(std::move(v)), loop_min(std::move(v_min)), slid_dimensions(slid_dimensions) { + SlidingWindowOnFunctionAndLoop(Function f, string v, Expr v_min, set &slid_dimensions, + Scope &bounds_scope) + : func(std::move(f)), loop_var(std::move(v)), loop_min(std::move(v_min)), + slid_dimensions(slid_dimensions), bounds_scope(bounds_scope) { } Expr new_loop_min; @@ -756,8 +762,16 @@ class SlidingWindow : public IRMutator { // outermost. list sliding; + Scope bounds_scope; + using IRMutator::visit; + Stmt visit(const LetStmt *op) override { + ScopedBinding bind(bounds_scope, op->name, + bounds_of_expr_in_scope(op->value, bounds_scope)); + return IRMutator::visit(op); + } + Stmt visit(const Realize *op) override { // Find the args for this function map::const_iterator iter = env.find(op->name); @@ -808,6 +822,7 @@ class SlidingWindow : public IRMutator { list> prev_loop_mins; list> new_lets; + for (const Function &func : sliding) { debug(3) << "Doing sliding window analysis on function " << func.name() << "\n"; @@ -827,7 +842,14 @@ class SlidingWindow : public IRMutator { set &slid_dims = slid_dimensions[func.name()]; size_t old_slid_dims_size = slid_dims.size(); - SlidingWindowOnFunctionAndLoop slider(func, name, prev_loop_min, slid_dims); + + Interval min_bounds = bounds_of_expr_in_scope(loop_min, bounds_scope); + Interval max_bounds = bounds_of_expr_in_scope(loop_max, bounds_scope); + ScopedBinding bind_bounds(bounds_scope, op->name, + Interval(min_bounds.min, max_bounds.max)); + + SlidingWindowOnFunctionAndLoop slider(func, name, prev_loop_min, slid_dims, bounds_scope); + body = slider(body); if (func.schedule().memory_type() == MemoryType::Register && diff --git a/test/correctness/sliding_window.cpp b/test/correctness/sliding_window.cpp index 36088804efc1..e7b0d86a433a 100644 --- a/test/correctness/sliding_window.cpp +++ b/test/correctness/sliding_window.cpp @@ -415,6 +415,30 @@ int main(int argc, char **argv) { } } + { + // Sliding a cascade of filters. Halide needs bounds propagation + // to prove that the innermost filters have monotonic bounds. + count = 0; + Func f1, f2, f3, f4, g; + f1(x) = call_counter(x, 0); + f2(x) = f1(0) + f1(x); + f3(x) = f2(0) + f2(x); + f4(x) = f3(0) + f3(x); + g(x) = f4(0) + f4(x); + f1.store_root().compute_at(g, x); + f2.store_root().compute_at(g, x); + f3.store_root().compute_at(g, x); + f4.store_root().compute_at(g, x); + g.bound(x, 0, 10); + + g.realize({10}); + // f1 spans x in [0, 9], so 10 calls when slid. + if (count != 10) { + printf("f1 was called %d times instead of %d times\n", count, 10); + return 1; + } + } + printf("Success!\n"); return 0; } From 269d7d696e06805acdbc3e0b133e05b4085f1043 Mon Sep 17 00:00:00 2001 From: Steven Raphael Date: Wed, 3 Jun 2026 11:19:02 -0400 Subject: [PATCH 2/2] Formatting --- src/SlidingWindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/SlidingWindow.cpp b/src/SlidingWindow.cpp index 3cca00972d44..1d908eb5a435 100644 --- a/src/SlidingWindow.cpp +++ b/src/SlidingWindow.cpp @@ -761,7 +761,6 @@ class SlidingWindow : public IRMutator { // Keep track of realizations we want to slide, from innermost to // outermost. list sliding; - Scope bounds_scope; using IRMutator::visit; @@ -822,7 +821,6 @@ class SlidingWindow : public IRMutator { list> prev_loop_mins; list> new_lets; - for (const Function &func : sliding) { debug(3) << "Doing sliding window analysis on function " << func.name() << "\n";