/*
 * 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/runtime/vm/bytecode.h
 * \brief The bytecode for the virtual machine.
 */
#ifndef TVM_RUNTIME_VM_BYTECODE_H_
#define TVM_RUNTIME_VM_BYTECODE_H_

#include <tvm/ffi/dtype.h>
#include <tvm/ffi/error.h>

#include <iostream>
#include <vector>

namespace tvm {
namespace runtime {
namespace vm {

/*!
 * \brief The storage type for the bytecode in the VM.
 */
using ExecWord = int64_t;

/*! \brief A register name. */
using RegName = ExecWord;

/*!
 * \brief An alias for the integer type used ubiquitously in the VM.
 */
using Index = ExecWord;

/*!
 * \brief An enumeration of Relax's opcodes.
 *
 * The opcode is used to implement instruction
 * as a tagged union.
 */
enum class Opcode {
  Call = 1U,
  Ret = 2U,
  Goto = 3U,
  If = 4U,
};

/*! \brief A single virtual machine instruction.
 *
 * The representation of the instruction is as
 * a tagged union.
 *
 * The first field represents which instruction,
 * and by extension which field of the union
 * is active.
 */
struct Instruction {
  /*! \brief The number of bit for storing value. */
  static constexpr ExecWord kKindBit = 8;
  /*! \brief The number of bit for storing value. */
  static constexpr ExecWord kValueBit = sizeof(ExecWord) * 8 - kKindBit;
  /*! \brief The bit mask of the value part. */
  static constexpr ExecWord kValueMask = (static_cast<ExecWord>(1) << kValueBit) - 1;
  /*! \brief Maximum possible value, use 1 bit for sign. */
  static constexpr ExecWord kValueMaxLimit = (static_cast<ExecWord>(1) << (kValueBit - 1)) - 1;
  /*! \brief Minimum possible value, remove 1 slot to keep things symmetric. */
  static constexpr ExecWord kValueMinLimit = -kValueMaxLimit;
  /*! \brief Beginning of special register section. */
  static constexpr RegName kBeginSpecialReg = static_cast<ExecWord>(1) << 54;
  /*! \brief Random magic number that represents void argument, indicate null value */
  static constexpr RegName kVoidRegister = kBeginSpecialReg + 0;
  /*! \brief Random magic number that represents the VM context */
  static constexpr RegName kVMRegister = kBeginSpecialReg + 1;
  /*!
   * \brief The kind of instruction's argument.
   */
  enum class ArgKind : int { kRegister = 0, kImmediate = 1, kConstIdx = 2, kFuncIdx = 3 };

  friend std::ostream& operator<<(std::ostream& os, const ArgKind& kind) {
    switch (kind) {
      case ArgKind::kRegister:
        os << "kRegister";
        break;
      case ArgKind::kImmediate:
        os << "kImmediate";
        break;
      case ArgKind::kConstIdx:
        os << "kConstIdx";
        break;
      case ArgKind::kFuncIdx:
        os << "kFuncIdx";
        break;
      default:
        TVM_FFI_THROW(InternalError)
            << "Internal error: "
            << "Invalid ArgKind with integer value " << static_cast<int>(kind);
    }
    return os;
  }

  /*!
   * \brief The auxiliary data structure for instruction argument.
   */
  class Arg {
   public:
    /*! \brief Construct a void argument. */
    Arg() : data_(Instruction::kVoidRegister) {}
    /*!
     * \brief construct Arg from data.
     * \param data The data repr.
     */
    static Arg FromData(ExecWord data) { return Arg(data); }
    /*!
     * \brief construct a register Arg.
     * \param reg The register number.
     * \return The constructed arg.
     */
    static Arg Register(RegName reg) { return Arg(ArgKind::kRegister, reg); }
    /*!
     * \brief construct a ConstIdx arg.
     * \param index The constant index.
     * \return The constructed arg.
     */
    static Arg ConstIdx(Index index) { return Arg(ArgKind::kConstIdx, index); }
    /*!
     * \brief construct a immediate arg.
     * \param imm_value The immediate value.
     * \return The constructed arg.
     */
    static Arg Immediate(int64_t imm_value) { return Arg(ArgKind::kImmediate, imm_value); }
    /*!
     * \brief construct a FuncIdx arg.
     * \param index The func index in the function table.
     * \return The constructed arg.
     */
    static Arg FuncIdx(Index index) { return Arg(ArgKind::kFuncIdx, index); }
    /*!
     * \brief Get the kind of argument.
     * \return The kind of argument.
     */
    ArgKind kind() const {
      uint8_t kind = (data_ >> kValueBit) & 0xFF;
      return Instruction::ArgKind(kind);
    }
    /*!
     * \brief Get the value of argument.
     * \return The value of argument.
     * \note We store both positive and negative values by sign extension.
     */
    ExecWord value() const { return ((data_ & kValueMask) << kKindBit) >> kKindBit; }
    /*!
     * \brief Get the raw data repr of the arg.
     * \return The raw data.
     */
    ExecWord data() const { return data_; }

   private:
    /*! \brief Construct from the data. */
    explicit Arg(ExecWord data) : data_(data) {}
    /*! \brief Construct from the kind and value. */
    Arg(ArgKind kind, Index value) {
      TVM_FFI_ICHECK_LE(value, kValueMaxLimit);
      TVM_FFI_ICHECK_GE(value, kValueMinLimit);
      data_ = (static_cast<ExecWord>(kind) << kValueBit) | (value & kValueMask);
    }
    /*! \brief The underlying stored data. */
    ExecWord data_;
  };
  /*! \brief The instruction opcode. */
  Opcode op;
  union {
    struct /* Call */ {
      /*! \brief The destination register. */
      RegName dst;
      /*! \brief The index into the packed function table. */
      Index func_idx;
      /*! \brief The number of arguments to the packed function. */
      Index num_args;
      /*! \brief The arguments of the packed function. */
      Arg* args;
    };
    struct /* Ret */ {
      /*! \brief The return result. */
      RegName result;
    };
    struct /* Goto */ {
      /*! \brief The jump offset. */
      Index pc_offset;
    };
    struct /* If */ {
      /*! \brief The register containing the cond value. */
      RegName cond;
      /*! \brief The program counter offset for the false branch. */
      Index false_offset;
    };
  };
  /*!
   * \brief Construct a Call instruction.
   * \param func_idx The index of the function to call.
   * \param num_args The number of arguments.
   * \param args The input arguments.
   * \param dst The destination register.
   * \return The call instruction.
   */
  static Instruction Call(Index func_idx, Index num_args, Arg* args, RegName dst);
  /*!
   * \brief Construct a return instruction.
   * \param result The register containing the return value.
   * \return The return instruction.
   */
  static Instruction Ret(RegName result);
  /*!
   * \brief Construct a goto instruction.
   * \param pc_offset The register containing the jump offset.
   * \return The goto instruction.
   */
  static Instruction Goto(RegName pc_offset);
  /*!
   * \brief Construct an If instruction.
   * \param cond The register containing the cond value.
   * \param false_offset The program counter offset for the false branch.
   * \return The If instruction.
   */
  static Instruction If(RegName cond, Index false_offset);
};

}  // namespace vm
}  // namespace runtime
}  // namespace tvm

#endif  // TVM_RUNTIME_VM_BYTECODE_H_
