diff --git a/src/Monotonic.cpp b/src/Monotonic.cpp index aa4a049b0b64..e8dc33afcc81 100644 --- a/src/Monotonic.cpp +++ b/src/Monotonic.cpp @@ -139,11 +139,12 @@ class DerivativeBounds : public IRVisitor { ConstantInterval rb = result; // This is essentially the product rule: a*rb + b*ra - // but only implemented for the case where a or b is constant. - if (auto b = as_const_int_or_uint(op->b)) { - result = ra * (*b); - } else if (auto a = as_const_int_or_uint(op->a)) { - result = rb * (*a); + // but only implemented for the case where a or b doesn't + // depend on the var. + if (rb.is_single_point(0)) { + result = ra * constant_integer_bounds(op->b); + } else if (ra.is_single_point(0)) { + result = rb * constant_integer_bounds(op->a); } else { result = ConstantInterval::everything(); } @@ -179,6 +180,34 @@ class DerivativeBounds : public IRVisitor { } return; } + op->b.accept(this); + if (result.is_single_point(0)) { + // The denominator doesn't depend on the var. If it's known + // to be nonzero, the quotient moves in the same direction as + // the numerator, or the opposite one, by at most as much. + ConstantInterval rb = constant_integer_bounds(op->b); + bool negative = rb < 0; + if (negative) { + rb = -rb; + } + if (rb > 0) { + op->a.accept(this); + ConstantInterval ra = result; + result = ConstantInterval::everything(); + if (ra.min_defined) { + result.min_defined = true; + result.min = std::min(div_imp(ra.min, rb.min), 0); + } + if (ra.max_defined && ra.max != INT64_MIN) { + result.max_defined = true; + result.max = std::max(div_imp(ra.max - 1, rb.min) + 1, 0); + } + if (negative) { + result = -result; + } + return; + } + } } result = ConstantInterval::everything(); } diff --git a/test/correctness/is_monotonic.cpp b/test/correctness/is_monotonic.cpp index 51385e656b72..fdcdc2b6ae0b 100644 --- a/test/correctness/is_monotonic.cpp +++ b/test/correctness/is_monotonic.cpp @@ -56,7 +56,15 @@ int main() { check_decreasing(x <= y); check_decreasing(x < y); check_unknown(x * y); - + check_unknown(x / y); + + // Scaling by something of known sign that doesn't depend on x. + check_increasing(x * max(y, 1)); + check_increasing(max(y, 0) * x); + check_increasing(x / max(y, 1)); + check_decreasing(x * min(y, -1)); + check_decreasing(x / min(y, -1)); + check_decreasing((4 - x) / max(y, 1)); // Not constant despite having constant args, because there's a side-effect. check_unknown(Call::make(Int(32), "foo", {Expr(3)}, Call::Extern)); diff --git a/test/correctness/sliding_window.cpp b/test/correctness/sliding_window.cpp index e7b0d86a433a..ac54a9f56f44 100644 --- a/test/correctness/sliding_window.cpp +++ b/test/correctness/sliding_window.cpp @@ -439,6 +439,25 @@ int main(int argc, char **argv) { } } + { + // Sliding over a division by a param of known sign. + count = 0; + Param k; + Func f, g; + f(x) = call_counter(x, 0); + Expr i = x / max(k, 1); + g(x) = f(i) + f(i + 1); + f.store_root().compute_at(g, x); + + k.set(3); + g.realize({100}); + // f spans x in [0, 34], so 35 calls when slid. + if (count != 35) { + printf("f was called %d times instead of %d times\n", count, 35); + return 1; + } + } + printf("Success!\n"); return 0; }