blob: e57cb0b03952cbb169bf33e6b22654a5b6241e40 [file]
/*
* 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 stackvm.h
* \brief A simple stack-based virtual machine.
*
* This can be used to interepret host side code
* to setup calls into device functions
* when only Runtime compilation for device is available(via NVRTC or OpenCL).
*/
#ifndef TVM_RUNTIME_STACKVM_STACKVM_H_
#define TVM_RUNTIME_STACKVM_STACKVM_H_
#include <tvm/runtime/c_runtime_api.h>
#include <tvm/runtime/module.h>
#include <tvm/runtime/packed_func.h>
#include <string>
#include <vector>
namespace tvm {
namespace runtime {
using runtime::operator<<;
/*!
* \brief A simple stack-based virtual machine program.
*/
class StackVM {
public:
/*!
* \brief Invoke the StackVM program.
* \param args The arguments to the StackVM.
* \param mod_ctx The module context used in running.
*/
void Run(const TVMArgs& args, runtime::ModuleNode* mod_ctx) const;
/*!
* \brief The opcode of stack vm
* \note Notation
* - sp Stack pointer
* - pc Program pointer
*/
enum OpCode {
// integer ops
ADD_I64,
SUB_I64,
MUL_I64,
DIV_I64,
MOD_I64,
EQ_I64,
LT_I64,
LE_I64,
// floating ops
ADD_F64,
SUB_F64,
MUL_F64,
DIV_F64,
EQ_F64,
LT_F64,
LE_F64,
// Pointer comparison
EQ_HANDLE,
/*!
* \brief Routine to load data from address with const offset.
* \code
* stack[sp].v_int64 = ((DType*)stack[sp].v_handle)[code[pc + 1].v_int];
* pc = pc + 2;
* \endcode
*/
ARRAY_LOAD_UINT32,
ARRAY_LOAD_INT32,
ARRAY_LOAD_INT64,
ARRAY_LOAD_FP64,
ARRAY_LOAD_HANDLE,
ARRAY_LOAD_TVMVALUE,
/*!
* \brief Routine to store data from constant offset.
* \code
* ((DType*)stack[sp - 1].v_handle)[code[pc + 1].v_int] = stack[sp];
* pc = pc + 2;
* sp = sp - 2;
* \endcode
*/
ARRAY_STORE_UINT32,
ARRAY_STORE_INT32,
ARRAY_STORE_INT64,
ARRAY_STORE_FP64,
ARRAY_STORE_HANDLE,
ARRAY_STORE_TVMVALUE,
// logical ops
NOT,
/*!
* \brief Add address by an offset.
* \code
* stack[sp - 1].v_handle = ((char*)stack[sp - 1].v_handle + stack[sp].v_int64);
* sp = sp - 1;
* \endcode
*/
ADDR_ADD,
/*!
* \brief push integer fetched from next pc position into stack
* \code
* stack[sp + 1].v_int64 = code[pc + 1].v_int;
* pc = pc + 2;
* sp = sp + 1;
* \endcode
*/
PUSH_I64,
/*!
* \brief push a value given relative index on the stack
* \code
* stack[sp + 1] = stack[sp + code[pc + 1].v_int];
* pc = pc + 2;
* sp = sp + 1;
* \endcode
*/
PUSH_VALUE,
/*!
* \brief Load data from heap to top of stack
* \code
* stack[sp + 1] = heap[code[pc + 1].v_int];
* pc = pc + 2;
* sp = sp + 1;
* \endcode
*/
LOAD_HEAP,
/*!
* \brief Store data to heap
* \code
* heap[code[pc + 1].v_int] = stack[sp];
* sp = sp - 1;
* \endcode
*/
STORE_HEAP,
/*! \brief pop value from top of the stack */
POP,
/*!
* \brief select based on operands.
* \code
* stack[sp - 2] = stack[sp].v_int64 ? stack[sp - 2] : stack[sp - 1]
* sp = sp - 2;
* \endcode
*/
SELECT,
/*!
* \brief Assert condition is true.
* \code
* ICHECK(stack[sp]) << str_data[code[pc + 1].v_int];
* sp = sp - 1;
* \endcode
*/
ASSERT,
/*!
* \brief Relative Jump if the condition is true,
* Does not change the stack status.
* \code
* if (stack[sp]) {
* pc += code[pc + 1].v_int
* } else {
* pc = pc + 2;
* }
* \endcode
*/
RJUMP_IF_TRUE,
/*!
* \brief Relative Jump if the condition is true,
* Does not change the stack status.
* \code
* if (stack[sp]) {
* pc += code[pc + 1].v_int
* } else {
* pc = pc + 2;
* }
* \endcode
*/
RJUMP_IF_FALSE,
/*!
* \brief Relative jump to a location.
* \code
* pc += code[pc + 1].v_int;
* \endcode
*/
RJUMP,
/*!
* \brief debug instruction.
* \code
* ICHECK_EQ(sp, code[pc + 1]).v_int;
* pc += 2;
* \code
*/
ASSERT_SP,
/*!
* \brief call an extern packed function
* \code
* value_stack = stack[sp - 1].v_handle;
* type_stack = stack[sp - 0].v_handle;
* call_fid = code[pc + 1].v_int;
* begin = code[pc + 2].v_int;
* end = code[pc + 3].v_int;
* num_args = end - begin - 1;
* f = extern_func[call_fid];
* stack[sp - 1] = f(&value_stack[begin:end-1], type_stack[begin:end-1], num_args);
* sp = sp - 1;
* // The type codes are hidden in the code space.
* pc = pc + 4
* \endcode
*/
CALL_PACKED_LOWERED,
// Allocate things on stack
/*!
* \brief allocate data from stack.
* \code
* num = code[pc + 1].v_int;
* void* addr = &stack[sp];
* sp = sp + num;
* stack[sp].v_handle = addr;
* pc = pc + 1;
* \endcode
*/
TVM_STACK_ALLOCA_BY_8BYTE,
/*!
* \brief allocate data from device.
* \code
* device_type = stack[sp - 2].v_int64;
* device_id = stack[sp - 1].v_int64;
* nbytes = stack[sp].v_int64;
* stack[sp - 2].v_handle = device_alloca(device_type, device_id, nbytes);
* sp = sp - 2;
* pc = pc + 1;
* \endcode
*/
TVM_DEVICE_ALLOCA,
/*!
* \brief free data into device.
* \code
* device_type = stack[sp - 2].v_int64;
* device_id = stack[sp - 1].v_int64;
* ptr = stack[sp].v_handle;
* stack[sp - 2].v_int64 = device_free(device_type, device_id, ptr);
* sp = sp - 2;
* pc = pc + 1;
* \endcode
*/
TVM_DEVICE_FREE,
/*!
* \brief throw last error
*/
TVM_THROW_LAST_ERROR,
/*!
* \brief get data from structure.
* \code
* index = code[pc + 1].v_int;
* field = code[pc + 2].v_int;
* stack[sp] = ((StructType*)stack[sp].v_handle)[index]->field;
* pc = pc + 3
* \endcode
*/
TVM_STRUCT_GET,
/*!
* \brief set data into structure.
* \code
* index = code[pc + 1].v_int;
* field = code[pc + 2].v_int;
* ((StructType*)stack[sp - 1].v_handle)[index]->field = stack[sp];
* pc = pc + 3
* sp = sp - 1
* \endcode
*/
TVM_STRUCT_SET
};
/*! \brief The kind of structure field info */
enum StructFieldKind : int {
// array head address
kArrAddr,
kArrData,
kArrShape,
kArrStrides,
kArrNDim,
kArrTypeCode,
kArrTypeBits,
kArrTypeLanes,
kArrByteOffset,
kArrDeviceId,
kArrDeviceType,
kArrKindBound_,
// TVMValue field
kTVMValueContent,
kTVMValueKindBound_
};
/*! \brief The code structure */
union Code {
OpCode op_code;
int v_int;
};
/*! \brief The state object of StackVM */
struct State {
/*! \brief The execution stack */
std::vector<TVMValue> stack;
/*! \brief The global heap space */
std::vector<TVMValue> heap;
/*! \brief stack pointer */
int64_t sp{0};
/*! \brief program counter */
int64_t pc{0};
/*! \brief The current module context of stackvm */
runtime::ModuleNode* mod_ctx{nullptr};
};
/*! \brief Initialize local cache*/
void InitCache();
/*!
* \brief Save stackvm program to an output stream
* \param strm The output stream
*/
void Save(dmlc::Stream* strm) const;
/*!
* \brief Load stackvm program from output stream
* \param strm The output stream
*/
bool Load(dmlc::Stream* strm);
/*!
* \brief Print instruction at location pc
* \param os The ostream
* \param pc The pc
* \return the pc to next instruction.
*/
int64_t PrintCode(std::ostream& os, int64_t pc) const; // NOLINT(*)
/*! \brief Get thread local state of the stack VM */
static State* ThreadLocalState();
// The code below are programs
/*! \brief The instructions */
std::vector<Code> code;
/*! \brief constant error messages */
std::vector<std::string> str_data;
/*! \brief Extern functions */
std::vector<std::string> extern_func_name;
/*! \brief name of each heap id */
std::vector<std::string> heap_id_name;
/*! \brief The memory size needed */
size_t heap_size{0};
/*! \brief The stack size required */
size_t stack_size{1024};
/*!
* \brief Convert I64 opcode to F64 Ones
* \param code The op code.
* \return the F64 op code.
*/
static OpCode CodeI64ToF64(OpCode code) {
switch (code) {
case ADD_I64:
return ADD_F64;
case SUB_I64:
return SUB_F64;
case MUL_I64:
return MUL_F64;
case DIV_I64:
return DIV_F64;
case EQ_I64:
return EQ_F64;
case LT_I64:
return LT_F64;
case LE_I64:
return LE_F64;
case MOD_I64:
LOG(FATAL) << "cannot handle mod for float";
return ADD_F64;
default:
LOG(FATAL) << "cannot handle op " << code;
return ADD_F64;
}
}
/*!
* \brief Get load opcode for type t
* \param t the type code.
* \return The load opcode
*/
static OpCode GetLoad(DLDataType t) {
ICHECK_EQ(t.lanes, 1U);
if (t.code == kTVMOpaqueHandle) return ARRAY_LOAD_HANDLE;
if (t.code == kDLInt) {
switch (t.bits) {
case 32:
return ARRAY_LOAD_INT32;
case 64:
return ARRAY_LOAD_INT64;
}
} else if (t.code == kDLUInt) {
switch (t.bits) {
case 32:
return ARRAY_LOAD_UINT32;
}
} else if (t.code == kDLFloat) {
switch (t.bits) {
case 64:
return ARRAY_LOAD_FP64;
}
}
LOG(FATAL) << "Cannot load type " << t;
return ARRAY_LOAD_FP64;
}
/*!
* \brief Get store opcode for type t
* \param t the type code.
* \return The load opcode
*/
static OpCode GetStore(DLDataType t) {
ICHECK_EQ(t.lanes, 1U);
if (t.code == kTVMOpaqueHandle) return ARRAY_STORE_HANDLE;
if (t.code == kDLInt) {
switch (t.bits) {
case 32:
return ARRAY_STORE_INT32;
case 64:
return ARRAY_STORE_INT64;
}
} else if (t.code == kDLUInt) {
switch (t.bits) {
case 32:
return ARRAY_STORE_UINT32;
}
} else if (t.code == kDLFloat) {
switch (t.bits) {
case 64:
return ARRAY_STORE_FP64;
}
}
LOG(FATAL) << "Cannot store type " << t;
return ARRAY_STORE_FP64;
}
friend std::ostream& operator<<(std::ostream& os, const StackVM& vm); // NOLINT(*)
private:
// execute the stack vm with given state
void Run(State* state) const;
// get extern function.
const PackedFunc& GetExtern(State* s, int fid) const;
// cached extern function
mutable std::vector<PackedFunc> extern_func_cache_;
};
} // namespace runtime
} // namespace tvm
namespace dmlc {
DMLC_DECLARE_TRAITS(has_saveload, ::tvm::runtime::StackVM, true);
}
#endif // TVM_RUNTIME_STACKVM_STACKVM_H_