/*
 * 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/relax/block_builder.h
 * \brief The utility for constructing Relax binding blocks.
 */
#ifndef TVM_RELAX_BLOCK_BUILDER_H_
#define TVM_RELAX_BLOCK_BUILDER_H_

#include <tvm/arith/analyzer.h>
#include <tvm/ir/unique_name_supply.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/utils.h>
#include <tvm/runtime/base.h>

namespace tvm {
namespace relax {

/*!
 * \brief A builder to build Relax binding blocks.
 *
 * BlockBuilder provides the following three categories
 * of main functionalities for IR building and transformations:
 *
 * - Global context management: manages the IRModule,
 *   allowing query, update the surrounding global context.
 *   Provide context tools for analysis.
 * - Scope management:
 *   - Manages block scopes for bulding nested blocks.
 *   - Emit bindings to the current scope.
 *   - Construct blocks by calling EndScope.
 * - Normalization: Take an Expr, normalize it
 *   to deduce shape/type, turn things into normal forms.
 *
 * Importantly, these three categories of features can be dependent
 * on each other. For example, when we emit into scope we will call
 * normalize to ensure the code is in normal form. Similarly, when we
 * normalize we could choose to emit into the current context.
 *
 * We would encourage the developers to keep these three category
 * in mind when using and developing BlockBuilder, we can group
 * the code in a logically clean way.
 *
 * BlockBuilderNode is implemented as a virtual interface to
 * allow logically grouped implementation and internal data
 * structures that are hidden from the users.
 */
class BlockBuilderNode : public ffi::Object {
 public:
  //-------------------------------
  // Global Context management
  //-------------------------------
  /*!
   * \brief Get the unique name supply for generating unique names.
   *
   * \return The unique name supply.
   */
  virtual UniqueNameSupply name_supply() = 0;

  /*!
   * \brief Get the context IRModule in this builder.
   *
   * \note The context
   * \return The IRModule in this BlockBuilder.
   */
  virtual IRModule GetContextIRModule() const = 0;

  /*!
   * \brief Finalize the building process and return the result IRModule. Possibly rename
   * GlobalVars in the IRModule to ensure name uniqueness and the invariant:
   * every public function has the same name as its "global_symbol" attribute.
   *
   * \note this method should be called only once at the end of the building process, since it may
   * invalidate global vars previously returned by this builder. See also
   * transform::NormalizeGlobalVar.
   *
   * \return The result IRModule.
   */
  virtual IRModule Finalize() = 0;

  /*!
   * \brief Add a Relax function or a TIR PrimFunc to internal context module.
   * \param func The function to be added.
   * \param func_name_hint The name hint of the function to be added.
   * \note If the function to be added already exists, return its
   * GlobalVar directly.
   * \return The global var bound to the added function.
   */
  virtual GlobalVar AddFunction(const BaseFunc& func, ffi::String func_name_hint) = 0;

  /*!
   * \brief Update a Relax function or a TIR PrimFunc in the internal context module.
   * \param gv The global var referring the function to be updated.
   * \param function The updated function.
   */
  virtual void UpdateFunction(const GlobalVar& gv, BaseFunc function) = 0;

  //-------------------------------
  // Scope management
  //-------------------------------
  /*!
   * \brief Lookup the binding value that var binds to in the current emitted sequences.
   * \param var The input var.
   * \return The Expr bound to the input \p var.
   * \note For function parameters, this function returns std::nullopt.
   */
  virtual ffi::Optional<Expr> LookupBinding(const Var& var) = 0;

  /*!
   * \brief Begin a new scope, with optional parameters that
   *        are visible within the scope.
   *
   * Symbolic variables from the parent scope are not available.
   *
   * \param params Parameters that are visible within the scope.
   *
   * \note This function should be called when new scope is introduced
   *       (e.g. function bodies) to properly track the variable
   *       availability and help the best effort deduction.
   *
   * \sa EndScope
   */
  virtual void BeginScope(ffi::Optional<ffi::Array<Var>> params) = 0;

  /*!
   * \brief Begin a new scope, which inherits visible parameters from
   * its parent scope.
   *
   * Symbolic variables from the parent scope are available.
   *
   * \note This function should be called when an inner scope is
   *       introduced (e.g. conditional branches) to properly track
   *       the variable availability and help the best effort
   *       deduction.
   *
   * \sa EndScope
   */
  virtual void BeginInnerScope() = 0;

  /*!
   * \brief Append a definition to the current scope.
   *
   * \param var A variable within the current scope.
   *
   * \note This function should be called when a new variable is
   *       defined that may impact struct inference (e.g. MatchCast)
   *       to properly track the variable availability and help the
   *       best effort deduction.
   *
   * \sa EndScope
   */
  virtual void AddDefinitionToScope(Var var) = 0;

  /*! \brief End the previously defined scope. */
  virtual void EndScope() = 0;

  /*! \brief Begin to build a DataflowBlock. */
  virtual void BeginDataflowBlock() = 0;

  /*! \brief Begin to build a BindingBlock. */
  virtual void BeginBindingBlock() = 0;
  /*!
   * \brief End building a BindingBlock.
   * \return The BindingBlock being built.
   */
  virtual BindingBlock EndBlock() = 0;

  /*!
   * \brief Check if the block being built is DataflowBlock or not.
   * \return A boolean that indicates if the block being built is DataflowBlock or not.
   */
  virtual bool CurrentBlockIsDataFlow() = 0;

  /*!
   * \brief Emits an Expr, and returns the variable it is bound to.
   * \param expr The Expr to be emitted.
   * \param name_hint Name hint for the bound variable.
   * \return The new variable that \p expr is bound to.
   *
   * \note This Emit function normalizes the \p expr, and
   *       performs shape and type deductions by calling Normalize.
   */
  virtual Var Emit(Expr expr, ffi::String name_hint = "") = 0;

  /*!
   * \brief Emit a MatchCast.
   * \param value The input value.
   * \param ty The type to be matched.
   * \param name_hint Name hint for the bound variable.
   * \return The variable bound to the MatchCast.
   */
  virtual Var EmitMatchCast(Expr value, Type ty, ffi::String name_hint = "") = 0;

  /*!
   * \brief Generate an output for the current dataflow block.
   * \param output The output variable of the block.
   * \param name_hint Name hint for the bound variable.
   * \return The variable bound to \p output.
   */
  virtual Var EmitOutput(Expr output, ffi::String name_hint = "") = 0;

  /*!
   * \brief Emit a binding that is already normalized.
   *
   * \param normalized_binding A binding whose value is already normalized.
   *
   * \note This function requires binding to be pre-normalized.
   */
  virtual void EmitNormalized(Binding normalized_binding) = 0;

  /*!
   * \brief Convert an expression to normal form, and try to eagerly infer types and shapes.
   * \param expr The input expression.
   * \return The normalized expression.
   *
   * \note Invariant: If any of the sub expr have ty field.
   *       they must have already been normalized.
   */
  virtual Expr Normalize(const Expr& expr) = 0;

  /*!
   * \brief Normalize argument to a call or another IRNode.
   * \param expr The input expression.
   * \return The normalized expression.
   *
   * \note This function will create a binding var for non-leaf expressions such as Call.
   */
  virtual Expr NormalizeArgument(const Expr& expr) = 0;

  /*!
   * \brief Get the analyzer of the BlockBuilder.
   * \return The BlockBuilder's arithmetic analyzer.
   */
  virtual arith::Analyzer GetAnalyzer() = 0;

  static constexpr const bool _type_mutable = true;
  TVM_FFI_DECLARE_OBJECT_INFO("relax.BlockBuilder", BlockBuilderNode, ffi::Object);
};

class BlockBuilder : public ffi::ObjectRef {
 public:
  /*!
   * \brief Create a BlockBuilder.
   *
   * \param ctx_mod Optional before-transformation context module for rewriting.
   *
   * \return The created BlockBuilder.
   *
   * \note When rewriting an existing IRModule, it is important to pass it in as
   *       ctx_mod so you can lookup the context functions for cross function
   *       call analysis.
   */
  TVM_DLL static BlockBuilder Create(ffi::Optional<IRModule> ctx_mod);

  /*! \brief A marker struct to disable FNormalize
   *
   * This struct is used as a marker to disable the use of FNormalize
   * by this block builder.  This should only be used for TVMScript
   * parsing, which may require producing un-normalized Relax IR for
   * testing purposes, and to ensure that round-trips are unchanged.
   *
   * The name is deliberately verbose to draw attention during a code
   * review.  The explicit default constructor prevents aggregate
   * initialization, ensuring that the full name of the marker struct
   * appears at the callsite.
   *
   * This constructor is marked as no-lint to allow a zero-parameter
   * constructor to be marked as explicit.  The constructor must be
   * explicit in order to disable aggregate initialization in C++17.
   * While C++20 disables aggregate initialization when a
   * user-declared constructor is present, C++17 only disables
   * aggregate initialization when a user-defined constructor is
   * present.  Therefore, we need to mark the zero-parameter
   * constructor as explicit in order to prevent aggregate
   * initialization, and to ensure that the name appears at all
   * callsites.
   */
  struct DisableOperatorSpecificNormalizationForTVMScript {
    explicit DisableOperatorSpecificNormalizationForTVMScript() = default;  // NOLINT(*)
  };
  /*!
   * \brief Create a BlockBuilder.
   *
   * \param ctx_mod Optional before-transformation context module for rewriting.
   *
   * \param tag An instance of DisableOperatorSpecificNormalizationForTVMScript
   *
   * \return The created BlockBuilder.
   *
   * \note When rewriting an existing IRModule, it is important to pass it in as
   *       ctx_mod so you can lookup the context functions for cross function
   *       call analysis.
   */
  TVM_DLL static BlockBuilder Create(ffi::Optional<IRModule> ctx_mod,
                                     DisableOperatorSpecificNormalizationForTVMScript tag);

  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BlockBuilder, ffi::ObjectRef, BlockBuilderNode);
};

}  // namespace relax
}  // namespace tvm

#endif  // TVM_RELAX_BLOCK_BUILDER_H_
