/*
 * 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 <tvm/arith/int_set.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/with_context.h>

#include <limits>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>

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<ConstIntBoundNode>()
        .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<int64_t>::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<PrimExpr, ConstIntBound, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>;
  /*!
   * \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<void()> 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<ModularSetNode>()
        .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<void()> 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<void()> 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<j && j<k, i<k, false) => if_then_else(i<j && j<k, true, false)
     */
    kTransitivelyProveInequalities = (1 << 0),

    /* When simplifying a boolean expression, convert to an AND of ORs
     * (conjunctive normal form).
     *
     * Example:
     *   (a && b) || c => (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<int>(lhs) & static_cast<int>(rhs));
}
inline constexpr CompareResult operator|(CompareResult lhs, CompareResult rhs) {
  return CompareResult(static_cast<int>(lhs) | static_cast<int>(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<void()> 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> 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<Var, IntSet>& 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<void()> 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<void()> 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<PrimExpr> 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<Var, Range>& 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<ConstraintContext>` 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<AnalyzerObj>()) {}
  explicit Analyzer(ffi::ObjectPtr<AnalyzerObj> 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<arith::ConstraintContext> 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<ConstraintContext>;
  /*!
   * \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>(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>(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<std::function<void()>> recovery_functions_;
  /*! \brief Whether the constraint comes from an assumption. */
  bool is_assume_;
};

}  // namespace arith
}  // namespace tvm
#endif  // TVM_ARITH_ANALYZER_H_
