/* * 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/analyzer.h * \brief Algebra expression simplifications. */ #ifndef TVM_ARITH_ANALYZER_H_ #define TVM_ARITH_ANALYZER_H_ #include #include #include #include #include #include #include #include #include #include #include namespace tvm { /*! \brief namespace of arithmetic analysis. */ namespace arith { //------------------------------------------------------- // Base integer analysis API. // // We have multiple type of analyzers to do relaxed // integer set analysis(bound analysis, modulo) and // equivalence checking and simplification. // // Importantly, each analyzer may need result from // another analyzer. //------------------------------------------------------- // Forward declare the analyzer object and its reference handle. class AnalyzerObj; class Analyzer; class ConstraintContext; using tirx::Var; enum DivMode { /*! \brief Truncated division. */ kTruncDiv, /*! \brief Floor division. */ kFloorDiv }; /*! * \brief The strength used in top-level condition proves * \note The higher, the more time consuming it can be. * * Do not use level beyond kDefault in internal recursive rewriting in arith * analysis and only use it at top-level simplification to avoid speed issues. */ enum class ProofStrength : int { /*! \brief default strength, can be used in. */ kDefault = 0, /*! * \brief Prove using symbolic bound analysis */ kSymbolicBound = 1, }; /*! * \brief Constant integer up and lower bound(inclusive). * Useful for value bound analysis. * * set = [min_value, max_value] */ class ConstIntBoundNode : public ffi::Object { public: int64_t min_value; int64_t max_value; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("min_value", &ConstIntBoundNode::min_value) .def_ro("max_value", &ConstIntBoundNode::max_value); } /*! \brief Number to represent +inf */ static const constexpr int64_t kPosInf = std::numeric_limits::max(); /*! * \brief Number to represent -inf * \note We can make use the of fact that -kPosInf == kNegInf in the project. */ static const constexpr int64_t kNegInf = -kPosInf; static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.ConstIntBound", ConstIntBoundNode, ffi::Object); }; /*! * \brief reference class to ConstIntBoundNode * \sa ConstIntBoundNode */ class ConstIntBound : public ffi::ObjectRef { public: /*! * \brief constructor by fields. * \param min_value The mininum value. * \param max_value The maximum value. */ TVM_DLL ConstIntBound(int64_t min_value, int64_t max_value); static const constexpr int64_t kPosInf = ConstIntBoundNode::kPosInf; static const constexpr int64_t kNegInf = ConstIntBoundNode::kNegInf; TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ConstIntBound, ffi::ObjectRef, ConstIntBoundNode); }; /*! * \brief Analyzer to get constant integer bound over expression. */ class ConstIntBoundAnalyzer { public: using BoundMapType = std::unordered_map; /*! * \brief analyze the expr * \param expr The expression of interest. * \return the result of the analysis. */ TVM_DLL ConstIntBound operator()(const PrimExpr& expr) const; /*! * \brief analyze the expr with the intermediate memorized to avoid redundant computation * \param expr The expression of interest. * \param bound The lookup table to store the intermediate results * \return the result of the analysis. */ TVM_DLL ConstIntBound operator()(const PrimExpr& expr, BoundMapType* bound); /*! * \brief Update constant int bound information of var. * * \param var The variable of interest. * \param info The bound information. * \param allow_override whether we allow override of existing information. */ TVM_DLL void Update(const Var& var, const ConstIntBound& info, bool allow_override = false); /*! * \brief Bind variable to a range. * * \param var The variable. * \param range The range we bind to. * \param allow_override Whether we allow overriding an existing var's range. */ TVM_DLL void Bind(const Var& var, const Range& range, bool allow_override = false); /*! * \brief Check if a variable is bound to a range. * \param var The variable. * \return Whether the variable is bound to a range. */ TVM_DLL bool IsBound(const Var& var) const; private: friend class AnalyzerObj; friend class ConstraintContext; explicit ConstIntBoundAnalyzer(AnalyzerObj* parent); TVM_DLL ~ConstIntBoundAnalyzer(); void CopyFrom(const ConstIntBoundAnalyzer& other); /*! * \brief Update the internal state to enter constraint. * \param constraint A constraint expression. * * \return an exit function that must be called to cleanup the constraint can be nullptr. */ std::function EnterConstraint(const PrimExpr& constraint); struct Entry; class Impl; /*! \brief Internal impl */ Impl* impl_; }; /*! * \brief Range of a linear integer function. * Use to do specify the possible index values. * * set = { coeff * x + base | x in Z } * * When coeff != 0, it can also be written as * set = { n | n % coeff == base } * * This is useful to decide if the index is dividable by certain value. * For example, if index = 0 + 4 x, then we know it can be divided by 4. */ class ModularSetNode : public ffi::Object { public: /*! \brief linear co-efficient */ int64_t coeff; /*! \brief The base */ int64_t base; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("coeff", &ModularSetNode::coeff) .def_ro("base", &ModularSetNode::base); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.ModularSet", ModularSetNode, ffi::Object); }; /*! * \brief reference of ModularSetNode * \sa ModularSetNode */ class ModularSet : public ffi::ObjectRef { public: TVM_DLL ModularSet(int64_t coeff, int64_t base); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ModularSet, ffi::ObjectRef, ModularSetNode); }; /*! * \brief Analyzer to get modular information over expression. */ class ModularSetAnalyzer { public: /*! * \brief analyze the expr * \param expr The expression of interest. * \return the result of the analysis. */ TVM_DLL ModularSet operator()(const PrimExpr& expr); /*! * \brief Update constant int bound information of var. * * \param var The variable of interest. * \param info The bound information. * \param allow_override whether we allow override of existing information. */ TVM_DLL void Update(const Var& var, const ModularSet& info, bool allow_override = false); private: friend class AnalyzerObj; friend class ConstraintContext; explicit ModularSetAnalyzer(AnalyzerObj* parent); TVM_DLL ~ModularSetAnalyzer(); void CopyFrom(const ModularSetAnalyzer& other); /*! * \brief Update the internal state to enter constraint. * \param constraint A constraint expression. * * \return an exit function that must be called to cleanup the constraint can be nullptr. */ std::function EnterConstraint(const PrimExpr& constraint); struct Entry; class Impl; /*! \brief Internal impl */ Impl* impl_; }; /*! * \brief Rewrite-rule based simplifier. */ class RewriteSimplifier { public: /*! * \brief analyze the expr * \param expr The expression of interest. * \return the result of the analysis. */ TVM_DLL PrimExpr operator()(const PrimExpr& expr); /*! * \brief Update binding of var to a new expression. * * \param var The variable of interest. * \param new_expr * \param allow_override Whether we allow override of existing information. */ TVM_DLL void Update(const Var& var, const PrimExpr& new_expr, bool allow_override = false); /*! * \brief Update the internal state to enter constraint. * \param constraint A constraint expression. * * \return an exit function that must be called to cleanup the constraint can be nullptr. */ TVM_DLL std::function EnterConstraint(const PrimExpr& constraint); /*! \brief Flags to enable more computationally-intensive simplifications * * These simplifications may be required for specific schedules, but * would impose too high a compile-time cost to enable by default. * They can be enabled on an as-needed basis by calling * `RewriteSimplifier::SetEnabledExtensions` prior to using * `RewriteSimplifier::operator()`. * * Flags are defined as powers of two to allow future expansion. To * enable multiple extensions, a user should pass a bitwise OR of the * flags for each desired extension. */ enum Extension { // No extensions enabled kNone = 0, /* When simplifying an inequality, attempt to use scope-based knowns. * * Example: * if_then_else(i if_then_else(i (a || c) && (b || c) */ kConvertBooleanToAndOfOrs = (1 << 1), /* When simplifying a boolean AND or a boolean OR, simplify each * branch under the assumption that the other branch does not * already dominate the result. That is, simplify each branch of * (A && B) under the assumption that the other branch is true, * and simplify each branch of (A || B) under the assumption that * the other branch is false. * * Example: * (n < 10) && (n < 5) => (n < 10) * (n < 10) || (n < 5) => (n < 5) */ kApplyConstraintsToBooleanBranches = (1 << 2), /* Special handling for expressions `(A+B)*C < (A*B)*D` * * Expressions of the form `(A+B)*C < (A*B)*D` can occur occur * when comparing the number of operations required for two * different orderings in which matrix multiplications can be * performed. Proving or disproving this conditional allows an * optimal order of execution to be selected, even for dynamic * argument shapes. * * The default behavior of `ConstIntBounds` assumes that each term * in an expression is independent, and is insufficient to prove * these inequalities. For example, the maximum value of `(A+B)*C * - (A*B)*D` is determined by taking the maximum value of * `(A+B)*C` and subtracting the minimum value of `(A*B)*D`. * While this algorithm can be applied in all cases, the bound it * provides is looser than strictly required. * * This extension adds a check for this case. When `A`, `B`, `C`, * and `D` are all positive values, as is the case for tensor * shapes, the inequality can be written as `1/A + 1/B < D/C`. If * this inequality holds for the minimum values of `A`, `B`, and * `D`, along with the maximum value of `C`, then the inequality * holds for all values. * * This extension requires little to no performance overhead, and * may be enabled by default in future releases. */ kComparisonOfProductAndSum = (1 << 3), }; /*! \brief Enable an optional extension or extensions * * \param flags A bitwise OR of all optional extensions that should * be enabled. */ TVM_DLL void SetEnabledExtensions(Extension flags); /*! \brief Return the currently enabled extensions */ TVM_DLL Extension GetEnabledExtensions() const; /*! \brief Return the statistics counters */ TVM_DLL ffi::ObjectRef GetStatsCounters() const; /*! \brief Reset the statistics counters */ TVM_DLL void ResetStatsCounters(); /*! \brief Set the maximum allowed number of rewrite steps * * By default, the simplifier may perform as many steps as are * required. If a positive limit is set, then the simplifier will * throw an exception when exceeding that number of rewrite steps. * This allows tests to guard against performance regressions. * * Note: To maintain accurate usage counters, `Analyzer` instances * should be re-used wherever possible. For example, TIR * transformations should declare a single `Analyzer` that is used * throughout the pass. Internal helper functions that only borrow * the analyzer temporarily may receive the underlying `AnalyzerObj*` * from their calling scope. */ TVM_DLL void SetMaximumRewriteSteps(int64_t maximum); private: friend class AnalyzerObj; friend class ConstraintContext; friend class CanonicalSimplifier; explicit RewriteSimplifier(AnalyzerObj* parent); TVM_DLL ~RewriteSimplifier(); void CopyFrom(const RewriteSimplifier& other); class Impl; /*! \brief Internal impl */ Impl* impl_; }; /*! * \brief Canonical-form based simplifier. */ class CanonicalSimplifier { public: /*! * \brief analyze the expr * \param expr The expression of interest. * \return the result of the analysis. */ TVM_DLL PrimExpr operator()(const PrimExpr& expr); /*! * \brief Update binding of var to a new expression. * * \param var The variable of interest. * \param new_expr * \param allow_override whether we allow override of existing information. */ TVM_DLL void Update(const Var& var, const PrimExpr& new_expr, bool allow_override = false); private: friend class AnalyzerObj; friend class ConstraintContext; explicit CanonicalSimplifier(AnalyzerObj* parent); TVM_DLL ~CanonicalSimplifier(); void CopyFrom(const CanonicalSimplifier& other); class Impl; /*! \brief Internal impl */ Impl* impl_; }; /*! \brief Structure for representing result of known * * Values are assigned to allow these flags to be used in bitwise * operations. */ enum class CompareResult : int { kInconsistent = 0, kEQ = 1, kLT = 2, kLE = 3, kGT = 4, kGE = 5, kNE = 6, kUnknown = 7 }; inline constexpr CompareResult operator&(CompareResult lhs, CompareResult rhs) { return CompareResult(static_cast(lhs) & static_cast(rhs)); } inline constexpr CompareResult operator|(CompareResult lhs, CompareResult rhs) { return CompareResult(static_cast(lhs) | static_cast(rhs)); } /*! * \brief Using previously specified knowns, compare the expressions provided * * Given known expressions [(a OP b), (b OP c), ..., (y OP z)], search * for a known result for `(a OP z)`. */ class TransitiveComparisonAnalyzer { public: /* \brief Using previously specified knowns, compare the expressions provided * * \param lhs The left-hand side of the comparison * * \param rhs The right-hand side of the comparison * * \param propagate_inequalities If true, attempt to find a sequence * of transitive inequalities that allow the lhs and rhs to be * compared. If false, only use the known comparison that have been * directly provided. Using `propagate_inequalities = false` is * roughly equivalent to comparing against all known inequality * expressions using `ExprDeepEqual`, but also allows for constant * offsets on either side of the inequality. * * \return The most specific result that can be proven about the * comparison. If nothing can be proven, returns kUnknown. */ TVM_DLL CompareResult TryCompare(const PrimExpr& lhs, const PrimExpr& rhs, bool propagate_inequalities = true); /*! \brief Bind a variable as being equal to a known expression * * \param var The variable of interest. * \param expr The bound expression * \param allow_override Whether to allow override of existing information. */ TVM_DLL void Bind(const Var& var, const PrimExpr& expr, bool allow_override = false); /*! \brief Bind a variable as being within a specified range * * \param var The variable of interest. * \param range The known range * \param allow_override Whether to allow override of existing information. */ TVM_DLL void Bind(const Var& var, const Range& range, bool allow_override = false); /*! * \brief Update the internal state to enter constraint. * \param constraint A constraint expression. * * \return an exit function that must be called to cleanup the constraint can be nullptr. */ TVM_DLL std::function EnterConstraint(const PrimExpr& constraint); private: friend class AnalyzerObj; friend class ConstraintContext; TransitiveComparisonAnalyzer(); TVM_DLL ~TransitiveComparisonAnalyzer(); void CopyFrom(const TransitiveComparisonAnalyzer& other); class Impl; /*! \brief Internal impl */ std::unique_ptr impl_; }; /*! * \brief Integer set analyzer. */ class IntSetAnalyzer { public: /*! * \brief Find a symbolic integer set that contains all possible values of * expr given the domain of each variables. * * \param expr The expression of interest. * \param dom_map The domain map to indicate which variable to relax. * \return the result of the analysis. */ TVM_DLL IntSet operator()(const PrimExpr& expr, const ffi::Map& dom_map); /*! * \brief Find a symbolic integer set that contains all possible * values of expr given the domain of each variables, using * the domain map defined by bound variables. * * \param expr The expression of interest. * \return the result of the analysis. */ TVM_DLL IntSet operator()(const PrimExpr& expr); /*! * \brief Update binding of var to a new expression. * * \param var The variable of interest. * \param new_interval_set The set of allowed values for this var. * \param allow_override whether we allow override of existing information. */ TVM_DLL void Update(const Var& var, const IntSet& new_interval_set, bool allow_override = false); /*! * \brief Update binding of var to a new expression. * * \param var The variable of interest. * \param new_range The range of allowed values for this var. * \param allow_override whether we allow override of existing information. */ TVM_DLL void Bind(const Var& var, const Range& new_range, bool allow_override = false); std::function EnterConstraint(const PrimExpr& constraint); private: friend class AnalyzerObj; explicit IntSetAnalyzer(AnalyzerObj* parent); TVM_DLL ~IntSetAnalyzer(); void CopyFrom(const IntSetAnalyzer& other); class Impl; /*! \brief Internal impl */ Impl* impl_; }; class Z3Prover { public: /*! * \brief Update binding of var to a new expression. * * \param var The variable of interest. * \param new_range The range of allowed values for this var. * \param allow_override whether we allow override of existing information. */ TVM_DLL void Bind(const Var& var, const Range& new_range, bool allow_override = false); /*! * \brief Update binding of var to a new expression. * * \param var The variable of interest. * \param expr The bound expression. * \param allow_override whether we allow override of existing information. */ TVM_DLL void Bind(const Var& var, const PrimExpr& expr, bool allow_override = false); /*! * \brief Whether the Z3 backend is compiled into this build (USE_Z3=ON). * * \return true if the real Z3 prover is available, false for the stub. */ TVM_DLL bool IsEnabled() const; /*! * \brief Whether can we prove expr is always true. * * \param expr The expression. * \return Whether we can prove it. */ TVM_DLL bool CanProve(const PrimExpr& expr); /*! * \brief Update the internal state to enter constraint. * * \param constraint A constraint expression. * \return an exit function that must be called to cleanup the constraint can be nullptr. */ std::function EnterConstraint(const PrimExpr& constraint); /*! * \brief Get the SMTLIB2 representation of the current context. * * \param expr The optional expression to check. * \return The SMTLIB2 string. */ ffi::String GetSMTLIB2(const ffi::Optional expr); /*! * \brief Get statistics about Z3 prover. * * \return The statistics string. */ ffi::String GetStats(); /*! * \brief Set timeout in milliseconds for Z3 prover. * * \param timeout_ms The timeout in milliseconds. */ void SetTimeoutMs(unsigned timeout_ms); /*! * \brief Set resource limitation for Z3 prover. * * \param rlimit the resource limitation. */ void SetRLimit(unsigned rlimit); /*! * \brief Get the Z3 model for the given expression if satisfiable. * * \param expr The expression to get the model for. * \return The model as a string. */ ffi::String GetModel(const PrimExpr& expr); /*! * \brief Count the number of integer values that satisfy the current constraints. * * This method uses Z3's model enumeration to count how many distinct values of * the given variable satisfy all current constraints. * * \param var The variable to count satisfying values for. * \param max_count Maximum number of solutions to enumerate. * \param min_consecutive Minimum consecutive count requirement. * \return The number of distinct values that satisfy the constraints, or a negative error code. */ TVM_DLL int64_t CountSatisfyingValues(const Var& var, int64_t max_count = 2048, int64_t min_consecutive = 1); private: friend class AnalyzerObj; friend class Analyzer; explicit Z3Prover(AnalyzerObj* parent); TVM_DLL ~Z3Prover(); void CopyFrom(const Z3Prover& other); class Impl; Impl* impl_; }; /*! * \brief Analyzer that contains bunch of sub-analyzers. * * Each sub-analyzer can make use of another sub-analyzer * by weak reference of this. * * NOTE for sub-analyzer developers: * If the analyzer uses memoization, we need to clear the internal * cache when information about a Var has been overridden. */ class TVM_DLL AnalyzerObj : public ffi::Object { public: /*! \brief sub-analyzer: const integer bound */ ConstIntBoundAnalyzer const_int_bound; /*! \brief sub-analyzer: modular set */ ModularSetAnalyzer modular_set; /*! \brief sub-analyzer rewrite simplify */ RewriteSimplifier rewrite_simplify; /*! \brief sub-analyzer canonical simplify */ CanonicalSimplifier canonical_simplify; /*! \brief sub-analyzer: int set */ IntSetAnalyzer int_set; /*! \brief sub-analyzer transitive comparisons */ TransitiveComparisonAnalyzer transitive_comparisons; /*! \brief sub-analyzer using Z3 */ Z3Prover z3_prover; /*! \brief constructor */ AnalyzerObj(); /*! * \brief Mark the value as non-negative value globally in analyzer. * * Only call this function if the non-neg condition is global and * not context-dependent. * * This function does best-effort propagations to the sub-analyzers * * A canonical use of MarkGlobalNonNegValue is to record a non-negativity * fact at a Var's definition site. Because each Var identity is defined * exactly once in canonical IR, the fact is globally valid for that identity. * * \note We expose this function because non-negative global values, * such as symbolic buffer shapes in function arguments are really * important to ensure the best simplification, and usually they * can be handled in a simpler way than the generic constraints. * * This function may call into the Update function of the sub-analyzers. */ void MarkGlobalNonNegValue(const PrimExpr& value); /*! * \brief Notify all the sub-analyzers that var * is created and binded to expr. * * Each var can only be bound once. * * \param var The variable. * \param expr The expression we bind to. * \param allow_override Whether we allow overriding an existing var's * expression. This option should not be used if there is any dependency * between variables. */ void Bind(const Var& var, const PrimExpr& expr, bool allow_override = false); /*! * \brief Notify all the sub-analyzers that var * is created and bound to a range. * * Each var can only be bound once. * * \param var The variable. * \param range The range we bind to. * \param allow_override Whether we allow overriding an existing var's * expression. This option should not be used if there is any dependency * between variables. */ void Bind(const Var& var, const Range& range, bool allow_override = false); /*! * \brief Bind all the vars in the Map * * \param variables The {variable -> range} map. * \param allow_override Whether we allow overriding an existing var's * expression. This option should not be used if there is any dependency * between variables. */ void Bind(const ffi::Map& variables, bool allow_override = false); /*! * \brief Whether can we prove expr >= val. * Non-negative proof is very useful in integer analysis * to lower divisions and mods given difference in trunc and ceil mode. * * \param expr The expression. * \param lower_bound The lower bound. * \return Whether we can prove it. * * \note Analyzer will call into sub-analyzers to get the result. */ bool CanProveGreaterEqual(const PrimExpr& expr, int64_t lower_bound); /*! * \brief Whether can we prove expr < val. * Non-negative proof is very useful in integer analysis * to lower divisions and mods given difference in trunc and ceil mode. * * \param expr The expression. * \param upper_bound The upper bound. * \return Whether we can prove it. * * \note Analyzer will call into sub-analyzers to get the result. */ bool CanProveLess(const PrimExpr& expr, int64_t upper_bound); /*! * \brief Whether can we prove lhs == rhs. * * \param lhs The input lhs. * \param rhs The input rhs. * \return Whether we can prove lhs == rhs. * * \note Analyzer will call into sub-analyzers to get the result. */ bool CanProveEqual(const PrimExpr& lhs, const PrimExpr& rhs); /*! * \brief Whether we can prove lhs is smaller than possibly symbolic shape. * * By calling this function, the caller gives an extra hint that shape > 0, * because it appeared in buffer shape. * * This is useful to prove condition such as 32 <= 32 * n where the 32 * n * is known to be a shape. Use this routine to reduce the symbolic comparisons * in buffer compaction. * * The underlying analyzer will use the kSymbolicBound proof. * * \param lhs The input lhs. * \param shape The symbolic shape. * \return Whether we can prove lhs <= shape. */ bool CanProveLessEqualThanSymbolicShapeValue(const PrimExpr& lhs, const PrimExpr& shape); /*! * \brief Whether can we prove condition. * * \param cond The expression to be proved. * \param strength the strength of the prove. * * \return The result. * * \note Analyzer will call into sub-analyzers to get the result. * Do not use strength beyond default in sub-analyzers and * only use it in top-level predicate analysis. */ bool CanProve(const PrimExpr& cond, ProofStrength strength = ProofStrength::kDefault); /*! * \brief Simplify expr. * * \param expr The expression to be simplified. * \param 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. * \return The result. * * \note Analyzer will call into sub-analyzers to get the result. */ PrimExpr Simplify(const PrimExpr& expr, int steps = 2); /*! * \brief Deep-copy this analyzer into a new, independent Analyzer. * * The returned analyzer carries the same accumulated facts (variable * bounds, modular sets, rewrite/canonical 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. This is the deep copy that handle-copying an * Analyzer does not provide. * * \note Do not call this while a `With` scope is active * on this analyzer. The clone would inherit the scoped constraints * but not the recovery functions that pop them on scope exit, so the * constraints would leak as if they were global facts. Clone at a * point where no constraint scope is in effect. * * \return A new Analyzer holding an independent copy of the facts. */ Analyzer Clone() const; /*! * \brief Analyzer methods update facts, constraints, caches, and stats. * * Marking the object mutable makes the `Analyzer` ObjectRef expose a * non-const `operator->`, so APIs can take `const Analyzer&` while still * allowing calls such as `analyzer->Bind(...)`. * `const Analyzer&` keeps the handle itself from being rebound; it does * not make the underlying AnalyzerObj immutable. */ static constexpr bool _type_mutable = true; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.Analyzer", AnalyzerObj, ffi::Object); }; /*! * \brief Managed reference to AnalyzerObj. * * Analyzer is a lightweight, reference-counted handle around a heap-allocated * AnalyzerObj. Because it is now a first-class FFI object, an Analyzer can be * passed across the tvm-ffi boundary (e.g. handed from Python into a C++ pass) * and shared, so that accumulated bindings/constraints persist across calls. * Copying an Analyzer copies the handle, and both handles share the same * mutable AnalyzerObj state. * This is not a deep copy of analyzer facts or caches. * * \sa AnalyzerObj */ class Analyzer : public ffi::ObjectRef { public: /*! \brief Default-construct a fresh analyzer (allocates an AnalyzerObj). */ Analyzer() : Analyzer(ffi::make_object()) {} explicit Analyzer(ffi::ObjectPtr n) : ffi::ObjectRef(std::move(n)) { TVM_FFI_ICHECK(this->get() != nullptr); } TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Analyzer, ffi::ObjectRef, AnalyzerObj); }; /*! * \brief Constraint context. * * \code * * Var x("x"); * arith::Analyzer analyzer; * { * With scope(analyzer, tvm::floormod(x, 3) == 0); * TVM_FFI_ICHECK_EQ(analyzer->modular_set(x)->coeff, 3); * } * // constraint no longer in effect. * TVM_FFI_ICHECK_NE(analyzer->modular_set(x)->coeff, 3); * * \endcode */ class ConstraintContext { private: // declare friend to enable with. friend class With; /*! * \brief Construct a constraint context. * \param analyzer The analyzer whose context is updated. The context * keeps a reference to the analyzer while the scope is active. * \param constraint The constraint to be applied. */ ConstraintContext(const Analyzer& analyzer, PrimExpr constraint) : ConstraintContext(analyzer, std::move(constraint), false) {} /*! * \brief Construct a constraint context. * \param analyzer The analyzer whose context is updated. The context * keeps a reference to the analyzer while the scope is active. * \param constraint The constraint to be applied. * \param is_assume Whether the constraint comes from an assumption. */ ConstraintContext(const Analyzer& analyzer, PrimExpr constraint, bool is_assume) : analyzer_(analyzer), constraint_(std::move(constraint)), is_assume_(is_assume) {} /*! * \brief Construct a constraint context from a borrowed analyzer object. * \param analyzer The borrowed analyzer object. * \param constraint The constraint to be applied. * * This overload is for internal callers that already operate on AnalyzerObj*. */ ConstraintContext(AnalyzerObj* analyzer, PrimExpr constraint) : ConstraintContext(ffi::GetRef(analyzer), std::move(constraint), false) {} /*! * \brief Construct a constraint context from a borrowed analyzer object. * \param analyzer The borrowed analyzer object. * \param constraint The constraint to be applied. * \param is_assume Whether the constraint comes from an assumption. */ ConstraintContext(AnalyzerObj* analyzer, PrimExpr constraint, bool is_assume) : ConstraintContext(ffi::GetRef(analyzer), std::move(constraint), is_assume) {} // enter the scope. void EnterWithScope(); // exit the scope. void ExitWithScope(); /*! \brief Analyzer kept alive while the context is active. */ Analyzer analyzer_; /*! \brief The constraint */ PrimExpr constraint_; /*! \brief functions to be called in recovery */ std::vector> recovery_functions_; /*! \brief Whether the constraint comes from an assumption. */ bool is_assume_; }; } // namespace arith } // namespace tvm #endif // TVM_ARITH_ANALYZER_H_