/*
 * 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/ir/function.h
 * \brief Function nodes.
 */
#ifndef TVM_IR_FUNCTION_H_
#define TVM_IR_FUNCTION_H_

#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/expr.h>

#include <string>
#include <type_traits>

namespace tvm {

/*!
 * \brief Possible Calling conventions.
 *
 *  NOTE: The calling convention also implies
 *  the way we implement the function during lowering.
 */
enum class CallingConv : int {
  /*!
   * \brief Default calling convention.
   *
   * - Uses the native calling convention of the target.
   * - Implementation: specified by the native target.
   */
  kDefault = 0,
  /*!
   * \brief ffi::Function that exposes a Cffi::Function signature.
   *
   * - Calling by ffi::Function calling convention.
   * - Implementation: Expose a function with the Cffi::Function signature.
   */
  kCPackedFunc = 1,
  /*!
   * \brief Device kernel launch
   *
   * - Call by ffi::Function calling convention.
   * - Implementation: defined by device runtime(e.g. runtime/cuda)
   */
  kDeviceKernelLaunch = 2,
};

/*!
 * \brief Supported linkage types.
 */
enum class LinkageType : int {
  /*!
   * \brief Internal linkage.
   */
  kInternal = 0,
  /*!
   * \brief External linkage.
   - Function with external linkage should have a global symbol attached to it.
   */
  kExternal = 1
};

/*!
 * \brief Generic attribute names that can be attached to any function.
 *
 * \sa tvm::tirx::attr, tvm::relax::attr
 */
namespace attr {
/*!
 * \brief Indicates the special calling convention.
 *
 * Type: IntImm
 *
 * \sa tvm::CallingConv
 */
constexpr const char* kCallingConv = "calling_conv";

/*!
 * \brief Compilation target of the function.
 *
 * Type: Target
 *
 * \sa tvm::Target
 */
constexpr const char* kTarget = "target";

/*!
 * \brief Global linker symbol of the function in generated code.
 *
 *  This option forces the code generator to name the
 *  function with the given.
 *
 *  For example, we could set a global_symbol of a function
 *  early to make sure that we can always refer to it by
 *  the symbol name in the generated DLL.
 *
 *  We should not set the attribute for local functions,
 *  so that the compiler can freely rename them.
 *
 *  A unique global symbol will be automatically assigned
 *  to each function in the module before the target code
 *  generation phase.
 *
 * Type: String
 */
constexpr const char* kGlobalSymbol = "global_symbol";

/*!
 * \brief The function uses s_tir (apache-derived TIR) semantics:
 *        parser fills layout=None, ScriptComplete wraps body in a root SBlock,
 *        and printer emits `s_tir=True` on the decorator.
 *        Default (attr absent or False) is tirx semantics.
 *
 * Type: IntImm (bool dtype)
 */
constexpr const char* kSTir = "s_tir";

/*!
 * \brief Number of inputs of the Primfunc
 *
 * Type: Int
 */
constexpr const char* kNumInputs = "num_inputs";

}  // namespace attr

/*!
 * \brief Base node of all functions.
 *
 * We support several variants of functions throughout the stack.
 * All of the functions share the same type system
 * to support cross variant calls.
 *
 * \sa BaseFunc
 */
class BaseFuncNode : public ExprNode {
 public:
  /*! \brief Additional attributes storing the meta-data */
  DictAttrs attrs;

  /*!
   * \brief Get a function attribute.
   *
   * \param attr_key The attribute key.
   * \param default_value The default value if the key does not exist, defaults to nullptr.
   *
   * \return The result
   *
   * \tparam TOBjectRef the expected object type.
   * \throw Error if the key exists but the value does not match TObjectRef
   *
   * \code
   *
   *  void GetAttrExample(const BaseFunc& f) {
   *    auto value = f->GetAttr<int64_t>("AttrKey", 0);
   *  }
   *
   * \endcode
   */
  template <typename TObjectRef>
  ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key,
                                    ffi::Optional<TObjectRef> default_value = std::nullopt) const {
    return attrs.GetAttr(attr_key, default_value);
  }
  // variant that uses TObjectRef to enable implicit conversion to default value.
  template <typename TObjectRef>
  ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key, TObjectRef default_value) const {
    return GetAttr<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
  }

  /*!
   * \brief Check whether the function has an non-zero integer attr.
   *
   * This function can be used to check whether an optional
   * attribute mark(e.g. inline) exists.
   *
   * \param attr_key The key to the attribute.
   * \return The check result.
   *
   * \code
   *
   *  void HasNonzeroAttrExample(const BaseFunc& f) {
   *    if (f->HasNonzeroAttr(attr::kInline)) {
   *      // inline the function.
   *    }
   *  }
   *
   * \endcode
   */
  bool HasNonzeroAttr(const std::string& attr_key) const { return attrs.HasNonzeroAttr(attr_key); }
  /*!
   * \brief Get the type of the linkage.
   *
   * Currently, we only consider external/internal linkage.
   * This can be extended in the future when necessary.
   *
   * \return Linkage type.
   *
   * \code
   *
   *  void Example(const BaseFunc& f) {
   *    if (f->GetLinkageType() == tvm::LinkageType::kExternal) {
   *      // Do not remove a function with external linkage
   *    }
   *  }
   *
   * \endcode
   */

  LinkageType GetLinkageType() const {
    if (GetAttr<ffi::String>(attr::kGlobalSymbol))
      return LinkageType::kExternal;
    else
      return LinkageType::kInternal;
  }

  static void RegisterReflection() {
    namespace refl = tvm::ffi::reflection;
    refl::ObjectDef<BaseFuncNode>().def_ro("attrs", &BaseFuncNode::attrs);
  }

  static constexpr const uint32_t _type_child_slots = 2;
  TVM_FFI_DECLARE_OBJECT_INFO("ir.BaseFunc", BaseFuncNode, ExprNode);
};

/*!
 * \brief Managed reference to BaseFuncNode.
 * \sa BaseFuncNode
 */
class BaseFunc : public Expr {
 public:
  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BaseFunc, Expr, BaseFuncNode);
};

}  // namespace tvm
#endif  // TVM_IR_FUNCTION_H_
