# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. import pytest import tvm import tvm.testing from tvm import tirx from tvm.arith.analyzer import CompareResult, Extension from tvm.runtime import Object def test_analyzer_is_ffi_object_with_persistent_state(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int64") assert isinstance(analyzer, Object) analyzer.bind(x, tvm.ir.Range(0, 8)) assert analyzer.const_int_bound_is_bound(x) assert analyzer.can_prove(x < 8) assert not analyzer.can_prove(x < 4) bound = analyzer.const_int_bound(x + 1) assert bound.min_value == 1 assert bound.max_value == 8 def test_analyzer_object_constraint_scope_and_override_bind(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int64") with analyzer.constraint_scope(x % 3 == 0): assert analyzer.modular_set(x).coeff == 3 assert analyzer.modular_set(x).coeff != 3 analyzer = tvm.arith.Analyzer() y = tirx.Var("y", "int64") analyzer.bind(y, tirx.const(4, "int64")) tvm.ir.assert_structural_equal(analyzer.simplify(y + 1), tirx.const(5, "int64")) analyzer.bind(y, tirx.const(8, "int64"), allow_override=True) tvm.ir.assert_structural_equal(analyzer.simplify(y + 1), tirx.const(9, "int64")) def test_analyzer_object_update_const_int_bound(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int64") analyzer.update(x, tvm.arith.ConstIntBound(2, 5)) bound = analyzer.const_int_bound(x + 1) assert bound.min_value == 3 assert bound.max_value == 6 def test_analyzer_object_update_modular_set(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int32") assert analyzer.modular_set(x).coeff == 1 analyzer.update(x, tvm.arith.ModularSet(4, 0)) result = analyzer.modular_set(x) assert result.coeff == 4 assert result.base == 0 def test_analyzer_object_update_int_set(): analyzer = tvm.arith.Analyzer() y = tirx.Var("y", "int32") analyzer.update(y, tvm.arith.IntervalSet(0, 8)) int_set = analyzer.int_set(y) assert int_set.min_value.value == 0 assert int_set.max_value.value == 8 def test_analyzer_object_update_rejects_unknown_info(): analyzer = tvm.arith.Analyzer() y = tirx.Var("y", "int32") with pytest.raises(TypeError): analyzer.update(y, "not-an-info-object") def test_analyzer_object_can_prove_comparison_predicates(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int32") analyzer.bind(x, tvm.ir.Range(0, 8)) assert analyzer.can_prove(x >= 0) assert not analyzer.can_prove(x >= 1) assert analyzer.can_prove(x < 8) assert not analyzer.can_prove(x < 7) def test_analyzer_object_update_const_int_bound_half_space(): analyzer = tvm.arith.Analyzer() n = tirx.Var("n", "int32") assert not analyzer.can_prove(n >= 0) analyzer.update(n, tvm.arith.ConstIntBound(0, tvm.arith.ConstIntBound.POS_INF)) assert analyzer.can_prove(n >= 0) def test_analyzer_object_int_set_from_bound_vars(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int32") analyzer.bind(x, tvm.ir.Range(0, 8)) int_set = analyzer.int_set(x + 1) assert int_set.min_value.value == 1 assert int_set.max_value.value == 8 def test_analyzer_object_set_maximum_rewrite_steps(): x = tirx.Var("x", "int32") y = tirx.Var("y", "int32") expr = (x + y) * 2 - x * 2 - y * 2 + tirx.max(x, y) - tirx.min(x, y) capped = tvm.arith.Analyzer() capped.set_maximum_rewrite_steps(1) with pytest.raises(RuntimeError): capped.rewrite_simplify(expr) # A generous limit must not interfere with normal simplification. relaxed = tvm.arith.Analyzer() relaxed.set_maximum_rewrite_steps(1000) relaxed.rewrite_simplify(expr) def test_analyzer_object_try_compare_transitive(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int32") y = tirx.Var("y", "int32") z = tirx.Var("z", "int32") assert analyzer.try_compare(x, y) == CompareResult.UNKNOWN with analyzer.constraint_scope(x < y): with analyzer.constraint_scope(y < z): # Direct known comparison. assert analyzer.try_compare(x, y) == CompareResult.LT # Transitive chain x < y < z is found only when propagation is enabled. assert analyzer.try_compare(x, z) == CompareResult.LT assert analyzer.try_compare(x, z, propagate_inequalities=False) == CompareResult.UNKNOWN def test_analyzer_object_enabled_extensions_round_trip(): analyzer = tvm.arith.Analyzer() assert analyzer.enabled_extensions == Extension.NoExtensions analyzer.enabled_extensions = Extension.ComparisonOfProductAndSum assert analyzer.enabled_extensions == Extension.ComparisonOfProductAndSum analyzer.enabled_extensions = Extension.NoExtensions assert analyzer.enabled_extensions == Extension.NoExtensions def test_analyzer_object_rewrite_simplify_stats(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int32") analyzer.reset_rewrite_simplify_stats() assert analyzer.rewrite_simplify_stats.nodes_visited == 0 analyzer.rewrite_simplify(x + 0) assert analyzer.rewrite_simplify_stats.nodes_visited > 0 analyzer.reset_rewrite_simplify_stats() assert analyzer.rewrite_simplify_stats.nodes_visited == 0 def test_analyzer_object_state_persists_across_ffi_calls(): analyzer = tvm.arith.Analyzer() tile = tirx.Var("tile", "int32") i = tirx.Var("i", "int32") analyzer.bind(tile, tvm.tirx.const(8, "int32")) # The same analyzer object is borrowed by the C++ DetectIterMap entry point; # its binding makes the otherwise-undetectable floormod recognizable. result = tvm.arith.detect_iter_map([i % tile], {i: tvm.ir.Range(0, 32)}, analyzer=analyzer) assert len(result.indices) == 1 # The binding still lives in the same stateful object after the FFI call. tvm.ir.assert_structural_equal(analyzer.simplify(tile), tvm.tirx.const(8, "int32")) def test_analyzer_object_clone_is_independent(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int64") y = tirx.Var("y", "int64") z = tirx.Var("z", "int64") analyzer.bind(x, tvm.ir.Range(0, 8)) clone = analyzer.clone() assert clone is not analyzer assert clone.can_prove(x < 8) clone.bind(y, tvm.ir.Range(0, 4)) assert clone.can_prove(y < 4) assert not analyzer.can_prove(y < 4) analyzer.bind(z, tvm.ir.Range(0, 4)) assert analyzer.can_prove(z < 4) assert not clone.can_prove(z < 4) assert analyzer.can_prove(x < 8) assert clone.can_prove(x < 8) def test_analyzer_object_clone_copies_every_sub_analyzer(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int64") w = tirx.Var("w", "int64") v = tirx.Var("v", "int64") analyzer.bind(x, tvm.ir.Range(0, 8)) analyzer.update(x, tvm.arith.ModularSet(4, 0)) analyzer.bind(w, tirx.const(4, "int64")) analyzer.update(v, tvm.arith.IntervalSet(2, 9)) analyzer.enabled_extensions = Extension.ComparisonOfProductAndSum clone = analyzer.clone() assert clone.can_prove(x < 8) assert clone.modular_set(x).coeff == 4 tvm.ir.assert_structural_equal(clone.simplify(w + 1), tirx.const(5, "int64")) assert clone.int_set(v).max_value.value == 9 assert clone.enabled_extensions == Extension.ComparisonOfProductAndSum assert clone.try_compare(x, tirx.const(0, "int64")) == CompareResult.GE t = tirx.Var("t", "int64") clone.update(x, tvm.arith.ModularSet(8, 0), override=True) clone.update(v, tvm.arith.IntervalSet(0, 3), override=True) clone.bind(w, tirx.const(8, "int64"), allow_override=True) clone.bind(t, tvm.ir.Range(0, 4)) clone.enabled_extensions = Extension.NoExtensions assert analyzer.modular_set(x).coeff == 4 assert clone.modular_set(x).coeff == 8 assert analyzer.int_set(v).max_value.value == 9 assert clone.int_set(v).max_value.value == 3 tvm.ir.assert_structural_equal(analyzer.simplify(w + 1), tirx.const(5, "int64")) tvm.ir.assert_structural_equal(clone.simplify(w + 1), tirx.const(9, "int64")) assert analyzer.enabled_extensions == Extension.ComparisonOfProductAndSum assert clone.enabled_extensions == Extension.NoExtensions assert clone.try_compare(t, tirx.const(0, "int64")) == CompareResult.GE assert analyzer.try_compare(t, tirx.const(0, "int64")) == CompareResult.UNKNOWN def test_analyzer_object_clone_resets_rewrite_stats(): analyzer = tvm.arith.Analyzer() x = tirx.Var("x", "int64") y = tirx.Var("y", "int64") analyzer.bind(x, tvm.ir.Range(0, 8)) analyzer.bind(y, tvm.ir.Range(0, 8)) analyzer.simplify((x + y) * 2 - x - y) source_attempts = analyzer.rewrite_simplify_stats.rewrites_attempted assert source_attempts > 0 clone = analyzer.clone() assert clone.rewrite_simplify_stats.rewrites_attempted == 0 assert analyzer.rewrite_simplify_stats.rewrites_attempted == source_attempts if __name__ == "__main__": tvm.testing.main()