This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new e1e7ac9261 [ARITH] Scope interval constraints to mapped variables 
(#19963)
e1e7ac9261 is described below

commit e1e7ac92617de8bced603cdde56395889f14da9f
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Jul 7 03:47:46 2026 +0800

    [ARITH] Scope interval constraints to mapped variables (#19963)
    
    ## Rationale
    
    Analyzer constraint scopes continue to provide loop-positive facts to
    constant-bound and rewrite proofs. During IntSet relaxation, however, a
    scoped domain constraint is a refinement only for a variable explicitly
    present in the relaxation map. Applying it to an unmapped variable
    reinterprets a free parameter as a relaxation domain and can let a
    loop-local symbol survive recursive interval evaluation.
    
    ## Changes
    
    - Apply scoped IntSet constraints only to variables already present in
    the relaxation map; unmapped variables remain free parameters.
    - Remove the finite-bound restoration fallback and its stronger
    parametric-bound contract.
    - Add a direct compact-buffer regression that prevents a loop-local
    variable from escaping into a function-scope allocation extent.
---
 src/arith/int_set.cc                               | 28 +++++++---------------
 tests/python/arith/test_arith_intset.py            | 18 --------------
 .../test_s_tir_transform_compact_buffer_region.py  | 15 ++++++++++++
 3 files changed, 24 insertions(+), 37 deletions(-)

diff --git a/src/arith/int_set.cc b/src/arith/int_set.cc
index 6c91ab8080..1e6f7497ea 100644
--- a/src/arith/int_set.cc
+++ b/src/arith/int_set.cc
@@ -423,17 +423,7 @@ class IntervalSetEvaluator : public 
ExprFunctor<IntervalSet(const Expr&)> {
     IntervalSet max_set = this->Eval(val->max_value);
     --recur_depth_;
 
-    PrimExpr min_value = min_set->min_value;
-    PrimExpr max_value = max_set->max_value;
-    // IntSet keeps symbolic bounds parametric.  If relaxing a bound under
-    // one-sided constraints loses it to infinity, keep the original bound.
-    if (is_neg_inf(min_value) && val->HasLowerBound()) {
-      min_value = val->min_value;
-    }
-    if (is_pos_inf(max_value) && val->HasUpperBound()) {
-      max_value = val->max_value;
-    }
-    return IntervalSet(min_value, max_value);
+    return IntervalSet(min_set->min_value, max_set->max_value);
   }
 
   IntervalSet VisitExpr_(const IntImmNode* op) final {
@@ -443,6 +433,13 @@ class IntervalSetEvaluator : public 
ExprFunctor<IntervalSet(const Expr&)> {
   IntervalSet VisitExpr_(const VarNode* op) final {
     Var var = ffi::GetRef<Var>(op);
 
+    auto it = dom_map_.find(var);
+    // Scoped constraints refine explicit relaxation domains.  Variables that
+    // are absent from dom_map_ remain free parameters of the relaxed result.
+    if (it == dom_map_.end()) {
+      return IntervalSet::SinglePoint(var.as_or_throw<PrimExpr>());
+    }
+
     ffi::Array<IntSet> values;
     if (dom_constraints_) {
       for (const auto& constraint : *dom_constraints_) {
@@ -452,14 +449,7 @@ class IntervalSetEvaluator : public 
ExprFunctor<IntervalSet(const Expr&)> {
       }
     }
 
-    auto it = dom_map_.find(var);
-    if (it != dom_map_.end()) {
-      values.push_back((*it).second);
-    }
-
-    if (values.empty()) {
-      return IntervalSet::SinglePoint(var.as_or_throw<PrimExpr>());
-    }
+    values.push_back((*it).second);
 
     IntSet intersection = [&]() {
       if (values.size() == 1) {
diff --git a/tests/python/arith/test_arith_intset.py 
b/tests/python/arith/test_arith_intset.py
index d526bff241..e83a8c9f9d 100644
--- a/tests/python/arith/test_arith_intset.py
+++ b/tests/python/arith/test_arith_intset.py
@@ -424,24 +424,6 @@ def test_relax_cyclic_variable_dependency():
     assert res is not None
 
 
-def test_constraint_scope_preserves_parametric_bounds():
-    """One-sided constraints should not erase symbolic bounds."""
-    analyzer = tvm.arith.Analyzer()
-    i = tvm.tirx.Var("i", "int32")
-    m = tvm.tirx.Var("m", "int32")
-    analyzer.bind(i, tvm.ir.Range.from_min_extent(0, m))
-
-    with analyzer.constraint_scope(m > 0):
-        res = analyzer.int_set(i - 1)
-        assert analyzer.can_prove_equal(res.min_value, -1)
-        assert analyzer.can_prove_equal(res.max_value, m - 2)
-
-    with analyzer.constraint_scope(m < 16):
-        res = analyzer.int_set(i)
-        assert analyzer.can_prove_equal(res.min_value, 0)
-        assert analyzer.can_prove_equal(res.max_value, 14)
-
-
 def test_estimate_region_accepts_external_analyzer():
     i = tvm.tirx.Var("i", "int32")
     tile = tvm.tirx.Var("tile", "int32")
diff --git 
a/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py 
b/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
index 3185147f8e..f0000581e1 100644
--- a/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
+++ b/tests/python/s_tir/transform/test_s_tir_transform_compact_buffer_region.py
@@ -1288,6 +1288,21 @@ class TestNonBoolCondition(BaseCompactTest):
                 A[i - 1] = A[i - 1] + 1
 
 
+def test_loop_var_does_not_escape_compacted_buffer_extent():
+    @T.prim_func(private=True, s_tir=True)
+    def before(a: T.handle):
+        n = T.int64()
+        A = T.match_buffer(a, (n,), "int32")
+        tmp = T.alloc_buffer((n,), "int32")
+        for i in range(n):
+            length: T.let[T.int64] = T.ceildiv(n, T.shift_left(T.int64(1), i + 
1))
+            for j in range(length):
+                tmp[j] = A[j]
+
+    after = 
s_tir.transform.CompactBufferAllocation()(tvm.IRModule.from_expr(before))
+    assert tirx.analysis.verify_well_formed(after)
+
+
 class TestCompactSymbolicBound0:
     """Test symbolic bound that get compacted to constant"""
 

Reply via email to