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 c1bf9ce656 [Refactor][Arith] Move conditional bounds into S-TIR
(#20355)
c1bf9ce656 is described below
commit c1bf9ce6561929b40c12058931c43c47a893024b
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Sep 16 07:23:01 2026 -0400
[Refactor][Arith] Move conditional bounds into S-TIR (#20355)
S-TIR buffer compaction and block-access analysis are the remaining
compiler users of the integer-inequality solver. Move their scoped
conditional bounds and the required solver implementation into private
S-TIR analysis files, keeping the general arithmetic analyses in arith.
Remove the retired equation and deskew solvers, their
constraint-transform machinery, and the old public C++/Python/FFI
interfaces. Retain the useful conditional-bound examples through
block-access analysis.
---
include/tvm/arith/int_solver.h | 328 -------------
python/tvm/arith/__init__.py | 1 -
python/tvm/arith/int_solver.py | 183 -------
python/tvm/backend/cuda/cpp/asm.py | 24 +-
python/tvm/backend/cuda/op.py | 7 +-
python/tvm/testing/utils.py | 57 ---
src/arith/int_constraints.cc | 300 ------------
src/arith/presburger_set.cc | 1 -
src/arith/solve_linear_equation.cc | 489 -------------------
.../analysis/conditional_bounds.cc} | 540 +++++++++++++--------
src/s_tir/analysis/conditional_bounds.h | 82 ++++
.../analysis/sblock_access_region_detector.cc | 14 +-
src/s_tir/transform/compact_buffer_region.cc | 2 +-
src/tirx/script/printer/stmt.cc | 2 +
src/tirx/transform/ir_utils.cc | 145 ------
src/tirx/transform/ir_utils.h | 43 --
.../arith/test_arith_solve_linear_equations.py | 186 -------
.../arith/test_arith_solve_linear_inequality.py | 223 ---------
.../s_tir/analysis/test_sblock_access_region.py | 67 +++
tests/python/tirx/codegen/test_cuda_wait_until.py | 8 +-
20 files changed, 511 insertions(+), 2191 deletions(-)
diff --git a/include/tvm/arith/int_solver.h b/include/tvm/arith/int_solver.h
deleted file mode 100644
index ccca3dcbcb..0000000000
--- a/include/tvm/arith/int_solver.h
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * 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.
- */
-
-/*!
- * \file tvm/arith/int_solver.h
- * \brief integer constraints data structures and solvers
- */
-#ifndef TVM_ARITH_INT_SOLVER_H_
-#define TVM_ARITH_INT_SOLVER_H_
-
-#include <tvm/ir/expr.h>
-#include <tvm/ir/prim/expr.h>
-
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "analyzer.h"
-
-namespace tvm {
-namespace arith {
-
-// According to experiments two best simplifications orders were can->rw and
rw->can->rw,
-// but rw->can->rw is better for a couple of cases.
-// Also we should end with rw because it factors multipliers out.
-constexpr int kSimplifyRewriteCanonicalRewrite = 3;
-
-/*!
- * \brief Represent integer grouped bounds which are classified into
- * lower bounds (inclusive), upper bounds (inclusive) and equalities.
- * It also contains coefficient as a multiplier for the bounds, i.e.,
- * coef * var >= lower
- * coef * var == equal
- * coef * var <= upper
- * \sa IntGroupBounds
- */
-class IntGroupBoundsNode : public ffi::Object {
- public:
- PrimExpr coef;
- ffi::Array<PrimExpr> lower;
- ffi::Array<PrimExpr> equal;
- ffi::Array<PrimExpr> upper;
-
- static void RegisterReflection() {
- namespace refl = tvm::ffi::reflection;
- refl::ObjectDef<IntGroupBoundsNode>()
- .def_ro("coef", &IntGroupBoundsNode::coef)
- .def_ro("lower", &IntGroupBoundsNode::lower)
- .def_ro("equal", &IntGroupBoundsNode::equal)
- .def_ro("upper", &IntGroupBoundsNode::upper);
- }
-
- static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind =
kTVMFFISEqHashKindTreeNode;
- TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IntGroupBounds",
IntGroupBoundsNode, ffi::Object);
-};
-
-/*!
- * \brief Managed reference to IntGroupBoundsNode.
- * \sa IntGroupBoundsNode
- */
-class IntGroupBounds : public ffi::ObjectRef {
- public:
- /*!
- * \brief Constructor by fields
- * \param coef The coefficient. Must be integer.
- * coef * var >= lower
- * coef * var == equal
- * coef * var >= upper
- * \param lower the lower bounds (include)
- * \param equal equalities
- * \param upper the upper bounds (include)
- */
- TVM_DLL IntGroupBounds(PrimExpr coef, ffi::Array<PrimExpr> lower,
ffi::Array<PrimExpr> equal,
- ffi::Array<PrimExpr> upper);
-
- /*!
- * \brief Construct bounds from a range.
- * \param r The range
- * \return constructed bounds.
- */
- static IntGroupBounds FromRange(const Range& r);
-
- /*!
- * \brief Find the best range from the grouped bounds.
- * \param vranges_addl additional variable ranges that help infer the best
range.
- * \return The best range (has the least difference between the lower bound
and upper bound).
- * undefined if (-inf, +inf).
- */
- Range FindBestRange(const ffi::Map<Var, Range>& vranges_addl = {}) const;
-
- /*!
- * \brief Combine the bounds with another range.
- * \param r range to be combined.
- * \return combined bounds.
- */
- IntGroupBounds operator+(const Range& r);
-
- TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntGroupBounds, ffi::ObjectRef,
IntGroupBoundsNode);
-};
-
-/*!
- * \brief Represent integer constrains including (integer) variables, their
ranges and
- * the relations between them (either equations or inequalities).
- * \sa LinearSystem
- */
-class IntConstraintsNode : public ffi::Object {
- public:
- // e.g., \alpha, \beta, must be integers
- ffi::Array<PrimVar> variables;
- // e.g., 1 <= \alpha <= N, etc.
- // it is absolutely ok to include ranges for parameters
- // (variables that are not in this->variables) in this map
- ffi::Map<Var, Range> ranges;
- // linear equalities or inequalities
- // e.g., A \alpha = \beta or A \alpha <= \beta
- ffi::Array<PrimExpr> relations;
-
- static void RegisterReflection() {
- namespace refl = tvm::ffi::reflection;
- refl::ObjectDef<IntConstraintsNode>()
- .def_ro("variables", &IntConstraintsNode::variables)
- .def_ro("ranges", &IntConstraintsNode::ranges)
- .def_ro("relations", &IntConstraintsNode::relations);
- }
-
- static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind =
kTVMFFISEqHashKindTreeNode;
- TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IntConstraints",
IntConstraintsNode, ffi::Object);
-};
-
-/*!
- * \brief Managed reference to IntConstraintsNode.
- * \sa IntConstraintsNode
- */
-class IntConstraints : public ffi::ObjectRef {
- public:
- /*!
- * \brief Constructor by fields
- * \param variables The variables in the constraints, must be integers.
- * \param ranges The ranges of the variables.
- * \param relations The linear relations between the variables
- * (either equations or inequalities)
- */
- TVM_DLL IntConstraints(ffi::Array<PrimVar> variables, ffi::Map<Var, Range>
ranges,
- ffi::Array<PrimExpr> relations);
-
- TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntConstraints, ffi::ObjectRef,
IntConstraintsNode);
-};
-
-/*!
- * \brief We can have different set of variables to represent the same
constraints.
- * For example, the following two systems are equivalent,
- * {a + b = 0 | a >= 0, b >= 0} and
- * {m - n = 0 | m >= 0, n <= 0}
- * This data structure represents the transformation
- * between two equivalent linear systems.
- * In the above example,
- * src : {a + b = 0 | a >= 0, b >= 0}
- * dst : {m - n = 0 | m >= 0, n <= 0}
- * src_to_dst : {a -> m, b -> -n}
- * dst_to_src : {m -> a, n -> -b}
- * \sa IntConstraintsTransform
- */
-class IntConstraintsTransformNode : public ffi::Object {
- public:
- IntConstraints src;
- IntConstraints dst;
- ffi::Map<Var, PrimExpr> src_to_dst;
- ffi::Map<Var, PrimExpr> dst_to_src;
-
- static void RegisterReflection() {
- namespace refl = tvm::ffi::reflection;
- refl::ObjectDef<IntConstraintsTransformNode>()
- .def_ro("src", &IntConstraintsTransformNode::src)
- .def_ro("dst", &IntConstraintsTransformNode::dst)
- .def_ro("src_to_dst", &IntConstraintsTransformNode::src_to_dst)
- .def_ro("dst_to_src", &IntConstraintsTransformNode::dst_to_src);
- }
-
- static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind =
kTVMFFISEqHashKindTreeNode;
- TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IntConstraintsTransform",
IntConstraintsTransformNode,
- ffi::Object);
-};
-
-/*!
- * \brief Managed reference to IntConstraintsTransformNode.
- * \sa IntConstraintsTransformNode
- */
-class IntConstraintsTransform : public ffi::ObjectRef {
- public:
- /*!
- * \brief Constructor by fields
- * \param src source integer constraints, e.g., {a + b = 0 | a >= 0,
b >= 0}
- * \param dst integer constraints equivalent to the source,
- * e.g., {m - n = 0 | m >= 0, n <= 0}
- * \param src_to_dst mapping from variables in the \p src to the variables
in the \p dst,
- * e.g., {a -> m, b -> -n}
- * \param dst_to_src mapping from variables in the \p dst to the variables
in the \p src,
- * e.g., {m -> a, n -> -b}
- */
- TVM_DLL IntConstraintsTransform(IntConstraints src, IntConstraints dst,
- ffi::Map<Var, PrimExpr> src_to_dst,
- ffi::Map<Var, PrimExpr> dst_to_src);
-
- /*!
- * \brief Chain-compose two IntConstraintsTransform together.
- * this->dst must be the same as other->src.
- * @param other another IntConstraintsTransform whose src is same as
this->dst.
- * @return composed IntConstraintsTransform(this->src, other->dst)
- * with its variables and ranges are properly modified.
- */
- IntConstraintsTransform operator+(const IntConstraintsTransform& other)
const;
-
- TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntConstraintsTransform,
ffi::ObjectRef,
- IntConstraintsTransformNode);
-};
-
-typedef std::pair<ffi::Map<Var, IntGroupBounds>, ffi::Array<PrimExpr>>
PartialSolvedInequalities;
-
-/*!
- * \brief Obtain Smith Normal Form of linear equation A x = y.
- * Smith Normal Form of matrix A_{mxn} is S_{mxn} = U_{mxm} A_{mxn}
V_{nxn},
- * in which S_{mxn} is diag(s1, s2, ..., sr, 0, ..., 0) and r is the
rank of A.
- * NOTE: Although in standard Smith Normal Form the diagonal elements
satisfy
- * s_i | s_{i+1} (| means divides), the implement here does not
guarantee it.
- * TODO(yzhliu): From sergei-grechanik:
- * computing the proper Smith normal form may improve stability of
automatic
- * differentiation (generating the same gradient code for slightly different
but equivalent input
- * code U_{mxm} and V_{nxn} are invertible matrices. This function modifies \p
S to be S_{mxn}, \p V
- * to be V_{nxn}, \p y to be U_{mxm} y_{mx1} and \p x to be V^{-1} x. \param S
the original
- * A_{mxn}, it will be modified to S_{mxn} \param V an identity matrix, it
will be modified to
- * V_{nxn} \param x the x in A x = y. it will be modified to V^{-1}_{nxn}
x_{nx1} \param y the y
- * in A x = y. it will be modified to U_{mxm} y_{mx1}
- */
-void SmithNormalFormDiag(std::vector<std::vector<int64_t>>* S,
std::vector<std::vector<int64_t>>* V,
- std::vector<PrimExpr>* x, std::vector<PrimExpr>* y);
-
-/*!
- * \brief Solve linear equations.
- * \param system_to_solve the variables to solve, their ranges, and a list of
equations.
- * \return A new linear system, with less variables (if \p system_to_solve is
NOT of full rank),
- * or no variable (if \p system_to_solve is of full rank),
- * or an empty linear system (if \p system_to_solve is unsolvable).
- * It also provides the ranges of the variables in the new system,
- * as well as inequalities inferred from the \p system_to_solve.
- * You can get the mapping from the original variables to the
solution via ret->src_to_dst.
- */
-IntConstraintsTransform SolveLinearEquations(const IntConstraints&
system_to_solve);
-
-/*!
- * \brief Solve linear inequalities.
- * \param system_to_solve the variables to solve, their ranges, and a list of
inequalities.
- * The inequalities are rewritten using Fourier-Motzkin elimination.
- * This function takes an array of (in)equalities and an array of
variables, and essentially
- * rewrites the (in)equalities into an array of (in)equalities of the
following form,
- *
- * x0 >= f0(x1, x2, ..., xn)
- * x0 <= g0(x1, x2, ..., xn)
- * x1 >= f1(x2, ..., xn)
- * x1 <= g1(x2, ..., xn)
- * ...
- * xn >= fn() // just a constant
- * xn <= gn() // just a constant
- *
- * \return A map of variables and their solved bounds,
- * and constrains that cannot be solved to bounds.
- */
-PartialSolvedInequalities SolveLinearInequalities(const IntConstraints&
system_to_solve);
-
-/*!
- * \brief Combine the information into an array of (in)equalities.
- * \param variables The variables in \p bounds.
- * It is used to determine the iteration order to avoid indeterministic
results.
- * \param bounds grouped boundary of the variables.
- * \param relations other relations.
- */
-ffi::Array<PrimExpr> AsConditions(const ffi::Array<PrimVar>& variables,
- const ffi::Map<Var, IntGroupBounds>& bounds,
- const ffi::Array<PrimExpr>& relations);
-
-/*!
- * \brief Solve linear inequalities and infer the range of each variable.
- * \param system_to_solve the variables to solve, their ranges, and a list of
inequalities.
- * \return The result ranges for each variables.
- * The returned IntConstraints(variables, ranges, relations) contains,
- * 1. variables - the variables that have been solved.
- * 2. ranges - the best range of each variable.
- * 3. relations - constraints that cannot be transformed to
- * Range will be stored in relations.
- */
-IntConstraints SolveInequalitiesToRange(const IntConstraints& system_to_solve);
-
-/*!
- * \brief Solve linear inequalities and deskew the ranges towards zero.
- * \param system_to_solve the variables to solve, their ranges, and a list of
inequalities.
- * \return A transform (src IntConstraints -> dst IntConstraints)
- * from original variables to a set of new variables.
- * The ranges of new variables always start from zero,
- * their extents are solved from \p system_to_solve.
- * src IntConstraints is the same as \p system_to_solve.
- * dst IntConstraints(variables, ranges, relations) contains,
- * 1. variables - the variables that have been solved.
- * 2. ranges - the best range (start from zero) of each variable.
- * 3. relations - constraints that cannot be transformed to
- * Range will be stored in relations.
- * Variable mapping can be obtained from
- * IntConstraintsTransform.src_to_dst and
IntConstraintsTransform.dst_to_src.
- */
-IntConstraintsTransform SolveInequalitiesDeskewRange(const IntConstraints&
system_to_solve);
-
-} // namespace arith
-} // namespace tvm
-#endif // TVM_ARITH_INT_SOLVER_H_
diff --git a/python/tvm/arith/__init__.py b/python/tvm/arith/__init__.py
index e2a4bf664b..b646a6bf69 100644
--- a/python/tvm/arith/__init__.py
+++ b/python/tvm/arith/__init__.py
@@ -37,7 +37,6 @@ from .analyzer import (
)
from .bound import deduce_bound
from .pattern import detect_linear_equation, detect_clip_bound
-from .int_solver import solve_linear_equations, solve_linear_inequalities
from .iter_affine_map import IterMapExpr, IterMark, IterSplitExpr, IterSumExpr
from .iter_affine_map import (
detect_iter_map,
diff --git a/python/tvm/arith/int_solver.py b/python/tvm/arith/int_solver.py
deleted file mode 100644
index a50d6818fd..0000000000
--- a/python/tvm/arith/int_solver.py
+++ /dev/null
@@ -1,183 +0,0 @@
-# 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.
-"""integer constraints data structures and solvers"""
-
-import tvm_ffi
-
-from tvm.runtime import Object
-
-from . import _ffi_api
-
-
-@tvm_ffi.register_object("arith.IntGroupBounds")
-class IntGroupBounds(Object):
- """Represent integer grouped bounds which are classified into
- lower bounds (include), upper bounds (include) and equalities.
-
- Parameters
- ----------
- coef : tvm.ir.Expr
- The coefficient. Must be integer type.
- coef * var >= lower
- coef * var == equal
- coef * var >= upper
- lower : List[tvm.ir.Expr]
- the lower bounds (include)
- equal : List[tvm.ir.Expr]
- equalities
- upper : List[tvm.ir.Expr]
- the upper bounds (include)
- """
-
- def __init__(self, coef, lower, equal, upper):
- self.__init_handle_by_constructor__(_ffi_api.IntGroupBounds, coef,
lower, equal, upper)
-
- @staticmethod
- def from_range(rng):
- """Construct a IntGroupedBounds by Range.
-
- Parameters
- ----------
- rng : tvm.ir.Range
-
-
- Returns
- -------
- ret : Range
- The constructed range.
- """
- return _ffi_api.IntGroupBounds_from_range(rng)
-
- def find_best_range(self):
- """Return the best range from the grouped bounds.
- None if (-inf, +inf).
- """
- return _ffi_api.IntGroupBounds_FindBestRange(self)
-
-
-@tvm_ffi.register_object("arith.IntConstraints")
-class IntConstraints(Object):
- """Represent a set of integer constraints including variables, their
ranges and
- the relations between them (either equations or inequalities)
-
- Parameters
- ----------
- variables : List[tvm.tirx.Var]
- The variables in the constraints. Must be integers
- ranges : Map[tvm.tirx.Var, tvm.ir.Range]
- The ranges of the variables.
- relations : List[tvm.ir.Expr]
- The relations between the variables (either equations or inequalities)
- """
-
- def __init__(self, variables, ranges, relations):
- self.__init_handle_by_constructor__(_ffi_api.IntConstraints,
variables, ranges, relations)
-
-
-@tvm_ffi.register_object("arith.IntConstraintsTransform")
-class IntConstraintsTransform(Object):
- """We can have different set of variables to represent the same integer
constraints.
- For example, the following two constrains are equivalent,
- {a + b = 0 | a >= 0, b >= 0} and
- {m - n = 0 | m >= 0, n <= 0}
- This data structure represents the transformation
- between two equivalent integer constraints.
- In the above example,
- src : {a + b = 0 | a >= 0, b >= 0}
- dst : {m - n = 0 | m >= 0, n <= 0}
- src_to_dst : {a -> m, b -> -n}
- dst_to_src : {m -> a, n -> -b}
-
- Parameters
- ----------
- src : arith.IntConstraints
- source integer constraints, e.g., {a + b = 0 | a >= 0, b >= 0}
- dst : arith.IntConstraints
- integer constraints equivalent to the source, e.g., {m - n = 0 | m >=
0, n <= 0}
- src_to_dst : Map[tvm.tirx.Var, tvm.ir.Expr]
- mapping from variables in the src to the variables in the dst,
- e.g., {a -> m, b -> -n}
- dst_to_src : Map[tvm.tirx.Var, tvm.ir.Expr]
- mapping from variables in the dst to the variables in the src,
- e.g., {m -> a, n -> -b}
- """
-
- def __init__(self, src, dst, src_to_dst, dst_to_src):
- self.__init_handle_by_constructor__(
- _ffi_api.IntConstraintsTransform, src, dst, src_to_dst, dst_to_src
- )
-
-
-def solve_linear_equations(equations, variables=None, ranges=None):
- """Solve linear equations.
-
- Parameters
- ----------
- equations: List[tvm.ir.Expr] or IntConstraints
- The equations of the variables
- variables : Optional[List[tvm.tirx.Var]]
- The variables in the system.
- ranges : Optional[Map[tvm.tirx.Var, tvm.ir.Range]]
- The ranges of the variables.
-
- Returns
- -------
- int_constraints_transform : IntConstraintsTransform
- New integer constraints, with less variables (if the problem is NOT of
full rank),
- or no variable (if the problem is of full rank),
- or an empty integer constraints (if the problem is unsolvable).
- It also provides the ranges of the variables in the new system,
- as well as inequalities inferred from the problem.
- You can get the mapping from the original variables to the solution via
- int_constraints_transform.src_to_dst.
- """
- if isinstance(equations, IntConstraints):
- return _ffi_api.SolveLinearEquations(equations)
- return _ffi_api.SolveLinearEquations(variables, ranges, equations)
-
-
-def solve_linear_inequalities(equations, variables=None, ranges=None,
deskew_range=False):
- """Solve linear inequalities.
-
- Parameters
- ----------
- equations : List[tvm.ir.Expr] or IntConstraints
- The inequalities of the variables
- variables : Optional[List[tvm.tirx.Var]]
- The variables in the system.
- ranges : Optional[Map[tvm.tirx.Var, tvm.ir.Range]]
- The ranges of the variables.
- deskew_range: Optional[bool]
- Whether deskew the result ranges to be started from zero.
- Default false.
-
- Returns
- -------
- ret_ranges: IntConstraints or IntConstraintsTransform
- The result ranges for each variables.
- Constrains that cannot be transformed to Range will be stored in
IntConstraints.relations.
- If deskew_range is set (=True), the result ranges will be deskewed to
be started from zero.
- New variables are created accordingly therefore
IntConstraintsTransform is returned.
- """
- solver = (
- _ffi_api.SolveInequalitiesDeskewRange if deskew_range else
_ffi_api.SolveInequalitiesToRange
- )
- if isinstance(equations, IntConstraints):
- assert variables is None
- assert ranges is None
- return solver(equations)
- return solver(variables, ranges, equations)
diff --git a/python/tvm/backend/cuda/cpp/asm.py
b/python/tvm/backend/cuda/cpp/asm.py
index b2c94b47f5..ccbe995be1 100644
--- a/python/tvm/backend/cuda/cpp/asm.py
+++ b/python/tvm/backend/cuda/cpp/asm.py
@@ -35,7 +35,6 @@ from ..codegen.schema import device_intrinsic
from ..codegen.types import PTXDataType
from ..codegen.utils import parse_str
-
# =============================================================================
# Declared synchronization words. The four direct forms emit exactly what their
# raw PTX spellings do; only the operation's identity differs, which is what
@@ -109,9 +108,10 @@ def _wait_until_word_suffix(ptr, requested, what):
# A 128-bit word spans two 64-bit elements, so a pointer into the pair is
# how a kernel names it; asking for `.b128` there is not a respelling of
# the pointee but the statement that the word is the wider one.
- if requested == "b128" and _WAIT_UNTIL_PTX_WIDTH.get(
- _WAIT_UNTIL_SCALARS.get(pointee, "")
- ) in (32, 64):
+ if requested == "b128" and
_WAIT_UNTIL_PTX_WIDTH.get(_WAIT_UNTIL_SCALARS.get(pointee, "")) in (
+ 32,
+ 64,
+ ):
return requested
if pointee in _WAIT_UNTIL_SCALARS:
return _wait_until_scalar_suffix(pointee, requested)
@@ -191,26 +191,20 @@ def cuda_wait_until(dst, ptr, condition, scope, space,
ptx_type, backoff_ns):
# ISA 8.4.2 puts it and `.relaxed` in one class, and measured they are
# within 0.1% everywhere -- but `.relaxed.<scope>` says the scope out loud
# instead of resting on `volatile` meaning `.sys`.
- load_call, tags = _wait_until_forward(
- f"ld.relaxed.{scope}.{space}.{suffix}", dst, ptr
- )
+ load_call, tags =
_wait_until_forward(f"ld.relaxed.{scope}.{space}.{suffix}", dst, ptr)
# The helper is named after the poll, which is the load that repeats; the
# closing acquire hangs off that name with an `_acquire` suffix.
load_name = parse_str(load_call.args[0])
name = load_name.replace("ptx_ld_", "cuda_wait_until_")
source = load_call.args[-1].value.replace(load_name, name + "_load")
- acquire_call, _ = _wait_until_forward(
- f"ld.acquire.{scope}.{space}.{suffix}", dst, ptr
- )
+ acquire_call, _ =
_wait_until_forward(f"ld.acquire.{scope}.{space}.{suffix}", dst, ptr)
acquire_name = parse_str(acquire_call.args[0])
acquire_source = acquire_call.args[-1].value.replace(acquire_name, name +
"_acquire")
source += "\n" + acquire_source
# NVRTC has no `__typeof__`, and `decltype` on the destination yields a
# reference that cannot be declared uninitialized, so the scratch takes
# the C type the generated helper already spells in its signature.
- scratch_type = re.search(
- rf"void\s+{re.escape(name)}_acquire\(\s*([\w:]+)\s*&", acquire_source
- )
+ scratch_type =
re.search(rf"void\s+{re.escape(name)}_acquire\(\s*([\w:]+)\s*&", acquire_source)
if scratch_type is None: # pragma: no cover - the helper shape is fixed
raise RuntimeError(f"cannot read the destination type of
{name}_acquire")
closing = (
@@ -247,7 +241,7 @@ def cuda_wait_until(dst, ptr, condition, scope, space,
ptx_type, backoff_ns):
source += (
f"\n#define {name}(dst, ptr, predicate) "
f"do {{ {name}_load((dst), (ptr)); if (!(predicate)) {{ "
- f"_Pragma(\"unroll 1\") "
+ f'_Pragma("unroll 1") '
f"do {{ {name}_load((dst), (ptr)); }} while (!(predicate)); }}"
f"{closing} }} while (0)\n"
)
@@ -260,7 +254,7 @@ def cuda_wait_until(dst, ptr, condition, scope, space,
ptx_type, backoff_ns):
source += (
f"\n#define {name}(dst, ptr, predicate, backoff_ns) "
f"do {{ {name}_load((dst), (ptr)); if (!(predicate)) {{ "
- f"_Pragma(\"unroll 1\") "
+ f'_Pragma("unroll 1") '
f"while (1) {{ __nanosleep(backoff_ns); {name}_load((dst), (ptr));
"
f"if (predicate) break; }} }}"
f"{closing} }} while (0)\n"
diff --git a/python/tvm/backend/cuda/op.py b/python/tvm/backend/cuda/op.py
index 0d6ea8806c..8c6a6a02ef 100644
--- a/python/tvm/backend/cuda/op.py
+++ b/python/tvm/backend/cuda/op.py
@@ -421,13 +421,10 @@ def _validate_wait_until_attrs(scope, space,
ptx_type=None):
if space == "shared"
else ""
)
- raise ValueError(
- f"invalid space={space!r}; expected one of
{_WAIT_UNTIL_SPACE}{detail}"
- )
+ raise ValueError(f"invalid space={space!r}; expected one of
{_WAIT_UNTIL_SPACE}{detail}")
if ptx_type is not None and ptx_type not in _WAIT_UNTIL_PTX_TYPES:
raise ValueError(
- f"invalid ptx_type={ptx_type!r}; expected one of "
- f"{tuple(sorted(_WAIT_UNTIL_PTX_TYPES))}"
+ f"invalid ptx_type={ptx_type!r}; expected one of
{tuple(sorted(_WAIT_UNTIL_PTX_TYPES))}"
)
diff --git a/python/tvm/testing/utils.py b/python/tvm/testing/utils.py
index 076a12c361..05f1e97c24 100644
--- a/python/tvm/testing/utils.py
+++ b/python/tvm/testing/utils.py
@@ -337,63 +337,6 @@ def check_bool_expr_is_true(bool_expr, vranges, cond=None):
)
-def check_int_constraints_trans_consistency(constraints_trans, vranges=None):
- """Check IntConstraintsTransform is a bijective transformation.
-
- Parameters
- ----------
- constraints_trans : arith.IntConstraintsTransform
- Integer constraints transformation
- vranges: Dict[tvm.tirx.Var, tvm.ir.Range]
- Free variables and their ranges
- """
- if vranges is None:
- vranges = {}
-
- def _check_forward(constraints1, constraints2, varmap, backvarmap):
- ana = tvm.arith.Analyzer()
- all_vranges = vranges.copy()
- all_vranges.update({v: r for v, r in constraints1.ranges.items()})
-
- # Check that the transformation is injective
- cond_on_vars = tvm.tirx.const(1, "bool")
- for v in constraints1.variables:
- if v in varmap:
- # variable mapping is consistent
- v_back = ana.simplify(_substitute_tir_vars(varmap[v],
backvarmap))
- cond_on_vars = tvm.te.all(cond_on_vars, v == v_back)
- # Also we have to check that the new relations are true when old
relations are true
- cond_subst = _substitute_tir_vars(
- tvm.te.all(tvm.tirx.const(1, "bool"), *constraints2.relations),
backvarmap
- )
- # We have to include relations from vranges too
- for v in constraints2.variables:
- if v in constraints2.ranges:
- r = constraints2.ranges[v]
- range_cond = tvm.te.all(v >= r.min, v < r.min + r.extent)
- range_cond = _substitute_tir_vars(range_cond, backvarmap)
- cond_subst = tvm.te.all(cond_subst, range_cond)
- cond_subst = ana.simplify(cond_subst)
- check_bool_expr_is_true(
- tvm.te.all(cond_subst, cond_on_vars),
- all_vranges,
- cond=tvm.te.all(tvm.tirx.const(1, "bool"),
*constraints1.relations),
- )
-
- _check_forward(
- constraints_trans.src,
- constraints_trans.dst,
- constraints_trans.src_to_dst,
- constraints_trans.dst_to_src,
- )
- _check_forward(
- constraints_trans.dst,
- constraints_trans.src,
- constraints_trans.dst_to_src,
- constraints_trans.src_to_dst,
- )
-
-
def _get_targets(target_names=None):
if target_names is None:
target_names = _tvm_test_targets()
diff --git a/src/arith/int_constraints.cc b/src/arith/int_constraints.cc
deleted file mode 100644
index 1d7bdf90e0..0000000000
--- a/src/arith/int_constraints.cc
+++ /dev/null
@@ -1,300 +0,0 @@
-/*
- * 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.
- */
-
-/*!
- * \file int_constraints.cc
- * \brief The integer constraints data structures.
- */
-#include <tvm/arith/analyzer.h>
-#include <tvm/arith/int_solver.h>
-#include <tvm/ffi/extra/structural_mutate.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
-#include <tvm/ir/prim/expr.h>
-#include <tvm/tirx/op.h>
-
-#include <algorithm>
-#include <unordered_map>
-#include <utility>
-
-namespace tvm {
-namespace arith {
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- IntGroupBoundsNode::RegisterReflection();
- IntConstraintsNode::RegisterReflection();
- IntConstraintsTransformNode::RegisterReflection();
-}
-
-ffi::Array<PrimExpr> AsConditions(const ffi::Array<PrimVar>& variables,
- const ffi::Map<Var, IntGroupBounds>& bounds,
- const ffi::Array<PrimExpr>& relations) {
- ffi::Array<PrimExpr> res;
- // use variables to keep the order of iteration
- // so as to get rid of any non-determinism.
- TVM_FFI_ICHECK_EQ(variables.size(), bounds.size());
- for (const auto v : variables) {
- TVM_FFI_ICHECK(bounds.count(v));
- const auto& bnds = bounds[v];
- PrimExpr lhs = bnds->coef * v.as_or_throw<PrimExpr>();
- for (const PrimExpr& rhs : bnds->equal) {
- res.push_back(lhs == rhs);
- }
- for (const PrimExpr& rhs : bnds->lower) {
- res.push_back(lhs >= rhs);
- }
- for (const PrimExpr& rhs : bnds->upper) {
- res.push_back(lhs <= rhs);
- }
- }
- for (const PrimExpr& e : relations) {
- res.push_back(e);
- }
- return res;
-}
-
-IntGroupBounds::IntGroupBounds(PrimExpr coef, ffi::Array<PrimExpr> lower,
- ffi::Array<PrimExpr> equal,
ffi::Array<PrimExpr> upper) {
- PrimType coef_ty = coef.ty();
- TVM_FFI_ICHECK(coef_ty.MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt))
- << "Coefficient in IntGroupBounds must be integers";
- ffi::ObjectPtr<IntGroupBoundsNode> node =
ffi::make_object<IntGroupBoundsNode>();
- node->coef = std::move(coef);
- node->lower = std::move(lower);
- node->equal = std::move(equal);
- node->upper = std::move(upper);
- data_ = std::move(node);
-}
-
-IntGroupBounds IntGroupBounds::FromRange(const Range& r) {
- Analyzer analyzer;
- PrimExpr coef = tirx::MakeConst(r->min.ty(), 1);
- ffi::Array<PrimExpr> equal;
- ffi::Array<PrimExpr> lower;
- ffi::Array<PrimExpr> upper;
- if (tirx::is_one(r->extent)) {
- equal.push_back(r->min);
- } else {
- lower.push_back(r->min);
- upper.push_back(analyzer->Simplify(r->min + r->extent - 1));
- }
- return IntGroupBounds(coef, lower, equal, upper);
-}
-
-IntGroupBounds IntGroupBounds::operator+(const Range& r) {
- Analyzer analyzer;
- ffi::Array<PrimExpr> equal;
- ffi::Array<PrimExpr> lower;
- ffi::Array<PrimExpr> upper;
- const PrimExpr& coef = operator->()->coef;
- if (tirx::is_one(r->extent)) {
- equal.push_back(analyzer->Simplify(r->min * coef));
- } else {
- lower.push_back(analyzer->Simplify(r->min * coef));
- upper.push_back(analyzer->Simplify((r->min + r->extent - 1) * coef));
- }
- for (const auto& eq : operator->()->equal) equal.push_back(eq);
- for (const auto& lb : operator->()->lower) lower.push_back(lb);
- for (const auto& ub : operator->()->upper) upper.push_back(ub);
- return IntGroupBounds(coef, lower, equal, upper);
-}
-
-Range IntGroupBounds::FindBestRange(const ffi::Map<Var, Range>& vranges_addl)
const {
- Analyzer analyzer;
- analyzer->Bind(vranges_addl);
-
- std::unordered_map<const VarNode*, IntSet> var_intsets;
- for (auto kv : vranges_addl) {
- var_intsets[kv.first.get()] = IntSet::FromRange(kv.second);
- }
-
- const ffi::Array<PrimExpr>& equal = operator->()->equal;
- const PrimExpr& coef = operator->()->coef;
-
- std::vector<PrimExpr> lowers(equal.begin(), equal.end());
- std::vector<PrimExpr> uppers(equal.begin(), equal.end());
- for (const auto& expr : operator->()->lower) {
- lowers.push_back(expr);
- }
- for (const auto& expr : operator->()->upper) {
- uppers.push_back(expr);
- }
-
- if (lowers.size() == 1 && uppers.size() == 1 && tirx::is_one(coef)) {
- return Range(analyzer->Simplify(lowers[0]), analyzer->Simplify(uppers[0] +
1));
- }
-
- // Here we will try all pairs of lower and upper bounds and find the best
pair, that is, the
- // pair with the minimal difference between the upper and the lower.
- // Note that the bounds are for v, not for v*coef
-
- // The lower bound of the best pair so far
- PrimExpr best_lower;
- // The difference between the upper and the lower of the best pair, maybe
overapproximation
- PrimExpr best_diff_over;
-
- for (const PrimExpr& low : lowers) {
- for (const PrimExpr& upp : uppers) {
- // Since diff may depend on some other variables, we compute its
overapproximation
- ffi::Optional<PrimExpr> diff_over;
- PrimExpr diff_1 = analyzer->Simplify(floordiv(upp - low, coef), 3);
- IntSet diff_set1 = EvalSet(diff_1, var_intsets);
- if (diff_set1.HasUpperBound()) {
- diff_over = analyzer->Simplify(diff_set1.max(), 3);
- }
-
- // low is the lower bound for v*coef, but we need the lower bound for v.
- // We use rounding-up division to compute it. Since we want to use a
single formula
- PrimExpr low_divided = analyzer->Simplify(floordiv(low + coef - 1,
coef), 3);
-
- // Compute another difference which may be more precise (or not).
- PrimExpr diff_2 = analyzer->Simplify(floordiv(upp, coef) - low_divided,
3);
- IntSet diff_set2 = EvalSet(diff_2, var_intsets);
- if (diff_set2.HasUpperBound()) {
- PrimExpr diff_over_2 = analyzer->Simplify(diff_set2.max(), 3);
- diff_over = diff_over.has_value() ? (analyzer->CanProve(diff_over_2 -
diff_over.value() < 0)
- ? diff_over_2
- : diff_over.value())
- : diff_over_2;
- }
-
- // If it is provable that the new one is strictly better than the
current best one,
- // then replace it. Note that we are biased towards earlier pairs which
should be simpler.
- if (diff_over.has_value() && (!best_diff_over.defined() ||
- analyzer->CanProve(diff_over.value() -
best_diff_over < 0))) {
- best_lower = low_divided;
- best_diff_over = diff_over.value();
- }
- }
- }
-
- if (!best_lower.defined()) {
- TVM_FFI_ICHECK(!best_diff_over.defined());
- return Range();
- }
- return Range::FromMinExtent(best_lower, analyzer->Simplify(best_diff_over +
1));
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef()
- .def("arith.IntGroupBounds",
- [](PrimExpr coef, ffi::Array<PrimExpr> lower, ffi::Array<PrimExpr>
equal,
- ffi::Array<PrimExpr> upper) { return IntGroupBounds(coef, lower,
equal, upper); })
- .def("arith.IntGroupBounds_from_range", IntGroupBounds::FromRange)
- .def_packed("arith.IntGroupBounds_FindBestRange", [](ffi::PackedArgs
args, ffi::Any* ret) {
- TVM_FFI_ICHECK(args.size() == 1 || args.size() == 2);
- auto bounds = args[0].cast<IntGroupBounds>();
- if (args.size() == 1) {
- *ret = bounds.FindBestRange();
- } else if (args.size() == 2) {
- *ret = bounds.FindBestRange(args[1].cast<ffi::Map<Var, Range>>());
- }
- });
-}
-
-// Pattern A (RM): auto-default repr from reflection.
-
-IntConstraints::IntConstraints(ffi::Array<PrimVar> variables, ffi::Map<Var,
Range> ranges,
- ffi::Array<PrimExpr> relations) {
- ffi::ObjectPtr<IntConstraintsNode> node =
ffi::make_object<IntConstraintsNode>();
- if (!variables.defined()) {
- variables = ffi::Array<PrimVar>();
- }
- if (!ranges.defined()) {
- ranges = ffi::Map<Var, Range>();
- }
- TVM_FFI_ICHECK(relations.defined());
- for (const PrimVar& var : variables) {
- TVM_FFI_CHECK(var.ty().MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt), TypeError)
- << "Variables in IntConstraints must be integers";
- }
- node->variables = std::move(variables);
- node->ranges = std::move(ranges);
- node->relations = std::move(relations);
- data_ = std::move(node);
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def(
- "arith.IntConstraints",
- [](ffi::Array<PrimVar> variables, ffi::Map<Var, Range> ranges,
- ffi::Array<PrimExpr> relations) { return IntConstraints(variables,
ranges, relations); });
-}
-
-// Pattern A (RM): auto-default repr from reflection.
-
-IntConstraintsTransform::IntConstraintsTransform(IntConstraints src,
IntConstraints dst,
- ffi::Map<Var, PrimExpr>
src_to_dst,
- ffi::Map<Var, PrimExpr>
dst_to_src) {
- ffi::ObjectPtr<IntConstraintsTransformNode> node =
- ffi::make_object<IntConstraintsTransformNode>();
- node->src = std::move(src);
- node->dst = std::move(dst);
- node->src_to_dst = std::move(src_to_dst);
- node->dst_to_src = std::move(dst_to_src);
- data_ = std::move(node);
-}
-
-IntConstraintsTransform IntConstraintsTransform::operator+(
- const IntConstraintsTransform& other) const {
- TVM_FFI_ICHECK(other->src.same_as(operator->()->dst));
- ffi::Map<Var, PrimExpr> dst_to_src;
- ffi::Map<Var, PrimExpr> src_to_dst;
-
- Analyzer ana_first;
- ana_first->Bind(operator->()->src->ranges);
- auto f_dst_to_src = [this](const Var& var) ->
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
- if (auto repl = operator->()->dst_to_src.Get(var)) return *std::move(repl);
- return ffi::Unchanged();
- };
- for (auto p : other->dst_to_src) {
- dst_to_src.Set(p.first,
ana_first->Simplify(ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(
- p.second, f_dst_to_src)
- .as_or_throw<PrimExpr>()));
- }
-
- Analyzer ana_second;
- ana_second->Bind(other->dst->ranges);
- auto f_src_to_dst = [&other](const Var& var) ->
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
- if (auto repl = other->src_to_dst.Get(var)) return *std::move(repl);
- return ffi::Unchanged();
- };
- for (auto p : operator->()->src_to_dst) {
- src_to_dst.Set(p.first,
ana_second->Simplify(ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(
- p.second, f_src_to_dst)
-
.as_or_throw<PrimExpr>()));
- }
- return IntConstraintsTransform(operator->()->src, other->dst, src_to_dst,
dst_to_src);
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def("arith.IntConstraintsTransform",
- [](IntConstraints src, IntConstraints dst,
- ffi::Map<Var, PrimExpr> src_to_dst, ffi::Map<Var,
PrimExpr> dst_to_src) {
- return IntConstraintsTransform(src, dst, src_to_dst,
dst_to_src);
- });
-}
-
-// Pattern A (RM): auto-default repr from reflection.
-
-} // namespace arith
-} // namespace tvm
diff --git a/src/arith/presburger_set.cc b/src/arith/presburger_set.cc
index 2f38200d61..be79bd3d10 100644
--- a/src/arith/presburger_set.cc
+++ b/src/arith/presburger_set.cc
@@ -24,7 +24,6 @@
#include "presburger_set.h"
#include <tvm/arith/int_set.h>
-#include <tvm/arith/int_solver.h>
#include <tvm/arith/pattern.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/extra/structural_visit.h>
diff --git a/src/arith/solve_linear_equation.cc
b/src/arith/solve_linear_equation.cc
deleted file mode 100644
index 40e5e0883e..0000000000
--- a/src/arith/solve_linear_equation.cc
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * 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.
- */
-
-/*!
- * \file tvm/arith/solve_linear_equation.cc
- * \brief Solve linear equations.
- */
-#include <tvm/arith/analyzer.h>
-#include <tvm/arith/int_solver.h>
-#include <tvm/arith/pattern.h>
-#include <tvm/ffi/dtype.h>
-#include <tvm/ffi/extra/structural_mutate.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
-#include <tvm/ir/prim/expr.h>
-#include <tvm/runtime/logging.h>
-#include <tvm/tirx/op.h>
-
-#include <unordered_set>
-#include <utility>
-
-#include "int_operator.h"
-
-namespace tvm {
-namespace arith {
-
-using namespace tvm::runtime;
-
-void SmithNormalFormDiag(std::vector<std::vector<int64_t>>* S,
std::vector<std::vector<int64_t>>* V,
- std::vector<PrimExpr>* x, std::vector<PrimExpr>* y) {
- if (S->empty() || V->empty()) return;
- size_t m = S->size();
- size_t n = (*S)[0].size(); // n is # of variables
- TVM_FFI_ICHECK_EQ(V->size(), n);
- TVM_FFI_ICHECK_EQ((*V)[0].size(), n);
-
- for (size_t index = 0; index < std::min(m, n); ++index) {
- // Here A is partially diagonalized, that is A[i, j] is zero for all i, j
- // such that (i < index) or (j < index), unless (i == j).
- // That is, now we are diagonalizing the submatrix with i >= index and j
>= index
-
- // Find a row with a nonzero element in the index-th column
- // (We also prefer rows where this element has minimal abs value)
- size_t best_i = index;
- for (size_t i = best_i; i < m; ++i) {
- int64_t s_old = (*S)[best_i][index];
- int64_t s_new = (*S)[i][index];
- if (s_new != 0) {
- if (s_old == 0 || std::abs(s_new) < std::abs(s_old)) {
- best_i = i;
- }
- }
- }
- // Move the row we found to the index-th position
- std::swap((*S)[index], (*S)[best_i]);
- std::swap((*y)[index], (*y)[best_i]);
-
- // If the index-th diagonal element is still zero, try to find a column
with nonzero index-th
- // element and move it to the index-th position
- if ((*S)[index][index] == 0) {
- for (size_t j = index + 1; j < n; ++j) {
- if ((*S)[index][j] != 0) {
- for (size_t i = index; i < m; ++i) {
- std::swap((*S)[i][index], (*S)[i][j]);
- }
- // swapping columns corresponds to swapping the corresponding x
- std::swap((*x)[index], (*x)[j]);
- for (size_t i = 0; i < n; ++i) {
- std::swap((*V)[i][index], (*V)[i][j]);
- }
- break;
- }
- }
- }
-
- // If the index-th diagonal element is still zero, then both the index-th
row and the index-th
- // column are completely zero, and we don't need to do anything; just go
to the next index
- if ((*S)[index][index] == 0) {
- continue;
- }
-
- // Now the index-th diagonal element is non-zero and we can zero all the
index-th column
- // below it by subtracting rows from each other
- for (auto i = index + 1; i < m; ++i) {
- if ((*S)[i][index] != 0) {
- int64_t g, a, b;
- // g = a*matrix[index][index] + b*matrix[i][index]
- if ((*S)[i][index] % (*S)[index][index] != 0) {
- g = ExtendedEuclidean((*S)[index][index], (*S)[i][index], &a, &b);
- } else {
- // Explicitly avoid changing the index-th row. This is important to
avoid infinite loop.
- g = (*S)[index][index];
- a = 1;
- b = 0;
- }
-
- // Let m = S[index][index], n = S[i][index], then the following is
true:
- //
- // [ a n/g ][ m/g n/g ] = [ 1 0 ]
- // [ b -m/g ][ b -a ] = [ 0 1 ]
- //
- // Note that the two matrices are integer (since g = gcd(m, n)).
- // We will essentially multiply our matrix on the left by a dilated
and transposed version
- // of the first of these two matrices. The second matrix is not needed
here, however we will
- // use it while zeroing the index-th row.
-
- int64_t m_g = (*S)[index][index] / g;
- int64_t n_g = (*S)[i][index] / g;
-
- // Note that j is the index of the column, not the row
- for (size_t j = index; j < (*S)[i].size(); ++j) {
- // Multiply index-th row by a and add the i-th row multiplied by b
- // This will make the index-th diagonal element equal to the gcd
- int64_t new_index_j = a * (*S)[index][j] + b * (*S)[i][j];
- // This transformation performs zeroing of matrix[i][index]
- int64_t new_i_j = n_g * (*S)[index][j] - m_g * (*S)[i][j];
- (*S)[index][j] = new_index_j;
- (*S)[i][j] = new_i_j;
- }
- // We have to do the same with rhs
- PrimExpr ea = tirx::MakeConst((*y)[index].ty(), a);
- PrimExpr eb = tirx::MakeConst((*y)[i].ty(), b);
- PrimExpr e_m_g = tirx::MakeConst((*y)[i].ty(), m_g);
- PrimExpr e_n_g = tirx::MakeConst((*y)[index].ty(), n_g);
- PrimExpr new_index_rhs = ea * (*y)[index] + eb * (*y)[i];
- PrimExpr new_i_rhs = e_n_g * (*y)[index] - e_m_g * (*y)[i];
- (*y)[index] = new_index_rhs;
- (*y)[i] = new_i_rhs;
- }
- }
-
- bool changed = false;
-
- // Now we have to zero the elements of the index-th row by manipulating
columns.
- // This is more difficult because column manipulation corresponds to
variable manipulation,
- // but the algorithm is essentially the same as before.
- for (size_t j = index + 1; j < n; ++j) {
- if ((*S)[index][j] != 0) {
- int64_t g, a, b;
- // g = a*matrix[index][index] + b*matrix[index][j]
- if ((*S)[index][j] % (*S)[index][index] != 0) {
- g = ExtendedEuclidean((*S)[index][index], (*S)[index][j], &a, &b);
- // During this phase we may disrupt the zeroness of the index-th
column, so we will
- // have to take some action if this might have happened.
- changed = true;
- } else {
- // Explicitly avoid changing the index-th column. This is important
to avoid infinite
- // loop. Note that here we don't have to set `changed` to true since
we don't change the
- // index-th column.
- g = (*S)[index][index];
- a = 1;
- b = 0;
- }
-
- // Let m = S[index][index], n = S[index][j], then the following is
true:
- //
- // [ a n/g ][ m/g n/g ] = [ 1 0 ]
- // [ b -m/g ][ b -a ] = [ 0 1 ]
- //
- // Now we are going to multiply our matrix on the right (to manipulate
columns instead of
- // rows), we will also transform the old_to_new matrix the same way,
and we will use the
- // second matrix to transform new_to_old.
-
- int64_t m_g = (*S)[index][index] / g;
- int64_t n_g = (*S)[index][j] / g;
-
- for (size_t i = index; i < m; ++i) {
- int64_t new_i_index = a * (*S)[i][index] + b * (*S)[i][j];
- int64_t new_i_j = n_g * (*S)[i][index] - m_g * (*S)[i][j];
- (*S)[i][index] = new_i_index;
- (*S)[i][j] = new_i_j;
- }
- // We do exactly the same transformations with V
- for (size_t i = 0; i < n; ++i) {
- int64_t new_i_index = a * (*V)[i][index] + b * (*V)[i][j];
- int64_t new_i_j = n_g * (*V)[i][index] - m_g * (*V)[i][j];
- (*V)[i][index] = new_i_index;
- (*V)[i][j] = new_i_j;
- }
- // And apply reverse transformations to new_to_old.
- PrimExpr ea = tirx::MakeConst((*x)[j].ty(), a);
- PrimExpr eb = tirx::MakeConst((*x)[index].ty(), b);
- PrimExpr e_m_g = tirx::MakeConst((*x)[index].ty(), m_g);
- PrimExpr e_n_g = tirx::MakeConst((*x)[j].ty(), n_g);
- PrimExpr new_index = e_m_g * (*x)[index] + e_n_g * (*x)[j];
- PrimExpr new_j = eb * (*x)[index] - ea * (*x)[j];
- (*x)[index] = new_index;
- (*x)[j] = new_j;
- }
- }
-
- if (changed) {
- // We might have changed the first column, so we have to zero it once
more
- // (or at least check if it's zero), so just perform this iteration once
more.
- index -= 1;
- }
- }
-}
-
-ffi::Map<Var, Range> InferRange(const ffi::Map<Var, PrimExpr>& vars_to_infer,
- const ffi::Array<PrimVar>& ori_vars,
- const ffi::Map<Var, Range>& ori_ranges) {
- // The resulting ranges
- ffi::Map<Var, Range> new_ranges;
-
- std::unordered_set<const VarNode*> ori_vset;
- for (const PrimVar& v : ori_vars) {
- ori_vset.insert(v.get());
- }
-
- std::unordered_map<const VarNode*, IntSet> var_intsets;
- for (const auto& p : ori_ranges) {
- if (!ori_vset.count(p.first.get())) {
- // First of all, fill the new ranges with outer variable ranges
- new_ranges.Set(p.first, p.second);
- }
- // Convert original ranges to IntSets
- var_intsets[p.first.get()] = IntSet::FromRange(p.second);
- }
-
- // Infer ranges for the new variables and add them to the resulting ranges
- for (const auto& p : vars_to_infer) {
- const auto& var = p.first;
- const auto& expr = p.second;
- Range range = EvalSet(expr, var_intsets).CoverRange(Range());
- if (range.defined()) {
- new_ranges.Set(var, range);
- }
- }
- return new_ranges;
-}
-
-// pretty print matrix equation
-void DebugPrint(const std::vector<std::vector<int64_t>>& S,
- const std::vector<std::vector<int64_t>>& V, const
std::vector<PrimExpr>& V_inv_x,
- const std::vector<PrimExpr>& rhs) {
- std::cout << "S:\n";
- for (size_t i = 0; i < S.size(); ++i) {
- for (auto e : S[i]) {
- std::cout << e << "\t";
- }
- std::cout << "\t->\t" << rhs[i];
- std::cout << "\n";
- }
- std::cout << "V:\n";
- for (const auto& r : V) {
- for (auto e : r) {
- std::cout << e << "\t";
- }
- std::cout << "\n";
- }
- std::cout << "V_inv x:\n" << ffi::Array<PrimExpr>(V_inv_x);
- std::cout << "\n" << std::endl;
-}
-
-IntConstraintsTransform SolveLinearEquations(const IntConstraints&
system_to_solve) {
- // m: # of equations
- // n: # of variables
- // we first construct A_{mxn} x_{nx1} = y_{mx1}
- // then get Smith normal form of matrix A,
- // S_{mxn} = U_{mxm} A_{mxn} V_{nxn}
- // => U^{-1} S V^{-1} x = y
- // S V^{-1} x = U y
- std::vector<PrimExpr> Uy; // mx1
- std::vector<std::vector<int64_t>> S; // mxn
- std::vector<std::vector<int64_t>> V; // nxn
- std::vector<PrimExpr> V_inv_x; // V^{-1} x, nx1
- // Conditions we don't know what to do with
- std::vector<PrimExpr> rest;
-
- Analyzer analyzer_problem;
- analyzer_problem->Bind(system_to_solve->ranges);
-
- size_t num_vars = system_to_solve->variables.size();
-
- // initialize V_{nxn} with identity matrix,
- // initialize V^{-1} x as x
- for (size_t i = 0; i < num_vars; ++i) {
- V.emplace_back(num_vars);
- V.back()[i] = 1;
- V_inv_x.push_back(system_to_solve->variables[i].as_or_throw<PrimExpr>());
- }
-
- // Transform formulas into rows of the matrix
- // S_{mxn} V^{-1}_{nxn} x_{nx1} = U y, in which n is # of variables
- // here we initialize S_{mxn} to be A, U to be identity matrix.
- for (const PrimExpr& equation : system_to_solve->relations) {
- if (const prim::EQNode* eq = equation.as<prim::EQNode>()) {
- // a-b = sum_{i=0}^{n-1} variables[i] * coeff[i] + coeff[n]
- ffi::Array<PrimExpr> coeffs = arith::DetectLinearEquation(
- analyzer_problem->Simplify(eq->a - eq->b),
system_to_solve->variables);
- if (!coeffs.empty()) {
- std::vector<int64_t> row;
- for (size_t j = 0; j < coeffs.size() - 1; ++j) {
- PrimExpr c = coeffs[j];
- if (const IntImmNode* ic = c.as<IntImmNode>()) {
- row.push_back(ic->value);
- } else {
- // elements in matrix S V must be integers
- // ignore equations that we cannot deal with.
- LOG(WARNING) << "Cannot deal with non-integer coefficients, ignore
equation "
- << equation;
- row.clear();
- break;
- }
- }
-
- if (!row.empty()) {
- // S V^{-1} (a-b) = Uy
- // V is identity for now
- S.push_back(row);
- Uy.push_back(-coeffs[coeffs.size() - 1]);
- continue;
- }
- }
- }
-
- // otherwise
- rest.push_back(equation);
- }
-
- // After diagonalizing, we have
- // S_{mxn} is the Smith normal form (diagonal matrix)
- // V_{nxn} is invertible
- // V_inv_x is V^{-1} \times x
- // Uy is U \times y
- SmithNormalFormDiag(&S, &V, &V_inv_x, &Uy);
-
- ffi::Array<PrimVar> new_vars;
- ffi::Array<PrimExpr> new_relations;
- ffi::Map<Var, PrimExpr> new_to_old_map;
- ffi::Map<Var, PrimExpr> old_to_new_map;
-
- // Simplify right hand sides
- for (PrimExpr r : Uy) {
- r = analyzer_problem->Simplify(r);
- }
-
- // Create the relations of the existence of a solution
- for (size_t j = 0; j < S.size(); ++j) {
- PrimExpr new_relation;
- if (j >= num_vars || S[j][j] == 0) {
- // The row of matrix is zero. A solution exists only if the Ub[j] is
also zero
- new_relation = (Uy[j] == 0);
- } else {
- // The diagonal element is non-zero. A solution exists only if the
diagonal element
- // is a divisor of the Ub[j]
- new_relation = (floormod(Uy[j], std::abs(S[j][j])) == 0);
- }
- new_relation = analyzer_problem->Simplify(new_relation);
- if (tirx::is_const_int(new_relation, 0)) {
- // unable to solve the system.
- return IntConstraintsTransform(system_to_solve,
- IntConstraints(
- /*variables=*/{},
- /*ranges=*/{},
- /*relations=*/{IntImm::Bool(false)}),
- {}, {});
- } else if (!tirx::is_const_int(new_relation, 1)) {
- new_relations.push_back(new_relation);
- }
- }
-
- ffi::Array<PrimExpr> solution_for_V_inv_x;
- // Now create new variables or directly solve the equations
- // suppose the rank of A is r, aka r = # of non-zeros in S
- // the solution of S_{mxn} V^{-1}_{nxn} x_{nx1} = U b
- // is
- // x = (pseudo-inverse of A) b + K_{(n)x(n-r)} z_{n-r}
- // = V_{nxn} S^{-1}_{nxm} (Ub)_{mxn} + K_{(n)x(n-r)} z_{n-r}
- // in which K is the right n-r columns of V, z is variable vector
- // thus,
- // V^{-1} x = S^{-1}_{nxm} (Ub)_{mxn} +
- // [[0, ... 0]_{n-r}, ... [0, ..., 0], diag(1, ...,
1)_{(n-r)x(n-r)}] z_{n-r}
- for (size_t j = 0; j < num_vars; ++j) {
- if (j >= S.size() || S[j][j] == 0) {
- // The j-th variable can take any integer value, create a tvm variable
for it
- PrimExpr to_old = analyzer_problem->Simplify(V_inv_x[j]);
- std::string name_hint = "n" + std::to_string(new_vars.size());
- if (auto old_var = to_old.as<PrimVar>()) {
- name_hint += "_" + (*old_var)->name;
- }
- PrimVar v(name_hint, V_inv_x[j].ty());
- solution_for_V_inv_x.push_back(v);
- new_vars.push_back(v);
- new_to_old_map.Set(v, to_old);
- } else {
- // The j-th variable is just a single value, don't create a tvm variable
- // S^{-1}_{nxm} Uy_{mxn}
- if (S[j][j] >= 0) {
- PrimExpr a = IntImm(Uy[j].ty(), S[j][j]);
-
solution_for_V_inv_x.push_back(analyzer_problem->Simplify(floordiv(Uy[j], a)));
- } else {
- // This is required because some simplifiers
- // have problems with dividing by negative numbers
- PrimExpr a = IntImm(Uy[j].ty(), -S[j][j]);
-
solution_for_V_inv_x.push_back(analyzer_problem->Simplify(floordiv(-Uy[j], a)));
- }
- }
- }
-
- // V V^{-1} x = x
- for (size_t i = 0; i < num_vars; ++i) {
- PrimExpr e =
IntImm(system_to_solve->variables[i]->ty.as_or_throw<PrimType>(), 0);
- for (size_t j = 0; j < num_vars; ++j) {
- e = e + tirx::MakeConst(e.ty(), V[i][j]) * solution_for_V_inv_x[j];
- }
- e = analyzer_problem->Simplify(e);
- old_to_new_map.Set(system_to_solve->variables[i], e);
- }
-
- // The resulting ranges
- ffi::Map<Var, Range> new_ranges =
- InferRange(new_to_old_map, system_to_solve->variables,
system_to_solve->ranges);
- Analyzer analyzer_solution;
- analyzer_solution->Bind(new_ranges);
-
- // We have to transform ranges of the old variables into relations over new
variables because
- // new ranges are not enough usually.
- for (const auto& old_var : system_to_solve->variables) {
- if (system_to_solve->ranges.find(old_var) !=
system_to_solve->ranges.end()) {
- const Range& old_range = system_to_solve->ranges.at(old_var);
- PrimExpr express_by_new_vars = old_to_new_map.at(old_var);
- PrimExpr lower_cond = analyzer_solution->Simplify(old_range->min <=
express_by_new_vars);
- PrimExpr upper_cond =
- analyzer_solution->Simplify(express_by_new_vars < old_range->min +
old_range->extent);
- if (!tirx::is_const_int(lower_cond, 1)) {
- new_relations.push_back(lower_cond);
- }
- if (!tirx::is_const_int(upper_cond, 1)) {
- new_relations.push_back(upper_cond);
- }
- }
- }
-
- // Add the rest conditions
- auto f_subst = [&old_to_new_map](const Var& var) ->
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
- if (auto repl = old_to_new_map.Get(var)) return *std::move(repl);
- return ffi::Unchanged();
- };
- for (const PrimExpr& cond : rest) {
- new_relations.push_back(
- ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(cond,
f_subst).as_or_throw<PrimExpr>());
- }
-
- IntConstraints solution(new_vars, new_ranges, new_relations);
- IntConstraintsTransform transform(system_to_solve, solution, old_to_new_map,
new_to_old_map);
-
- return transform;
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def_packed(
- "arith.SolveLinearEquations", [](ffi::PackedArgs args, ffi::Any* ret) {
- if (args.size() == 1) {
- *ret = SolveLinearEquations(args[0].cast<IntConstraints>());
- } else if (args.size() == 3) {
- auto opt_vars = args[0].cast<ffi::Optional<ffi::Array<PrimVar>>>();
- auto opt_map = args[1].cast<ffi::Optional<ffi::Map<Var, Range>>>();
- auto opt_relations =
args[2].cast<ffi::Optional<ffi::Array<PrimExpr>>>();
- IntConstraints problem(opt_vars.value_or({}), opt_map.value_or({}),
- opt_relations.value_or({}));
- *ret = SolveLinearEquations(problem);
- } else {
- TVM_FFI_THROW(InternalError)
- << "arith.SolveLinearEquations expects 1 or 3 arguments, gets "
<< args.size();
- }
- });
-}
-
-} // namespace arith
-} // namespace tvm
diff --git a/src/arith/solve_linear_inequality.cc
b/src/s_tir/analysis/conditional_bounds.cc
similarity index 53%
rename from src/arith/solve_linear_inequality.cc
rename to src/s_tir/analysis/conditional_bounds.cc
index 4bc103708a..2576fb62db 100644
--- a/src/arith/solve_linear_inequality.cc
+++ b/src/s_tir/analysis/conditional_bounds.cc
@@ -18,66 +18,219 @@
*/
/*!
- * \file tvm/arith/solve_linear_inequality.cc
- * \brief Solve linear inequalities.
+ * \file s_tir/analysis/conditional_bounds.cc
+ * \brief Scoped conditional bounds and private inequality support for S-TIR.
*/
+#include "conditional_bounds.h"
+
#include <tvm/arith/analyzer.h>
-#include <tvm/arith/int_solver.h>
#include <tvm/arith/pattern.h>
-#include <tvm/ffi/dtype.h>
#include <tvm/ffi/extra/structural_mutate.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/expr_functor.h>
-#include <tvm/ir/prim/expr.h>
+#include <tvm/ir/prim/builtin.h>
#include <tvm/tirx/analysis.h>
#include <tvm/tirx/op.h>
+#include <algorithm>
+#include <functional>
#include <utility>
-#include "int_operator.h"
+#include "../../arith/int_operator.h"
namespace tvm {
-namespace arith {
+namespace s_tir {
-using namespace tvm::runtime;
using namespace tvm::tirx;
-
-struct ExprLess {
- bool operator()(const PrimExpr& l, const PrimExpr& r) const {
- return CalculateExprComplexity(l) < CalculateExprComplexity(r);
+using arith::Analyzer;
+using arith::AnalyzerObj;
+using arith::EvalSet;
+using arith::IntSet;
+
+namespace {
+using arith::ExtendedEuclidean;
+using arith::LeastCommonMultiple;
+
+// The solver's intermediate representations remain local to this analysis.
+struct IntGroupBounds {
+ PrimExpr coef;
+ ffi::Array<PrimExpr> lower;
+ ffi::Array<PrimExpr> equal;
+ ffi::Array<PrimExpr> upper;
+
+ IntGroupBounds(PrimExpr coef, ffi::Array<PrimExpr> lower,
ffi::Array<PrimExpr> equal,
+ ffi::Array<PrimExpr> upper)
+ : coef(std::move(coef)),
+ lower(std::move(lower)),
+ equal(std::move(equal)),
+ upper(std::move(upper)) {
+ TVM_FFI_ICHECK(this->coef.ty().MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt))
+ << "Coefficient in IntGroupBounds must be integers";
}
+
+ Range FindBestRange(const ffi::Map<Var, Range>& vranges_addl) const;
+ IntGroupBounds operator+(const Range& range);
};
-void DebugPrint(const std::vector<PrimExpr>& current_ineq_set,
- const std::vector<PrimExpr>& next_ineq_set, const
std::vector<PrimExpr>& rest,
- const std::vector<std::pair<int64_t, PrimExpr>>& coef_pos,
- const std::vector<std::pair<int64_t, PrimExpr>>& coef_neg) {
- std::cout << "Current ineq set:\n[";
- for (auto& ineq : current_ineq_set) {
- std::cout << ineq << ", ";
+struct IntConstraints {
+ ffi::Array<PrimVar> variables;
+ ffi::Map<Var, Range> ranges;
+ ffi::Array<PrimExpr> relations;
+
+ IntConstraints(ffi::Array<PrimVar> variables, ffi::Map<Var, Range> ranges,
+ ffi::Array<PrimExpr> relations);
+};
+
+using GroupedBounds =
+ std::unordered_map<Var, IntGroupBounds, ffi::ObjectPtrHash,
ffi::ObjectPtrEqual>;
+using PartialSolvedInequalities = std::pair<GroupedBounds,
ffi::Array<PrimExpr>>;
+
+// Rewrite, canonicalize, then rewrite to factor multipliers out.
+constexpr int kSimplifyRewriteCanonicalRewrite = 3;
+
+IntConstraints::IntConstraints(ffi::Array<PrimVar> variables, ffi::Map<Var,
Range> ranges,
+ ffi::Array<PrimExpr> relations) {
+ if (!variables.defined()) {
+ variables = ffi::Array<PrimVar>();
+ }
+ if (!ranges.defined()) {
+ ranges = ffi::Map<Var, Range>();
+ }
+ TVM_FFI_ICHECK(relations.defined());
+ for (const PrimVar& var : variables) {
+ TVM_FFI_CHECK(var.ty().MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt), TypeError)
+ << "Variables in IntConstraints must be integers";
+ }
+ this->variables = std::move(variables);
+ this->ranges = std::move(ranges);
+ this->relations = std::move(relations);
+}
+
+ffi::Array<PrimExpr> AsConditions(const ffi::Array<PrimVar>& variables, const
GroupedBounds& bounds,
+ const ffi::Array<PrimExpr>& relations) {
+ ffi::Array<PrimExpr> res;
+ // use variables to keep the order of iteration
+ // so as to get rid of any non-determinism.
+ TVM_FFI_ICHECK_EQ(variables.size(), bounds.size());
+ for (const auto v : variables) {
+ TVM_FFI_ICHECK(bounds.count(v));
+ const auto& bnds = bounds.at(v);
+ PrimExpr lhs = bnds.coef * v.as_or_throw<PrimExpr>();
+ for (const PrimExpr& rhs : bnds.equal) {
+ res.push_back(lhs == rhs);
+ }
+ for (const PrimExpr& rhs : bnds.lower) {
+ res.push_back(lhs >= rhs);
+ }
+ for (const PrimExpr& rhs : bnds.upper) {
+ res.push_back(lhs <= rhs);
+ }
+ }
+ for (const PrimExpr& e : relations) {
+ res.push_back(e);
+ }
+ return res;
+}
+
+IntGroupBounds IntGroupBounds::operator+(const Range& r) {
+ Analyzer analyzer;
+ ffi::Array<PrimExpr> equal;
+ ffi::Array<PrimExpr> lower;
+ ffi::Array<PrimExpr> upper;
+ const PrimExpr& coef = this->coef;
+ if (tirx::is_one(r->extent)) {
+ equal.push_back(analyzer->Simplify(r->min * coef));
+ } else {
+ lower.push_back(analyzer->Simplify(r->min * coef));
+ upper.push_back(analyzer->Simplify((r->min + r->extent - 1) * coef));
+ }
+ for (const auto& eq : this->equal) equal.push_back(eq);
+ for (const auto& lb : this->lower) lower.push_back(lb);
+ for (const auto& ub : this->upper) upper.push_back(ub);
+ return IntGroupBounds(coef, lower, equal, upper);
+}
+
+Range IntGroupBounds::FindBestRange(const ffi::Map<Var, Range>& vranges_addl)
const {
+ Analyzer analyzer;
+ analyzer->Bind(vranges_addl);
+
+ std::unordered_map<const VarNode*, IntSet> var_intsets;
+ for (auto kv : vranges_addl) {
+ var_intsets[kv.first.get()] = IntSet::FromRange(kv.second);
+ }
+
+ const ffi::Array<PrimExpr>& equal = this->equal;
+ const PrimExpr& coef = this->coef;
+
+ std::vector<PrimExpr> lowers(equal.begin(), equal.end());
+ std::vector<PrimExpr> uppers(equal.begin(), equal.end());
+ for (const auto& expr : this->lower) {
+ lowers.push_back(expr);
+ }
+ for (const auto& expr : this->upper) {
+ uppers.push_back(expr);
}
- std::cout << "]\n";
- std::cout << "Next ineq set:\n[";
- for (auto& ineq : next_ineq_set) {
- std::cout << ineq << ", ";
+ if (lowers.size() == 1 && uppers.size() == 1 && tirx::is_one(coef)) {
+ return Range(analyzer->Simplify(lowers[0]), analyzer->Simplify(uppers[0] +
1));
}
- std::cout << "]\n";
- std::cout << "coef_pos:\n[";
- for (auto& coef : coef_pos) {
- std::cout << "(" << coef.first << ", " << coef.second << "), ";
+ // Here we will try all pairs of lower and upper bounds and find the best
pair, that is, the
+ // pair with the minimal difference between the upper and the lower.
+ // Note that the bounds are for v, not for v*coef
+
+ // The lower bound of the best pair so far
+ PrimExpr best_lower;
+ // The difference between the upper and the lower of the best pair, maybe
overapproximation
+ PrimExpr best_diff_over;
+
+ for (const PrimExpr& low : lowers) {
+ for (const PrimExpr& upp : uppers) {
+ // Since diff may depend on some other variables, we compute its
overapproximation
+ ffi::Optional<PrimExpr> diff_over;
+ PrimExpr diff_1 = analyzer->Simplify(floordiv(upp - low, coef), 3);
+ IntSet diff_set1 = EvalSet(diff_1, var_intsets);
+ if (diff_set1.HasUpperBound()) {
+ diff_over = analyzer->Simplify(diff_set1.max(), 3);
+ }
+
+ // low is the lower bound for v*coef, but we need the lower bound for v.
+ // We use rounding-up division to compute it. Since we want to use a
single formula
+ PrimExpr low_divided = analyzer->Simplify(floordiv(low + coef - 1,
coef), 3);
+
+ // Compute another difference which may be more precise (or not).
+ PrimExpr diff_2 = analyzer->Simplify(floordiv(upp, coef) - low_divided,
3);
+ IntSet diff_set2 = EvalSet(diff_2, var_intsets);
+ if (diff_set2.HasUpperBound()) {
+ PrimExpr diff_over_2 = analyzer->Simplify(diff_set2.max(), 3);
+ diff_over = diff_over.has_value() ? (analyzer->CanProve(diff_over_2 -
diff_over.value() < 0)
+ ? diff_over_2
+ : diff_over.value())
+ : diff_over_2;
+ }
+
+ // If it is provable that the new one is strictly better than the
current best one,
+ // then replace it. Note that we are biased towards earlier pairs which
should be simpler.
+ if (diff_over.has_value() && (!best_diff_over.defined() ||
+ analyzer->CanProve(diff_over.value() -
best_diff_over < 0))) {
+ best_lower = low_divided;
+ best_diff_over = diff_over.value();
+ }
+ }
}
- std::cout << "]\n";
- std::cout << "coef_neg:\n[";
- for (auto& coef : coef_neg) {
- std::cout << "(" << coef.first << ", " << coef.second << "), ";
+ if (!best_lower.defined()) {
+ TVM_FFI_ICHECK(!best_diff_over.defined());
+ return Range();
}
- std::cout << "]\n";
+ return Range::FromMinExtent(best_lower, analyzer->Simplify(best_diff_over +
1));
}
+struct ExprLess {
+ bool operator()(const PrimExpr& l, const PrimExpr& r) const {
+ return CalculateExprComplexity(l) < CalculateExprComplexity(r);
+ }
+};
+
/*!
* \brief normalize to the form `expr <= 0`
*/
@@ -206,7 +359,7 @@ void MoveEquality(std::vector<PrimExpr>* upper_bounds,
std::vector<PrimExpr>* lo
PartialSolvedInequalities SolveLinearInequalities(const IntConstraints&
system_to_solve) {
arith::Analyzer analyzer;
- analyzer->Bind(system_to_solve->ranges);
+ analyzer->Bind(system_to_solve.ranges);
// The algorithm consists in doing the following things for each variable v
// - Take formulas from `current_ineq_set_to_solve` and
@@ -231,14 +384,14 @@ PartialSolvedInequalities SolveLinearInequalities(const
IntConstraints& system_t
// Simplify each inequality into the form `expr <= 0` and add to current
formulas
auto normalizer = ffi::make_object<NormalizeComparisons>();
- for (const PrimExpr& ineq : system_to_solve->relations) {
+ for (const PrimExpr& ineq : system_to_solve.relations) {
PrimExpr simplified = analyzer->Simplify(ineq,
kSimplifyRewriteCanonicalRewrite);
PrimExpr normalized =
normalizer->Mutate(simplified).ValueOrUnchanged(simplified);
AddInequality(¤t_ineq_set_to_solve, normalized, analyzer.get());
}
- ffi::Map<Var, IntGroupBounds> res_bounds;
- for (const PrimVar& v : system_to_solve->variables) {
+ GroupedBounds res_bounds;
+ for (const PrimVar& v : system_to_solve.variables) {
TVM_FFI_ICHECK(!res_bounds.count(v))
<< "Variable " << v
<< " appears more than one time in the `variables` which might be a
bug";
@@ -248,8 +401,8 @@ PartialSolvedInequalities SolveLinearInequalities(const
IntConstraints& system_t
coef_neg.clear();
// Add bounds from vranges
- if (system_to_solve->ranges.count(v)) {
- const Range& range = system_to_solve->ranges[v];
+ if (system_to_solve.ranges.count(v)) {
+ const Range& range = system_to_solve.ranges[v];
PrimExpr range_lbound = analyzer->Simplify(range->min,
kSimplifyRewriteCanonicalRewrite);
PrimExpr range_ubound =
analyzer->Simplify(range->min + range->extent - 1,
kSimplifyRewriteCanonicalRewrite);
@@ -352,7 +505,7 @@ PartialSolvedInequalities SolveLinearInequalities(const
IntConstraints& system_t
ffi::Array<PrimExpr>(lower_bounds.begin(),
lower_bounds.end()),
ffi::Array<PrimExpr>(equal_list.begin(),
equal_list.end()),
ffi::Array<PrimExpr>(upper_bounds.begin(),
upper_bounds.end()));
- res_bounds.Set(v, bnds);
+ res_bounds.emplace(v, bnds);
std::swap(current_ineq_set_to_solve, next_ineq_set_to_solve);
}
@@ -384,40 +537,39 @@ PartialSolvedInequalities SolveLinearInequalities(const
IntConstraints& system_t
#endif
IntConstraints SolveInequalitiesToRange(const IntConstraints& inequalities) {
// Resulting ranges will contain ranges for the new variables and for the
variables that are
- // not in the inequalities->variables but are in inequalities->ranges
- // It will be useful when solving Jacobian axes jac_xxx)
+ // not in the inequalities.variables but are in inequalities.ranges
ffi::Map<Var, Range> res_ranges;
// we get a set of equality, lower, upper bound of each variable.
auto solved_system = SolveLinearInequalities(inequalities);
- ffi::Map<Var, IntGroupBounds> solved_bounds = solved_system.first;
+ GroupedBounds solved_bounds = solved_system.first;
ffi::Array<PrimExpr> solved_other_relations = solved_system.second;
ffi::Array<PrimExpr> res_relations;
// this keeps being updated during determining the range of each variable.
ffi::Map<Var, Range> vranges;
- for (std::pair<Var, Range> vr : inequalities->ranges) {
+ for (std::pair<Var, Range> vr : inequalities.ranges) {
vranges.Set(vr.first, vr.second);
}
// We process variables in the reverse direction to start with the most
independent one.
// This order is needed to compute new ranges.
- for (auto it = inequalities->variables.rbegin(); it !=
inequalities->variables.rend(); ++it) {
+ for (auto it = inequalities.variables.rbegin(); it !=
inequalities.variables.rend(); ++it) {
arith::Analyzer analyzer;
analyzer->Bind(vranges);
const PrimVar& var = *it;
TVM_FFI_ICHECK(solved_bounds.count(var));
- auto bnd = solved_bounds[var];
- if (is_one(bnd->coef) && !bnd->equal.empty()) {
+ auto bnd = solved_bounds.at(var);
+ if (is_one(bnd.coef) && !bnd.equal.empty()) {
// There is an equation of the form `v == expr`, so this variable can be
completely removed.
// Note that we use the 0-th expression because they are ordered by
complexity,
// so it must be the simplest one.
- // The MSVC compiler optimization must be disabled for the expression
`bnd->equal[0]` which
+ // The MSVC compiler optimization must be disabled for the expression
`bnd.equal[0]` which
// triggers an internal compiler error.
- Range best_range(bnd->equal[0],
- analyzer->Simplify(bnd->equal[0] + 1,
kSimplifyRewriteCanonicalRewrite));
+ Range best_range(bnd.equal[0],
+ analyzer->Simplify(bnd.equal[0] + 1,
kSimplifyRewriteCanonicalRewrite));
res_ranges.Set(var, best_range);
vranges.Set(var, best_range);
} else {
@@ -443,14 +595,14 @@ IntConstraints SolveInequalitiesToRange(const
IntConstraints& inequalities) {
arith::Analyzer analyzer;
analyzer->Bind(vranges);
for (const PrimExpr& old_cond :
- AsConditions(inequalities->variables, solved_bounds,
solved_other_relations)) {
+ AsConditions(inequalities.variables, solved_bounds,
solved_other_relations)) {
if (!analyzer->CanProve(old_cond)) {
// those not represented in vranges (res_ranges)
res_relations.push_back(old_cond);
}
}
- IntConstraints system(inequalities->variables, res_ranges, res_relations);
+ IntConstraints system(inequalities.variables, res_ranges, res_relations);
return system;
}
@@ -458,171 +610,151 @@ IntConstraints SolveInequalitiesToRange(const
IntConstraints& inequalities) {
#pragma optimize("g", on)
#endif
-IntConstraintsTransform SolveInequalitiesDeskewRange(const IntConstraints&
inequalities) {
- // Resulting ranges will contain ranges for the new variables and for the
variables that are
- // not in the inequalities->variables but are in inequalities->ranges
(jac_xxx)
- ffi::Map<Var, Range> res_ranges;
- // we get a set of equality, lower, upper bound of each variable.
- auto solved_system = SolveLinearInequalities(inequalities);
- ffi::Map<Var, IntGroupBounds> solved_bounds = solved_system.first;
- ffi::Array<PrimExpr> solved_other_relations = solved_system.second;
+} // namespace
+ffi::Optional<ffi::Map<Var, Range>>
ConditionalBoundsContext::TrySolveCondition() {
+ // extract equations and related vars from condition expression.
+ // currently only extract simple integral equations which could be solvable.
arith::Analyzer analyzer;
-
- ffi::Map<Var, PrimExpr> res_src_to_dst;
- ffi::Map<Var, PrimExpr> res_dst_to_src;
- ffi::Array<PrimVar> res_variables;
- ffi::Array<PrimExpr> res_relations;
-
- // this keeps being updated during determining the range of each variable.
- ffi::Map<Var, Range> vranges;
- for (std::pair<Var, Range> vr : inequalities->ranges) {
- vranges.Set(vr.first, vr.second);
+ PrimExpr condition = analyzer->Simplify(condition_);
+ if (is_const_int(condition)) {
+ return std::nullopt;
}
- analyzer->Bind(vranges);
-
- auto subst = [&res_src_to_dst](const Var& var) ->
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
- if (auto repl = res_src_to_dst.Get(var)) return *std::move(repl);
- return ffi::Unchanged();
- };
- auto f_dst_to_src =
- [&res_dst_to_src](const Var& var) ->
ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
- if (auto repl = res_dst_to_src.Get(var)) return *std::move(repl);
- return ffi::Unchanged();
+ ffi::Array<PrimExpr> equations;
+ ffi::Array<PrimVar> vars;
+ std::function<void(const PrimExpr&)> fvisit = [&equations, &vars,
&fvisit](const PrimExpr& e) {
+ if (e->IsInstance<prim::GENode>() || e->IsInstance<prim::GTNode>() ||
+ e->IsInstance<prim::LENode>() || e->IsInstance<prim::LTNode>() ||
+ e->IsInstance<prim::EQNode>() || e->IsInstance<prim::NENode>()) {
+ bool is_simple = true;
+ std::vector<PrimVar> cand_vars;
+ auto walk_fn = [&cand_vars, &is_simple,
+ &e](const PrimExpr& obj) ->
ffi::Expected<ffi::WalkResult> {
+ if (obj.same_as(e)) {
+ return ffi::WalkResult::Advance();
+ } else if (const VarNode* var = obj.as<VarNode>()) {
+ PrimType var_ty = var->ty.as_or_throw<PrimType>();
+ if (var_ty.MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt)) {
+ cand_vars.push_back(ffi::GetRef<Var>(var).as_or_throw<PrimVar>());
+ }
+ } else {
+ is_simple &= obj->IsInstance<prim::AddNode>() ||
obj->IsInstance<prim::SubNode>() ||
+ obj->IsInstance<prim::MulNode>() ||
obj->IsInstance<prim::FloorDivNode>() ||
+ obj->IsInstance<prim::FloorModNode>() ||
obj->IsInstance<IntImmNode>();
+ }
+ return ffi::WalkResult::Advance();
+ };
+ ffi::StructuralWalk<ffi::WalkOrder::kPostOrder>(e, walk_fn);
+ if (is_simple && !cand_vars.empty()) {
+ for (const PrimVar& new_var : cand_vars) {
+ if (!std::any_of(vars.begin(), vars.end(),
+ [&new_var](const PrimVar& v) { return
v.same_as(new_var); })) {
+ vars.push_back(new_var);
+ }
+ }
+ equations.push_back(e.as_or_throw<PrimExpr>());
+ }
+ } else if (e->IsInstance<prim::AndNode>()) {
+ prim::And op = e.as_or_throw<prim::And>();
+ fvisit(op->a);
+ fvisit(op->b);
+ } else if (e->IsInstance<CallNode>()) {
+ Call op = e.as_or_throw<Call>();
+ if (op->op.same_as(prim::builtin::likely())) {
+ fvisit(op->args[0].as_or_throw<PrimExpr>());
+ }
+ }
};
-
- // We process variables in the reverse direction to start with the most
independent one.
- // This order is needed to compute new ranges.
- for (auto it = inequalities->variables.rbegin(); it !=
inequalities->variables.rend(); ++it) {
- const PrimVar& var = *it;
- auto bnd = solved_bounds[var];
- // Note that we replace old vars with new ones
- bnd = ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(bnd,
subst).as_or_throw<IntGroupBounds>();
-
- if (is_one(bnd->coef) && !bnd->equal.empty()) {
- // There is an equation of the form `v == expr`,
- // so this variable can be completely removed.
- // Note that we use the 0-th expression because they are ordered by
complexity,
- // so it must be the simplest one.
- res_src_to_dst.Set(var, bnd->equal[0]);
+ fvisit(condition);
+ if (equations.empty() || vars.empty()) {
+ return std::nullopt;
+ }
+ // build dom ranges for related vars
+ ffi::Map<Var, Range> ranges;
+ for (const Var& v : vars) {
+ arith::IntSet dom;
+ auto relax_it = relax_map_->find(v.get());
+ if (relax_it != relax_map_->end()) {
+ dom = relax_it->second;
} else {
- if (vranges.count(var) > 0) {
- bnd = bnd + vranges[var];
+ auto hint_it = hint_map_->find(v.get());
+ if (hint_it != hint_map_->end()) {
+ dom = hint_it->second;
}
+ }
+ if (dom.defined()) {
+ ranges.Set(v, Range::FromMinExtent(dom.min(),
analyzer->Simplify(dom.max() - dom.min() + 1)));
+ }
+ }
+ // solve constraints
+ IntConstraints constraint(vars, ranges, equations);
+ IntConstraints result = SolveInequalitiesToRange(constraint);
+ if (!result.relations.empty()) {
+ return std::nullopt;
+ }
+ return result.ranges;
+}
- auto best_range = bnd.FindBestRange(vranges);
-
- PrimVar new_var = var.CopyWithSuffix(".shifted");
- if (!best_range.defined()) {
- res_src_to_dst.Set(var, var.as_or_throw<PrimExpr>());
- res_dst_to_src.Set(var, var.as_or_throw<PrimExpr>());
- res_variables.push_back(var);
- } else if (is_const_int(best_range->extent, 1)) {
- // Don't create an itervar, just replace it everywhere with its min
- res_src_to_dst.Set(var, best_range->min);
- } else if (analyzer->CanProveGreaterEqual(-best_range->extent, 0)) {
- // range.extent <= 0 implies the input inequality system is unsolvable
- return IntConstraintsTransform(inequalities,
- IntConstraints(
- /*variables=*/{},
- /*ranges=*/{},
-
/*relations=*/{IntImm::Bool(false)}),
- {}, {});
+ConditionalBoundsContext::ConditionalBoundsContext(
+ const PrimExpr& condition, std::unordered_map<const VarNode*,
arith::IntSet>* relax_map,
+ std::unordered_map<const VarNode*, arith::IntSet>* hint_map,
+ std::vector<PrimExpr>* pending_conditions)
+ : condition_(condition),
+ relax_map_(relax_map),
+ hint_map_(hint_map),
+ pending_conditions_(pending_conditions),
+ origin_pending_conditions_num_(pending_conditions->size()) {}
+
+void ConditionalBoundsContext::EnterWithScope() {
+ ffi::Optional<ffi::Map<Var, Range>> constraints = TrySolveCondition();
+ if (!constraints.has_value()) {
+ // fail to process the condition, add to unresolved
+ pending_conditions_->push_back(condition_);
+ return;
+ }
+ // update solved var ranges
+ for (const auto& kv : constraints.value()) {
+ const VarNode* var = kv.first.get();
+ arith::IntSet new_dom = arith::IntSet::FromRange(kv.second);
+ auto relax_it = relax_map_->find(var);
+ if (relax_it != relax_map_->end()) {
+ // this is a bound for relaxed var
+ origin_map_.emplace(var, relax_it->second);
+ relax_it->second = arith::Intersect({relax_it->second, new_dom});
+ } else {
+ // this is a bound for free var
+ auto hint_it = hint_map_->find(var);
+ if (hint_it != hint_map_->end()) {
+ origin_map_.emplace(var, hint_it->second);
+ hint_it->second = arith::Intersect({hint_it->second, new_dom});
} else {
- // created new_var starts from 0
- res_src_to_dst.Set(var, new_var.as_or_throw<PrimExpr>() +
best_range->min);
- // Note that we are substituting old with new, so best_range contains
new var,
- // that is we have to substitute new with old in best_range here
- res_dst_to_src.Set(new_var,
- analyzer->Simplify(var.as_or_throw<PrimExpr>() -
-
ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(
- best_range->min,
f_dst_to_src)
- .as_or_throw<PrimExpr>()));
-
- // Add the new var to the resulting axis
- auto range = Range(IntImm(new_var->ty.as_or_throw<PrimType>(), 0),
best_range->extent);
- res_variables.push_back(new_var);
- res_ranges.Set(new_var, range);
-
- vranges.Set(new_var, range);
- analyzer->Bind(new_var, range);
+ origin_map_.emplace(var, arith::IntSet::Nothing());
+ hint_map_->insert(hint_it, {var, new_dom});
}
}
}
+}
- // Add the original conditions (with variables substituted) to the resulting
conditions
- for (const PrimExpr& old_cond :
- AsConditions(inequalities->variables, solved_bounds,
solved_other_relations)) {
- PrimExpr new_cond = analyzer->Simplify(
- ffi::StructuralMap<ffi::WalkOrder::kPreOrder>(old_cond,
subst).as_or_throw<PrimExpr>());
- if (!is_const_int(new_cond, 1)) {
- // those not represented in vranges (res_ranges)
- res_relations.push_back(new_cond);
+void ConditionalBoundsContext::ExitWithScope() {
+ pending_conditions_->resize(origin_pending_conditions_num_);
+ for (const auto& p : origin_map_) {
+ const auto* var = p.first;
+ auto relax_it = relax_map_->find(var);
+ if (relax_it != relax_map_->end()) {
+ // recover bound for relaxed var
+ relax_it->second = p.second;
+ } else {
+ // recover bound for free var
+ auto hint_it = hint_map_->find(var);
+ TVM_FFI_ICHECK(hint_it != hint_map_->end());
+ if (p.second.IsNothing()) {
+ hint_map_->erase(hint_it);
+ } else {
+ hint_it->second = p.second;
+ }
}
}
-
- // Reverse the axis so that it matches the order of the original variables
- res_variables = ffi::Array<PrimVar>(res_variables.rbegin(),
res_variables.rend());
-
- IntConstraints new_inequalities(res_variables, res_ranges, res_relations);
- IntConstraintsTransform transform(inequalities, new_inequalities,
res_src_to_dst, res_dst_to_src);
-
- return transform;
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef()
- .def_packed("arith.SolveInequalitiesAsCondition",
- [](ffi::PackedArgs args, ffi::Any* ret) {
- IntConstraints problem;
- PartialSolvedInequalities ret_ineq;
- if (args.size() == 1) {
- problem = args[0].cast<IntConstraints>();
- ret_ineq = SolveLinearInequalities(problem);
- } else if (args.size() == 3) {
- problem =
IntConstraints(args[0].cast<ffi::Array<PrimVar>>(),
- args[1].cast<ffi::Map<Var,
Range>>(),
-
args[2].cast<ffi::Array<PrimExpr>>());
- ret_ineq = SolveLinearInequalities(problem);
- } else {
- TVM_FFI_THROW(InternalError)
- << "arith.SolveInequalitiesAsCondition expects 1 or
3 arguments, gets "
- << args.size();
- }
- *ret = AsConditions(problem->variables, ret_ineq.first,
ret_ineq.second);
- })
- .def_packed("arith.SolveInequalitiesToRange",
- [](ffi::PackedArgs args, ffi::Any* ret) {
- if (args.size() == 1) {
- *ret =
SolveInequalitiesToRange(args[0].cast<IntConstraints>());
- } else if (args.size() == 3) {
- auto opt_map = args[1].cast<ffi::Optional<ffi::Map<Var,
Range>>>();
- IntConstraints
problem(args[0].cast<ffi::Array<PrimVar>>(),
- opt_map.value_or({}),
-
args[2].cast<ffi::Array<PrimExpr>>());
- *ret = SolveInequalitiesToRange(problem);
- } else {
- TVM_FFI_THROW(InternalError)
- << "arith.SolveInequalitiesToRange expects 1 or 3
arguments, gets "
- << args.size();
- }
- })
- .def_packed("arith.SolveInequalitiesDeskewRange", [](ffi::PackedArgs
args, ffi::Any* ret) {
- if (args.size() == 1) {
- *ret = SolveInequalitiesDeskewRange(args[0].cast<IntConstraints>());
- } else if (args.size() == 3) {
- auto opt_map = args[1].cast<ffi::Optional<ffi::Map<Var, Range>>>();
- IntConstraints problem(args[0].cast<ffi::Array<PrimVar>>(),
opt_map.value_or({}),
- args[2].cast<ffi::Array<PrimExpr>>());
- *ret = SolveInequalitiesDeskewRange(problem);
- } else {
- TVM_FFI_THROW(InternalError)
- << "arith.SolveInequalitiesDeskewRange expects 1 or 3 arguments,
gets "
- << args.size();
- }
- });
}
-} // namespace arith
+} // namespace s_tir
} // namespace tvm
diff --git a/src/s_tir/analysis/conditional_bounds.h
b/src/s_tir/analysis/conditional_bounds.h
new file mode 100644
index 0000000000..779e89bb5a
--- /dev/null
+++ b/src/s_tir/analysis/conditional_bounds.h
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file s_tir/analysis/conditional_bounds.h
+ * \brief Scoped conditional bounds for S-TIR buffer analysis.
+ */
+#ifndef TVM_S_TIR_ANALYSIS_CONDITIONAL_BOUNDS_H_
+#define TVM_S_TIR_ANALYSIS_CONDITIONAL_BOUNDS_H_
+
+#include <tvm/arith/int_set.h>
+#include <tvm/ir/prim/expr.h>
+#include <tvm/ir/with_context.h>
+
+#include <unordered_map>
+#include <vector>
+
+namespace tvm {
+namespace s_tir {
+
+/*!
+ * \brief Context helper to update domain map within conditional scope.
+ * Assume the condition is `0 <= i && i < 9` and domain of i is [0, 20], Then
+ * `With<ConditionalBoundsContext> ctx(condition, &relax_map, &hint_map,
&constraints)`
+ * step into scope where dom_map[i] is [0, 8]; and
+ * `With<ConditionalBoundsContext> ctx(!condition, &relax_map, &hint_map,
&constraints)`
+ * step into scope where dom_map[i] is [9, 20]
+ */
+class ConditionalBoundsContext {
+ private:
+ friend class With<ConditionalBoundsContext>;
+ /*!
+ * \brief Construct a condition bounds context.
+ * \param condition The condition holds on true branch.
+ * \param relax_map The domain map for relaxed vars to update.
+ * \param hint_map The domain map for free vars to update.
+ * \param pending_conditions The stack of unresolved constraints.
+ */
+ ConditionalBoundsContext(const PrimExpr& condition,
+ std::unordered_map<const VarNode*, arith::IntSet>*
relax_map,
+ std::unordered_map<const VarNode*, arith::IntSet>*
hint_map,
+ std::vector<PrimExpr>* pending_constraints);
+ void EnterWithScope();
+ void ExitWithScope();
+
+ /*! \brief Helper to solve related variable's bound within conditional
scope.*/
+ ffi::Optional<ffi::Map<Var, Range>> TrySolveCondition();
+
+ /*! \brief the condition holds on true branch. */
+ const PrimExpr& condition_;
+ /*! \brief domain map for relaxed vars to update */
+ std::unordered_map<const VarNode*, arith::IntSet>* relax_map_;
+ /*! \brief domain map for free vars to update */
+ std::unordered_map<const VarNode*, arith::IntSet>* hint_map_;
+ /*! \brief unresolved condition stack */
+ std::vector<PrimExpr>* pending_conditions_;
+ /*! \brief used to record and restore original var bounds */
+ std::unordered_map<const VarNode*, arith::IntSet> origin_map_;
+ /*! \brief used to record unresolved conditions num. */
+ size_t origin_pending_conditions_num_;
+};
+
+} // namespace s_tir
+} // namespace tvm
+
+#endif // TVM_S_TIR_ANALYSIS_CONDITIONAL_BOUNDS_H_
diff --git a/src/s_tir/analysis/sblock_access_region_detector.cc
b/src/s_tir/analysis/sblock_access_region_detector.cc
index 9b13d6c511..b8699b1526 100644
--- a/src/s_tir/analysis/sblock_access_region_detector.cc
+++ b/src/s_tir/analysis/sblock_access_region_detector.cc
@@ -33,6 +33,8 @@
#include <unordered_set>
#include "../../tirx/transform/ir_utils.h"
+#include "conditional_bounds.h"
+
namespace tvm {
namespace tirx {
@@ -215,12 +217,14 @@ ffi::Optional<VisitInterrupt>
BlockReadWriteDetector::Visit_(const IfThenElseNod
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(op->condition));
{
// Visit then branch
- With<ConditionalBoundsContext> ctx(op->condition, &dom_map_, &hint_map_,
&pending_conditions_);
+ With<s_tir::ConditionalBoundsContext> ctx(op->condition, &dom_map_,
&hint_map_,
+ &pending_conditions_);
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(StmtExprVisitor::Visit(op->then_case));
}
if (op->else_case) {
// Visit else branch
- With<ConditionalBoundsContext> ctx(!op->condition, &dom_map_, &hint_map_,
&pending_conditions_);
+ With<s_tir::ConditionalBoundsContext> ctx(!op->condition, &dom_map_,
&hint_map_,
+ &pending_conditions_);
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(StmtExprVisitor::Visit(op->else_case.value()));
}
return std::nullopt;
@@ -316,13 +320,15 @@ ffi::Optional<VisitInterrupt>
BlockReadWriteDetector::Visit_(const CallNode* op)
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(Visit(condition));
{
// Visit then branch
- With<ConditionalBoundsContext> ctx(condition, &dom_map_, &hint_map_,
&pending_conditions_);
+ With<s_tir::ConditionalBoundsContext> ctx(condition, &dom_map_,
&hint_map_,
+ &pending_conditions_);
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(
StmtExprVisitor::Visit(op->args[1].as_or_throw<PrimExpr>()));
}
{
// Visit else branch
- With<ConditionalBoundsContext> ctx(!condition, &dom_map_, &hint_map_,
&pending_conditions_);
+ With<s_tir::ConditionalBoundsContext> ctx(!condition, &dom_map_,
&hint_map_,
+ &pending_conditions_);
TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(
StmtExprVisitor::Visit(op->args[2].as_or_throw<PrimExpr>()));
}
diff --git a/src/s_tir/transform/compact_buffer_region.cc
b/src/s_tir/transform/compact_buffer_region.cc
index 3c81566cf6..5cacf41bad 100644
--- a/src/s_tir/transform/compact_buffer_region.cc
+++ b/src/s_tir/transform/compact_buffer_region.cc
@@ -23,7 +23,6 @@
*/
#include <tvm/arith/int_set.h>
-#include <tvm/arith/int_solver.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/extra/structural_visit.h>
#include <tvm/ffi/reflection/registry.h>
@@ -38,6 +37,7 @@
#include "../../support/arena.h"
#include "../../support/utils.h"
#include "../../tirx/transform/ir_utils.h"
+#include "../analysis/conditional_bounds.h"
#include "../schedule/utils.h"
#include "../support/nd_int_set.h"
diff --git a/src/tirx/script/printer/stmt.cc b/src/tirx/script/printer/stmt.cc
index 864f781c06..2901c01c85 100644
--- a/src/tirx/script/printer/stmt.cc
+++ b/src/tirx/script/printer/stmt.cc
@@ -16,6 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
+#include <tvm/arith/analyzer.h>
+
#include <algorithm>
#include "../../../tirx/transform/ir_utils.h" // For `GetPtrStorageScope`
diff --git a/src/tirx/transform/ir_utils.cc b/src/tirx/transform/ir_utils.cc
index 653e61a6a7..c629688ee0 100644
--- a/src/tirx/transform/ir_utils.cc
+++ b/src/tirx/transform/ir_utils.cc
@@ -24,7 +24,6 @@
#include "ir_utils.h"
#include <tvm/arith/analyzer.h>
-#include <tvm/arith/int_solver.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/extra/structural_visit.h>
#include <tvm/ffi/reflection/registry.h>
@@ -785,150 +784,6 @@ Region ConvertRegion(const MatchBufferRegion&
match_buffer, const Region& region
return result;
}
-ffi::Optional<arith::IntConstraints>
ConditionalBoundsContext::TrySolveCondition() {
- // extract equations and related vars from condition expression.
- // currently only extract simple integral equations which could be solvable.
- arith::Analyzer analyzer;
- PrimExpr condition = analyzer->Simplify(condition_);
- if (is_const_int(condition)) {
- return std::nullopt;
- }
- ffi::Array<PrimExpr> equations;
- ffi::Array<PrimVar> vars;
- std::function<void(const PrimExpr&)> fvisit = [&equations, &vars,
&fvisit](const PrimExpr& e) {
- if (e->IsInstance<prim::GENode>() || e->IsInstance<prim::GTNode>() ||
- e->IsInstance<prim::LENode>() || e->IsInstance<prim::LTNode>() ||
- e->IsInstance<prim::EQNode>() || e->IsInstance<prim::NENode>()) {
- bool is_simple = true;
- std::vector<PrimVar> cand_vars;
- auto walk_fn = [&cand_vars, &is_simple,
- &e](const PrimExpr& obj) ->
ffi::Expected<ffi::WalkResult> {
- if (obj.same_as(e)) {
- return ffi::WalkResult::Advance();
- } else if (const VarNode* var = obj.as<VarNode>()) {
- PrimType var_ty = var->ty.as_or_throw<PrimType>();
- if (var_ty.MatchesCode(DLDataTypeCode::kDLInt,
DLDataTypeCode::kDLUInt)) {
- cand_vars.push_back(ffi::GetRef<Var>(var).as_or_throw<PrimVar>());
- }
- } else {
- is_simple &= obj->IsInstance<prim::AddNode>() ||
obj->IsInstance<prim::SubNode>() ||
- obj->IsInstance<prim::MulNode>() ||
obj->IsInstance<prim::FloorDivNode>() ||
- obj->IsInstance<prim::FloorModNode>() ||
obj->IsInstance<IntImmNode>();
- }
- return ffi::WalkResult::Advance();
- };
- ffi::StructuralWalk<ffi::WalkOrder::kPostOrder>(e, walk_fn);
- if (is_simple && !cand_vars.empty()) {
- for (const PrimVar& new_var : cand_vars) {
- if (!std::any_of(vars.begin(), vars.end(),
- [&new_var](const PrimVar& v) { return
v.same_as(new_var); })) {
- vars.push_back(new_var);
- }
- }
- equations.push_back(e.as_or_throw<PrimExpr>());
- }
- } else if (e->IsInstance<prim::AndNode>()) {
- prim::And op = e.as_or_throw<prim::And>();
- fvisit(op->a);
- fvisit(op->b);
- } else if (e->IsInstance<CallNode>()) {
- Call op = e.as_or_throw<Call>();
- if (op->op.same_as(prim::builtin::likely())) {
- fvisit(op->args[0].as_or_throw<PrimExpr>());
- }
- }
- };
- fvisit(condition);
- if (equations.empty() || vars.empty()) {
- return std::nullopt;
- }
- // build dom ranges for related vars
- ffi::Map<Var, Range> ranges;
- for (const Var& v : vars) {
- arith::IntSet dom;
- auto relax_it = relax_map_->find(v.get());
- if (relax_it != relax_map_->end()) {
- dom = relax_it->second;
- } else {
- auto hint_it = hint_map_->find(v.get());
- if (hint_it != hint_map_->end()) {
- dom = hint_it->second;
- }
- }
- if (dom.defined()) {
- ranges.Set(v, Range::FromMinExtent(dom.min(),
analyzer->Simplify(dom.max() - dom.min() + 1)));
- }
- }
- // solve constraints
- arith::IntConstraints constraint(vars, ranges, equations);
- arith::IntConstraints result = arith::SolveInequalitiesToRange(constraint);
- if (!result->relations.empty()) {
- return std::nullopt;
- }
- return result;
-}
-
-ConditionalBoundsContext::ConditionalBoundsContext(
- const PrimExpr& condition, std::unordered_map<const VarNode*,
arith::IntSet>* relax_map,
- std::unordered_map<const VarNode*, arith::IntSet>* hint_map,
- std::vector<PrimExpr>* pending_conditions)
- : condition_(condition),
- relax_map_(relax_map),
- hint_map_(hint_map),
- pending_conditions_(pending_conditions),
- origin_pending_conditions_num_(pending_conditions->size()) {}
-
-void ConditionalBoundsContext::EnterWithScope() {
- ffi::Optional<arith::IntConstraints> constraints = TrySolveCondition();
- if (!constraints.has_value()) {
- // fail to process the condition, add to unresolved
- pending_conditions_->push_back(condition_);
- return;
- }
- // update solved var ranges
- for (const auto& kv : constraints.value()->ranges) {
- const VarNode* var = kv.first.get();
- arith::IntSet new_dom = arith::IntSet::FromRange(kv.second);
- auto relax_it = relax_map_->find(var);
- if (relax_it != relax_map_->end()) {
- // this is a bound for relaxed var
- origin_map_.emplace(var, relax_it->second);
- relax_it->second = arith::Intersect({relax_it->second, new_dom});
- } else {
- // this is a bound for free var
- auto hint_it = hint_map_->find(var);
- if (hint_it != hint_map_->end()) {
- origin_map_.emplace(var, hint_it->second);
- hint_it->second = arith::Intersect({hint_it->second, new_dom});
- } else {
- origin_map_.emplace(var, arith::IntSet::Nothing());
- hint_map_->insert(hint_it, {var, new_dom});
- }
- }
- }
-}
-
-void ConditionalBoundsContext::ExitWithScope() {
- pending_conditions_->resize(origin_pending_conditions_num_);
- for (const auto& p : origin_map_) {
- const auto* var = p.first;
- auto relax_it = relax_map_->find(var);
- if (relax_it != relax_map_->end()) {
- // recover bound for relaxed var
- relax_it->second = p.second;
- } else {
- // recover bound for free var
- auto hint_it = hint_map_->find(var);
- TVM_FFI_ICHECK(hint_it != hint_map_->end());
- if (p.second.IsNothing()) {
- hint_map_->erase(hint_it);
- } else {
- hint_it->second = p.second;
- }
- }
- }
-}
-
std::pair<PrimExpr, PrimExpr> GetAsyncWaitAttributes(const AttrStmtNode* op) {
TVM_FFI_ICHECK(op && op->attr_key == s_tir::attr::async_wait_queue_scope);
auto inner = op->body.as<AttrStmtNode>();
diff --git a/src/tirx/transform/ir_utils.h b/src/tirx/transform/ir_utils.h
index 9ad1099a91..1b24e462ee 100644
--- a/src/tirx/transform/ir_utils.h
+++ b/src/tirx/transform/ir_utils.h
@@ -25,7 +25,6 @@
#define TVM_TIR_TRANSFORM_IR_UTILS_H_
#include <tvm/arith/int_set.h>
-#include <tvm/arith/int_solver.h>
#include <tvm/ffi/container/tuple.h>
#include <tvm/ir/prim/builtin.h>
#include <tvm/ir/prim/expr.h>
@@ -255,48 +254,6 @@ Region ConvertRegion(const MatchBufferRegion&
match_buffer, const Region& region
*/
ffi::Array<PrimExpr> GetBufferAllocationShape(const BufferVar& buffer);
-/*!
- * \brief Context helper to update domain map within conditional scope.
- * Assume the condition is `0 <= i && i < 9` and domain of i is [0, 20], Then
- * `With<ConditionalBoundsContext> ctx(condition, &relax_map, &hint_map,
&constraints)`
- * step into scope where dom_map[i] is [0, 8]; and
- * `With<ConditionalBoundsContext> ctx(!condition, &relax_map, &hint_map,
&constraints)`
- * step into scope where dom_map[i] is [9, 20]
- */
-class ConditionalBoundsContext {
- private:
- friend class With<ConditionalBoundsContext>;
- /*!
- * \brief Construct a condition bounds context.
- * \param condition The condition holds on true branch.
- * \param relax_map The domain map for relaxed vars to update.
- * \param hint_map The domain map for free vars to update.
- * \param pending_conditions The stack of unresolved constraints.
- */
- ConditionalBoundsContext(const PrimExpr& condition,
- std::unordered_map<const VarNode*, arith::IntSet>*
relax_map,
- std::unordered_map<const VarNode*, arith::IntSet>*
hint_map,
- std::vector<PrimExpr>* pending_constraints);
- void EnterWithScope();
- void ExitWithScope();
-
- /*! \brief Helper to solve related variable's bound within conditional
scope.*/
- ffi::Optional<arith::IntConstraints> TrySolveCondition();
-
- /*! \brief the condition holds on true branch. */
- const PrimExpr& condition_;
- /*! \brief domain map for relaxed vars to update */
- std::unordered_map<const VarNode*, arith::IntSet>* relax_map_;
- /*! \brief domain map for free vars to update */
- std::unordered_map<const VarNode*, arith::IntSet>* hint_map_;
- /*! \brief unresolved condition stack */
- std::vector<PrimExpr>* pending_conditions_;
- /*! \brief used to record and restore original var bounds */
- std::unordered_map<const VarNode*, arith::IntSet> origin_map_;
- /*! \brief used to record unresolved conditions num. */
- size_t origin_pending_conditions_num_;
-};
-
// Information of tensor core fragment.
struct FragmentInfo {
// fragment shape
diff --git a/tests/python/arith/test_arith_solve_linear_equations.py
b/tests/python/arith/test_arith_solve_linear_equations.py
deleted file mode 100644
index b9550d570d..0000000000
--- a/tests/python/arith/test_arith_solve_linear_equations.py
+++ /dev/null
@@ -1,186 +0,0 @@
-# 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.
-# ruff: noqa: F401
-import random
-import sys
-
-import pytest
-import tvm_ffi
-
-import tvm
-from tvm import arith, ir, testing, tirx
-from tvm.script import tirx as T
-
-
-def test_solution_consistency():
- seed = random.randrange(sys.maxsize)
- print(
- "\nThis test is intentionally non-deterministic, "
- f"if it fails please report it in GitHub issue together with this seed
{seed}\n"
- )
- random.seed(seed)
-
- def _check(num_vars, num_formulas, coef=(-5, 5), bounds=(-20, 20)):
- variables = [tvm.tirx.Var("x" + str(i), "int32") for i in
range(num_vars)]
-
- relations = []
- for i in range(num_formulas):
- s1 = sum([v * random.randint(coef[0], coef[1]) for v in variables])
- s1 += random.randint(coef[0], coef[1])
- s2 = sum([v * random.randint(coef[0], coef[1]) for v in variables])
- s2 += random.randint(coef[0], coef[1])
- if random.random() < 0.7:
- op = tvm.tirx.EQ
- else:
- # we also make sure it can correctly handle inequalities
- op = random.choice([tvm.tirx.LE, tvm.tirx.LT, tvm.tirx.GE,
tvm.tirx.GT])
- relations.append(op(s1, s2))
-
- vranges = {v: tvm.ir.expr.Range(bounds[0], bounds[1] + 1) for v in
variables}
- solution = arith.solve_linear_equations(relations, variables, vranges)
-
- testing.check_int_constraints_trans_consistency(solution)
-
- # leaving some variables as parameters should also be ok
- for k in [1, 2]:
- if len(variables) > k:
- solution = arith.solve_linear_equations(relations,
variables[:-k], vranges)
- param_ranges = {v: vranges[v] for v in variables[-k:]}
- testing.check_int_constraints_trans_consistency(solution,
param_ranges)
-
- for i in range(2):
- _check(num_vars=1, num_formulas=1)
- for i in range(2):
- _check(num_vars=1, num_formulas=2)
-
- for i in range(2):
- _check(num_vars=2, num_formulas=1)
- for i in range(2):
- _check(num_vars=2, num_formulas=2)
- for i in range(2):
- _check(num_vars=2, num_formulas=3)
-
- for i in range(3):
- _check(num_vars=3, num_formulas=3, coef=(-2, 2))
- for i in range(3):
- _check(num_vars=3, num_formulas=4, coef=(-2, 2))
-
- for i in range(3):
- _check(num_vars=4, num_formulas=3, coef=(-1, 1))
-
- for i in range(3):
- _check(num_vars=10, num_formulas=2, coef=(-1, 1), bounds=(0, 4))
- for i in range(3):
- _check(num_vars=10, num_formulas=3, coef=(0, 1), bounds=(0, 4))
-
-
-def test_empty_var_to_solve():
- x, y = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32")
- equations = [
- tvm.tirx.EQ(x + y, 20),
- tvm.tirx.EQ(x - y, 10),
- ]
- solution = arith.solve_linear_equations(equations)
- assert len(solution.src_to_dst) == 0
- assert len(solution.dst_to_src) == 0
- assert len(solution.src.variables) == 0
- assert len(solution.src.ranges) == 0
- assert tvm_ffi.structural_equal(solution.src.relations, equations)
- assert tvm_ffi.structural_equal(solution.src, solution.dst)
-
-
-def test_unique_solution():
- x, y = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32")
-
- solution = arith.solve_linear_equations(
- [
- tvm.tirx.EQ(x + y, 20),
- tvm.tirx.EQ(x - y, 10),
- ],
- [x, y],
- )
- assert list(solution.dst.variables) == []
- assert tvm_ffi.structural_equal(solution.src_to_dst[x], T.int32(15))
- assert tvm_ffi.structural_equal(solution.src_to_dst[y], T.int32(5))
-
-
-def test_low_rank():
- x, y, z = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32"),
tvm.tirx.Var("z", "int32")
- ranges = {}
-
- solution = arith.solve_linear_equations(
- [
- tvm.tirx.EQ(x + y + z, 15),
- tvm.tirx.EQ(x + y, 10),
- ],
- [x, y, z],
- ranges,
- )
- [n0] = solution.dst.variables
- assert tvm_ffi.structural_equal(solution.src_to_dst[x], n0 + 10)
- assert tvm_ffi.structural_equal(solution.src_to_dst[y], -n0)
- assert tvm_ffi.structural_equal(solution.src_to_dst[z], T.int32(5))
-
-
-def test_infer_range():
- x, y = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32")
- ranges = {
- x: tvm.ir.Range.from_min_extent(-5, 10),
- y: tvm.ir.Range.from_min_extent(0, 10),
- }
-
- solution = arith.solve_linear_equations(
- [
- tvm.tirx.EQ(x + y, 0),
- ],
- [x, y],
- ranges,
- )
- [n0] = solution.dst.variables
- assert tvm_ffi.structural_equal(solution.src_to_dst[x], n0)
- assert tvm_ffi.structural_equal(solution.src_to_dst[y], -n0)
- # inferred from y's range
- assert tvm_ffi.structural_equal(solution.dst.ranges[n0].min, T.int32(-9))
- assert tvm_ffi.structural_equal(solution.dst.ranges[n0].extent,
T.int32(10))
- # additional inequality is added into the system for x
- [ineq] = solution.dst.relations
- assert isinstance(ineq, tvm.tirx.LE)
- assert tvm_ffi.structural_equal(ineq.a, T.int32(-5))
- assert tvm_ffi.structural_equal(ineq.b, n0)
-
-
-def test_ill_formed():
- x, y = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32")
-
- solution = arith.solve_linear_equations(
- [
- tvm.tirx.EQ(x + y, 0),
- tvm.tirx.EQ(x - y, 0),
- tvm.tirx.EQ(x, 5),
- ],
- [x, y],
- {},
- )
- assert list(solution.dst.variables) == []
- [rel] = solution.dst.relations
- ir.assert_structural_equal(rel, tirx.const(False))
- assert len(solution.src_to_dst) == 0
- assert len(solution.dst_to_src) == 0
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/arith/test_arith_solve_linear_inequality.py
b/tests/python/arith/test_arith_solve_linear_inequality.py
deleted file mode 100644
index 04b109fef3..0000000000
--- a/tests/python/arith/test_arith_solve_linear_inequality.py
+++ /dev/null
@@ -1,223 +0,0 @@
-# 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 random
-import sys
-
-import pytest
-import tvm_ffi
-
-import tvm
-from tvm import arith, ir, testing, tirx
-from tvm.script import tirx as T
-
-
[email protected](reason="See https://github.com/apache/tvm/issues/11458")
-def test_solution_consistency():
- seed = random.randrange(sys.maxsize)
- print(
- "\nThis test is intentionally non-deterministic, "
- f"if it fails please report it in GitHub issue together with this seed
{seed}\n"
- )
- random.seed(seed)
-
- def _check(variables, formulas, coef=(-5, 5), bounds=(-20, 20)):
- vs = [tvm.tirx.Var("x" + str(i), "int32") for i in range(variables)]
-
- fs = []
- for i in range(formulas):
- s1 = sum([v * random.randint(coef[0], coef[1]) for v in vs])
- s1 += random.randint(coef[0], coef[1])
- s2 = sum([v * random.randint(coef[0], coef[1]) for v in vs])
- s2 += random.randint(coef[0], coef[1])
- op = random.choice(
- [tirx.expr.EQ, tirx.expr.LE, tirx.expr.LT, tirx.expr.GE,
tirx.expr.GT]
- )
- fs.append(op(s1, s2))
-
- vranges = {v: tvm.ir.expr.Range(bounds[0], bounds[1] + 1) for v in vs}
- before = tvm.tirx.all(tirx.const(1, "bool"), *fs)
- after = arith._ffi_api.SolveInequalitiesAsCondition(vs, vranges, fs)
- after = tvm.tirx.all(tirx.const(1, "bool"), *after)
- testing.check_bool_expr_is_true(before == after, vranges)
-
- solution = arith.solve_linear_inequalities(fs, vs, vranges,
deskew_range=True)
- testing.check_int_constraints_trans_consistency(solution)
-
- for i in range(3):
- _check(1, 1)
- for i in range(3):
- _check(1, 2)
-
- for i in range(3):
- _check(2, 1)
- for i in range(3):
- _check(2, 2)
- for i in range(3):
- _check(2, 3)
-
- # Somewhere here coefficients in the results become too large, leading to
overflow,
- # so we use smaller initial coefficients
- for i in range(5):
- _check(3, 3, coef=(-2, 2))
- for i in range(5):
- _check(3, 4, coef=(-2, 2))
-
- for i in range(5):
- _check(4, 3, coef=(-1, 1))
-
- for i in range(5):
- _check(10, 2, coef=(-1, 1), bounds=(0, 4))
- for i in range(5):
- _check(10, 3, coef=(0, 1), bounds=(0, 4))
-
-
-def test_dual_variable():
- x, y = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32")
-
- variables = [x, y]
- ranges = {
- x: tvm.ir.Range(-100, 100),
- y: tvm.ir.Range(0, 10),
- }
- problem = [
- tvm.tirx.LE(x + y, 20),
- tvm.tirx.GE(x - y, 10),
- ]
-
- # solution as conditions
- solution = arith._ffi_api.SolveInequalitiesAsCondition(variables, ranges,
problem)
- assert tvm_ffi.structural_equal(solution[0], x >= (y + 10))
- assert tvm_ffi.structural_equal(solution[1], x <= (20 - y))
- assert tvm_ffi.structural_equal(solution[2], y >= 0)
- assert tvm_ffi.structural_equal(solution[3], y <= 5)
-
- # solve and get the ranges
- solution = arith.solve_linear_inequalities(problem, variables, ranges)
- # 0 <= y <=5
- assert solution.ranges[y].min == 0
- assert solution.ranges[y].extent == 6
- # y + 10 <= x <= 20 - y
- assert tvm_ffi.structural_equal(solution.ranges[x].min, y + 10)
- assert solution.ranges[x].extent == 11 # max(10 - 2y)
-
- # deskew the solved ranges to be starting from zero
- solution = arith.solve_linear_inequalities(problem, variables, ranges,
deskew_range=True)
- [x_new, y_new] = solution.dst.variables
- [rel] = solution.dst.relations
- assert tvm_ffi.structural_equal(rel, (y_new * 2) + x_new <= 10)
- assert tvm_ffi.structural_equal(solution.dst.ranges[x_new].min, T.int32(0))
- assert tvm_ffi.structural_equal(solution.dst.ranges[x_new].extent,
T.int32(11))
- assert tvm_ffi.structural_equal(solution.dst.ranges[y_new].min, T.int32(0))
- assert tvm_ffi.structural_equal(solution.dst.ranges[y_new].extent,
T.int32(6))
- assert tvm_ffi.structural_equal(solution.src_to_dst[x], x_new + (y_new +
10))
- assert tvm_ffi.structural_equal(solution.src_to_dst[y], y_new)
- assert tvm_ffi.structural_equal(solution.dst_to_src[x_new], x - y - 10)
- assert tvm_ffi.structural_equal(solution.dst_to_src[y_new], y)
-
-
-def test_equal():
- x, y = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32")
- problem = [
- tvm.tirx.GE(x + y, 10),
- tvm.tirx.GE(x - y, 2),
- tvm.tirx.LE(x, 6),
- ]
-
- solution = arith.solve_linear_inequalities(problem, [x, y])
- assert solution.ranges[x].min == 6
- assert solution.ranges[x].extent == 1
- assert solution.ranges[y].min == 4
- assert solution.ranges[y].extent == 1
-
- solution = arith.solve_linear_inequalities(problem, [x, y],
deskew_range=True)
- assert len(solution.dst.variables) == 0
- assert len(solution.dst.ranges) == 0
- assert len(solution.dst.relations) == 0
- assert solution.src_to_dst[x] == 6
- assert solution.src_to_dst[y] == 4
-
-
-def test_multi_equal():
- x, y, z = tvm.tirx.Var("x", "int32"), tvm.tirx.Var("y", "int32"),
tvm.tirx.Var("z", "int32")
- problem = [
- tvm.tirx.LE(x, 6),
- tvm.tirx.GE(x, 6),
- tvm.tirx.GE(x - z * y, 0),
- tvm.tirx.LE(x - z * y, 0),
- ]
-
- solution = arith.solve_linear_inequalities(problem, [x, y, z])
- assert solution.ranges[x].min == 6
- assert solution.ranges[x].extent == 1
- assert len(solution.relations) == 3
- assert tvm_ffi.structural_equal(solution.relations[0], x == z * y)
-
- assert isinstance(solution.relations[1], tvm.tirx.LE)
- assert solution.relations[1].b == 0
- assert isinstance(solution.relations[2], tvm.tirx.LE)
- assert solution.relations[2].b == 0
- # (z*y - 6) <= 0 && (6 - z*y) <= 0
- ana = tvm.arith.Analyzer()
- assert ana.simplify(solution.relations[1].a + solution.relations[2].a) == 0
- assert tvm_ffi.structural_equal(
- solution.relations[1].a, (z * y - 6)
- ) or tvm_ffi.structural_equal(solution.relations[2].a, (z * y - 6))
-
- solution = arith.solve_linear_inequalities(problem, [x, y, z],
deskew_range=True)
- assert solution.src_to_dst[y] == y
- assert solution.src_to_dst[z] == z
- assert solution.src_to_dst[x] == 6
-
-
-def test_no_solution():
- x = tvm.tirx.Var("x0", "int32")
- vranges = {x: tvm.ir.Range.from_min_extent(-20, 41)}
- problem = [-x - 4 <= -5 * x + 2, x * 4 + 5 <= x * 5]
-
- solution = arith.solve_linear_inequalities(problem, [x], vranges,
deskew_range=True)
- assert list(solution.dst.variables) == []
- [rel] = solution.dst.relations
- ir.assert_structural_equal(rel, tirx.const(False))
- assert len(solution.src_to_dst) == 0
- assert len(solution.dst_to_src) == 0
-
- solution = arith.solve_linear_inequalities(problem, [x], vranges)
- assert len(solution.variables) == 0
- assert len(solution.ranges) == 0
- [rel] = solution.relations
- assert not rel
-
-
-def test_unbound_var_range():
- x = tvm.tirx.Var("x0", "int32")
- free_var = tvm.tirx.Var("fv", "int32")
- vranges = {
- x: tvm.ir.Range.from_min_extent(0, tvm.tirx.Cast("int32", 1 +
tvm.tirx.log(free_var)))
- }
- problem = [x > 3]
- solution = arith.solve_linear_inequalities(
- problem,
- [x],
- vranges,
- )
- assert len(solution.variables) == 1
- assert len(solution.ranges) == 0
- assert len(solution.relations) == 3
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/s_tir/analysis/test_sblock_access_region.py
b/tests/python/s_tir/analysis/test_sblock_access_region.py
index b9266be09a..7218fad53f 100644
--- a/tests/python/s_tir/analysis/test_sblock_access_region.py
+++ b/tests/python/s_tir/analysis/test_sblock_access_region.py
@@ -457,5 +457,72 @@ def test_buffer_access_with_nested_let_binding():
tvm.ir.assert_structural_equal(block.writes, ret[1])
[email protected]("case", ["coupled", "equal", "nonlinear", "empty",
"unbounded", "rounded"])
+def test_conditional_inequality_access_regions(case):
+ # Retain the live cases from the former arith inequality solver tests
through
+ # the block-access consumer, including its conservative unresolved
fallback.
+ tirx = tvm.tirx
+ x, y, z = [tirx.Var(name, "int32") for name in ("x", "y", "z")]
+ free_var = tirx.Var("free_var", "int32")
+ unbounded_extent = tirx.Cast("int32", 1 + tirx.log(free_var))
+ cases = {
+ "coupled": (
+ [x, y],
+ [(-100, 200), (0, 10)],
+ tirx.all(x + y <= 20, x - y >= 10),
+ [(-100, 200), (0, 10)],
+ ),
+ "equal": (
+ [x, y],
+ [(-100, 200), (-100, 200)],
+ tirx.all(x + y >= 10, x - y >= 2, x <= 6),
+ [(6, 1), (4, 1)],
+ ),
+ "nonlinear": (
+ [x, y, z],
+ [(-100, 200)] * 3,
+ tirx.all(x <= 6, x >= 6, x - z * y >= 0, x - z * y <= 0),
+ [(-100, 200)] * 3,
+ ),
+ "empty": (
+ [x],
+ [(-20, 41)],
+ tirx.all(-x - 4 <= -5 * x + 2, x * 4 + 5 <= x * 5),
+ [(-20, 41)],
+ ),
+ "unbounded": ([x], [(0, unbounded_extent)], x > 3, [(0, 256)]),
+ "rounded": ([x], [(-20, 41)], tirx.all(x * 3 >= -7, x * 2 <= 9), [(-2,
7)]),
+ }
+ variables, domains, condition, expected = cases[case]
+ inside = tirx.decl_buffer([256] * len(variables), name="inside")
+ outside = tirx.decl_buffer([256] * len(variables), name="outside")
+ body = tirx.SeqStmt(
+ [
+ tirx.IfThenElse(condition,
tirx.Evaluate(inside[tuple(variables)]), None),
+ tirx.Evaluate(outside[tuple(variables)]),
+ ]
+ )
+ for var, (minimum, extent) in reversed(list(zip(variables, domains))):
+ body = tirx.For(var, minimum, extent, tirx.ForKind.SERIAL, body)
+ block = tirx.SBlock([], [], [], "conditional", body)
+ # Unbounded access sets conservatively cover the whole buffer.
+ outside_expected = [(0, 256)] if case == "unbounded" else domains
+ reads, writes, opaque = s_tir.analysis.get_sblock_access_region(
+ block, {inside: inside, outside: outside}
+ )
+ tvm.ir.assert_structural_equal(
+ reads,
+ [
+ tirx.BufferRegion(inside, [Range.from_min_extent(*bounds) for
bounds in expected]),
+ # Leaving the conditional scope must restore the original domains.
+ tirx.BufferRegion(
+ outside, [Range.from_min_extent(*bounds) for bounds in
outside_expected]
+ ),
+ ],
+ )
+ assert not writes
+ assert not opaque
+
+
if __name__ == "__main__":
tvm.testing.main()
diff --git a/tests/python/tirx/codegen/test_cuda_wait_until.py
b/tests/python/tirx/codegen/test_cuda_wait_until.py
index 26fa522626..20be1e8da9 100644
--- a/tests/python/tirx/codegen/test_cuda_wait_until.py
+++ b/tests/python/tirx/codegen/test_cuda_wait_until.py
@@ -95,9 +95,7 @@ def packed_contribution():
def _wait_macro(source):
return next(
- line
- for line in source.splitlines()
- if line.startswith("#define") and "wait_until" in line
+ line for line in source.splitlines() if line.startswith("#define") and
"wait_until" in line
)
@@ -327,9 +325,7 @@ def
test_a_declared_word_is_global_and_says_where_a_shared_wait_belongs():
"""
with pytest.raises(ValueError, match="mbarrier"):
- T.cuda.wait_until(
- None, None, None, scope="cta", space="shared", ptx_type="b32"
- )
+ T.cuda.wait_until(None, None, None, scope="cta", space="shared",
ptx_type="b32")
def test_the_four_direct_forms_are_gone():