Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

530 lines
15 KiB
Python

# 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.
# pylint: disable=invalid-name
"""Arithmetic data structure and utility"""
import enum
import tvm_ffi
from tvm import ir, tirx
from tvm.arith import IntSet
from tvm.runtime import Object
from . import _ffi_api
class ProofStrength(enum.IntEnum):
"""Proof strength of the analysis"""
DEFAULT = 0
SYMBOLIC_BOUND = 1
class CompareResult(enum.IntEnum):
"""Result of a transitive comparison.
Values must match the C++ ``arith::CompareResult`` enum.
"""
INCONSISTENT = 0
EQ = 1
LT = 2
LE = 3
GT = 4
GE = 5
NE = 6
UNKNOWN = 7
class Extension(enum.Flag):
"""Extensions enabled for RewriteSimplifier
Values should match `RewriteSimplifier::Extensions`
"""
NoExtensions = 0
TransitivelyProveInequalities = 1 << 0
ConvertBooleanToAndOfOrs = 1 << 1
ApplyConstraintsToBooleanBranches = 1 << 2
ComparisonOfProductAndSum = 1 << 3
@tvm_ffi.register_object("arith.ModularSet")
class ModularSet(Object):
"""Represent range of (coeff * x + base) for x in Z"""
def __init__(self, coeff, base):
self.__init_handle_by_constructor__(_ffi_api.ModularSet, coeff, base)
@tvm_ffi.register_object("arith.ConstIntBound")
class ConstIntBound(Object):
"""Represent constant integer bound
Parameters
----------
min_value : int
The minimum value of the bound.
max_value : int
The maximum value of the bound.
"""
POS_INF = (1 << 63) - 1
NEG_INF = -POS_INF
def __init__(self, min_value, max_value):
self.__init_handle_by_constructor__(_ffi_api.ConstIntBound, min_value, max_value)
class ConstraintScope:
"""Constraint scope.
Parameters
----------
fenter : function
A function that will be called to create an enter context.
Note
----
Do not create object directly, use Analyzer.constraint_scope
"""
def __init__(self, fenter):
self._fenter = fenter
self._fexit = None
def __enter__(self):
self._fexit = self._fenter()
def __exit__(self, ptype, value, trace):
self._fexit()
@tvm_ffi.register_object("arith.Analyzer")
class Analyzer(Object):
"""Integer arithmetic analyzer
This is a stateful analyzer class that can be used to perform
various symbolic integer analysis. The same analyzer instance can
be passed to FFI APIs to share accumulated facts across calls.
"""
def __init__(self):
self.__init_handle_by_constructor__(_ffi_api.Analyzer)
@property
def is_z3_enabled(self) -> bool:
"""Whether this build includes the Z3 backend (``USE_Z3=ON``).
The Z3-specific methods (:py:meth:`get_smtlib2`, :py:meth:`get_z3_stats`,
:py:meth:`set_z3_timeout_ms`, :py:meth:`set_z3_rlimit`) only work when
this is ``True``.
"""
return bool(_ffi_api.AnalyzerIsZ3Enabled(self))
def _check_z3_enabled(self) -> None:
if not self.is_z3_enabled:
raise RuntimeError(
"The Z3 backend is not available in this build. "
"Rebuild TVM with USE_Z3=ON to use Z3-specific Analyzer APIs."
)
def get_smtlib2(self, expr: tirx.Expr | None = None) -> str:
"""Get the current Z3 problem in SMT-LIB2 format.
Raises
------
RuntimeError
If TVM was built without Z3 (``USE_Z3=OFF``), since there is no
solver state to export. Use :py:attr:`is_z3_enabled` to check first.
Parameters
----------
expr : Optional[Expr]
The expression to prove. If provided, its negation is added to the problem.
"""
self._check_z3_enabled()
return _ffi_api.AnalyzerGetSMTLIB2(self, expr)
def set_z3_timeout_ms(self, timeout_ms: int) -> None:
"""Set Z3 timeout in milliseconds.
Raises
------
RuntimeError
If TVM was built without Z3 (``USE_Z3=OFF``).
Parameters
----------
timeout_ms : int
The timeout in milliseconds.
"""
self._check_z3_enabled()
_ffi_api.AnalyzerSetZ3TimeoutMs(self, timeout_ms)
def set_z3_rlimit(self, rlimit: int) -> None:
"""Set Z3 resource limit.
The resource limit gives deterministic solver budgeting (unlike a wall
clock timeout). A value of ``0`` disables the limit.
Raises
------
RuntimeError
If TVM was built without Z3 (``USE_Z3=OFF``).
Parameters
----------
rlimit : int
The resource limit.
"""
self._check_z3_enabled()
_ffi_api.AnalyzerSetZ3RLimit(self, rlimit)
def get_z3_stats(self) -> str:
"""Get Z3 solver statistics.
Raises
------
RuntimeError
If TVM was built without Z3 (``USE_Z3=OFF``).
Returns
-------
stats : str
The Z3 statistics.
"""
self._check_z3_enabled()
return _ffi_api.AnalyzerGetZ3Stats(self)
def const_int_bound(self, expr: tirx.Expr) -> ConstIntBound:
"""Find constant integer bound for expr.
Parameters
----------
expr : Expr
The expression.
Returns
-------
bound : ConstIntBound
The result bound
"""
return _ffi_api.AnalyzerConstIntBound(self, expr)
def const_int_bound_is_bound(self, var: tirx.Var) -> bool:
"""Check if a variable is bound to a range.
Parameters
----------
var : tvm.tirx.Var
The variable.
Returns
-------
result : bool
Whether the variable is bound to a range.
"""
return _ffi_api.AnalyzerConstIntBoundIsBound(self, var)
def modular_set(self, expr: tirx.Expr) -> ModularSet:
"""Find a modular set that expr belongs to.
Parameters
----------
expr : Expr
The expression.
Returns
-------
result : ModularSet
The result.
"""
return _ffi_api.AnalyzerModularSet(self, expr)
def simplify(self, expr: tirx.Expr, steps: int = 2) -> tirx.Expr:
"""Simplify expression via both rewrite and canonicalization.
Parameters
----------
expr : Expr
The expression.
steps : The simplification runs in the order of
rewrite_simplify (step 1) -> canonical_simplify (step 2) ->
rewrite_simplify (step 3) -> canonical_simplify (step 4) -> ...
param steps controls how many steps to run.
Default is 2, i.e., rewrite_simplify + canonical_simplify.
Returns
-------
result : Expr
The result.
"""
return _ffi_api.AnalyzerSimplify(self, expr, steps)
def clone(self) -> "Analyzer":
"""Return a deep copy of this analyzer with independent state.
The returned analyzer carries the same accumulated facts (variable
bounds, modular sets, bindings, integer-set domains, literal
constraints and transitive comparisons) as this one, but owns its own
state: binding or simplifying on either analyzer afterwards does not
affect the other. Unlike copying the handle, this is a true deep copy.
Do not call this while a constraint scope is active on this analyzer.
Returns
-------
result : Analyzer
A new analyzer holding an independent copy of the facts.
"""
return _ffi_api.AnalyzerClone(self)
def rewrite_simplify(self, expr: tirx.Expr) -> tirx.Expr:
"""Simplify expression via rewriting rules.
Parameters
----------
expr : Expr
The expression.
Returns
-------
result : Expr
The result.
"""
return _ffi_api.AnalyzerRewriteSimplify(self, expr)
@property
def rewrite_simplify_stats(self):
return _ffi_api.AnalyzerGetRewriteSimplifyStats(self)
def reset_rewrite_simplify_stats(self):
_ffi_api.AnalyzerResetRewriteSimplifyStats(self)
def canonical_simplify(self, expr: tirx.Expr) -> tirx.Expr:
"""Simplify expression via canonicalization.
Parameters
----------
expr : Expr
The expression.
Returns
-------
result : Expr
The result.
"""
return _ffi_api.AnalyzerCanonicalSimplify(self, expr)
def int_set(self, expr: tirx.Expr, dom_map: dict[tirx.Var, IntSet] | None = None) -> IntSet:
"""Compute a symbolic IntSet that covers expr for all values in dom_map.
Parameters
----------
expr : Expr
The expression.
dom_map : Optional[Dict[tvm.tirx.Var, tvm.arith.IntSet]]
The domain for variables to be relaxed. When omitted, the analyzer
uses the domains of the variables already bound to it.
Returns
-------
result : IntSet
The result.
"""
return _ffi_api.AnalyzerIntSet(self, expr, dom_map)
def can_prove(self, expr: tirx.Expr, strength: ProofStrength = ProofStrength.DEFAULT) -> bool:
"""Check whether we can prove expr to be true.
Parameters
----------
expr : Expr
The expression.
strength: ProofStrength
The proof strength. When TVM is built with Z3 (``USE_Z3=ON``), the
optional Z3 fallback is only consulted at ``SYMBOLIC_BOUND`` or
higher, after the native analyzers fail to prove the predicate.
Returns
-------
result : Expr
The result.
"""
return _ffi_api.AnalyzerCanProve(self, expr, strength)
def set_maximum_rewrite_steps(self, maximum: int) -> None:
"""Set the maximum allowed number of rewrite-simplify steps.
When a positive limit is set, the simplifier raises an exception once
it exceeds that number of rewrite steps. This is useful for guarding
against performance regressions in tests.
Parameters
----------
maximum : int
The maximum number of rewrite steps, or a non-positive value to
allow an unlimited number of steps.
"""
_ffi_api.AnalyzerSetMaximumRewriteSteps(self, maximum)
def bind(
self,
var: tirx.Var,
expr: tirx.Expr | ir.Range,
allow_override: bool = False,
) -> None:
"""Bind a variable to the expression.
Parameters
----------
var : tvm.tirx.Var
The variable.
expr : Union[tirx.Expr, ir.Range]
The expression or the range to bind to.
allow_override : bool
Whether to allow overriding an existing binding for the variable.
"""
return _ffi_api.AnalyzerBind(self, var, expr, allow_override)
def constraint_scope(self, constraint: tirx.Expr) -> ConstraintScope:
"""Create a constraint scope.
Parameters
----------
constraint : Expr
The constraint expression.
returns
-------
scope : ConstraintScope
The constraint scope
Examples
--------
.. code-block:: python
x = te.var("x")
analyzer = tvm.arith.Analyzer()
with analyzer.constraint_scope(x % 3 == 0):
# constraint in effect
assert analyzer.modular_set(x).coeff == 3
# constraint no longer in effect
assert analyzer.modular_set(x).coeff != 3
"""
def _fenter():
return _ffi_api.AnalyzerEnterConstraintContext(self, constraint)
return ConstraintScope(_fenter)
def update(
self, var: tirx.Var, info: ConstIntBound | ModularSet | IntSet, override: bool = False
) -> None:
"""Update information about var.
Parameters
----------
var : tvm.tirx.Var
The variable.
info : Union[ConstIntBound, ModularSet, IntSet]
Related information. A ``ConstIntBound`` updates the constant
integer bound, a ``ModularSet`` updates the modular set, and an
``IntSet`` updates the integer-set domain of ``var``.
override : bool
Whether allow override.
"""
if isinstance(info, ConstIntBound):
_ffi_api.AnalyzerConstIntBoundUpdate(self, var, info, override)
elif isinstance(info, ModularSet):
_ffi_api.AnalyzerModularSetUpdate(self, var, info, override)
elif isinstance(info, IntSet):
_ffi_api.AnalyzerIntSetUpdate(self, var, info, override)
else:
raise TypeError(f"Do not know how to handle type {type(info)}")
def can_prove_equal(self, lhs: tirx.Expr, rhs: tirx.Expr) -> bool:
"""Whether we can prove that lhs == rhs
Parameters
----------
lhs: Expr
The left-hand side of the comparison
rhs: Expr
The right-hand side of the comparison
Returns
-------
result: bool
Whether we can prove that lhs == rhs
"""
return _ffi_api.AnalyzerCanProveEqual(self, lhs, rhs)
def try_compare(
self, lhs: tirx.Expr, rhs: tirx.Expr, propagate_inequalities: bool = True
) -> CompareResult:
"""Compare lhs and rhs using previously provided known comparisons.
Parameters
----------
lhs : Expr
The left-hand side of the comparison.
rhs : Expr
The right-hand side of the comparison.
propagate_inequalities : bool
If true, attempt to find a sequence of transitive inequalities that
allow lhs and rhs to be compared.
Returns
-------
result : CompareResult
The most specific result that can be proven about the comparison.
Returns ``CompareResult.UNKNOWN`` when nothing can be proven.
"""
return CompareResult(_ffi_api.AnalyzerTryCompare(self, lhs, rhs, propagate_inequalities))
@property
def enabled_extensions(self) -> Extension:
"""Return the currently enabled extensions"""
value = _ffi_api.AnalyzerGetEnabledExtensions(self)
return Extension(value)
@enabled_extensions.setter
def enabled_extensions(self, flags: int | Extension):
"""Enable extensions for the analyzer
Parameters
----------
flags: Union[int,Extension]
The extensions to enable.
"""
flags = Extension(flags).value
_ffi_api.AnalyzerSetEnabledExtensions(self, flags)