tvm::runtime::Object
#
参考:tvm/include/tvm/runtime/object.h
TypeIndex
#
源码中为什么要将 enum 类型放在 struct 结构体中呢?
包含在 struct
的 {}
中,相当于在一个命名空间下,这样已经能够避免命名冲突的问题,而且 TypeIndex::ENumName
使用起来比较方便。
TypeIndex
用于 tvm::runtime::Object
的成员变量 _type_index
。
使用 Python 模拟 TypeIndex
(共 4 类):
from enum import Enum
class TypeIndex(Enum):
kRoot: int = 0 # 1. root object 类型
# 2. 标准的静态索引赋值,前端可以利用这些常量。
kRuntimeModule: int = 1 # runtime::Module
kRuntimeNDArray: int = 2 # runtime::NDArray
kRuntimeString: int = 3 # runtime::String
kRuntimeArray: int = 4 # runtime::Array
kRuntimeMap: int = 5 # runtime::Map
kRuntimeShapeTuple: int = 6 # runtime::ShapeTuple
kRuntimePackedFunc: int = 7 # runtime::PackedFunc
# 3. 可能需要更改的静态赋值。
kRuntimeClosure: int = 8
kRuntimeADT: int = 9
kStaticIndexEnd: int = 10
kDynamic = kStaticIndexEnd # 4. 类型索引在运行时分配。
Object
#
tvm::runtime::Object
是 TVM 对象容器的基类。
子类应声明以下静态常量字段:
_type_index
: 对象的静态类型索引,如果分配给TypeIndex::kDynamic
,则在运行时分配类型索引。可以通过ObjectType::TypeIndex()
访问运行时类型索引。_type_key
: 类型的唯一字符串标识符。_type_final
: 该类型是否为终端类型(对象系统中没有该类型的子类)。此字段由宏TVM_DECLARE_FINAL_OBJECT_INFO
自动设置。仍然可以对终端对象类型T
进行子类化,并使用make_object
构造它。但是IsInstance
检查只会显示对象类型是T
(而不是子类)。
如何定义 tvm::runtime::Object 子类?
需要包含两个字段:_type_child_slots
和 _type_child_slots_can_overflow
。
_type_child_slots
:表示为当前对象类型预留的子类类型索引槽位数,用于运行时优化IsInstance
中的类型检查。如果对象的类型索引在[type_index, type_index + _type_child_slots]
范围内,则可以快速判断该对象是否为当前对象类型的子类。否则,将使用回退机制来检查全局类型表。建议将其设置为估计所需的子类数量。_type_child_slots_can_overflow
:表示是否可以在子类数量超过_type_child_slots
的情况下添加额外的子类。如果为true
,则会使用回退机制来检查全局类型表。建议将其设置为false
,以获得最优的运行时速度(如果我们知道确切的子类数量)。
此外,还介绍了两个宏:TVM_DECLARE_BASE_OBJECT_INFO
和 TVM_DECLARE_FINAL_OBJECT_INFO
,用于声明可以被子类化的对象和不可被子类化的对象的辅助函数。
也可以使用:
make_object
:用于创建具有给定 type_index 和 deleter 的新对象的函数。它用于创建动态类型的对象,这些对象可以被其他对象子类化。ObjectPtr
:表示指向对象的指针的类。它提供了管理由指针指向的对象生命周期的方法。ObjectRef
:表示引用对象的类。
示例:
class BaseObj :public Object {
public:
// 对象字段
int field0;
// 对象属性
static constexpr const uint32_t _type_index = TypeIndex::kDynamic;
static constexpr const char* _type_key = "test.BaseObj";
// 告诉 TVM 编译器,BaseObj 类是 Object 类的子类,并且需要在编译时进行一些特殊的处理。
TVM_DECLARE_BASE_OBJECT_INFO(BaseObj, Object);
};
class LeafObj :public BaseObj {
public:
// 字段
int child_field0;
// 对象属性
static constexpr const uint32_t _type_index = TypeIndex::kDynamic;
static constexpr const char* _type_key = "test.LeafObj";
TVM_DECLARE_BASE_OBJECT_INFO(LeafObj, Object);
};
还需要注册:
TVM_REGISTER_OBJECT_TYPE(BaseObj);
TVM_REGISTER_OBJECT_TYPE(LeafObj);
接下来,便可使用:
void TestObjects() {
// 创建对象
ObjectRef leaf_ref(make_object<LeafObj>());
// 转换为特定实例
const LeafObj* leaf_ptr = leaf_ref.as<LeafObj>();
ICHECK(leaf_ptr != nullptr);
// 也可以转换为基类
ICHECK(leaf_ref.as<BaseObj>() != nullptr);
}
小技巧
TVM 里有个不成文的约定,所有以 Node
为结尾的类名都是继承自 Object
,不以 Node
结尾的类名都是继承自 ObjectRef
。
对象系统常用宏#
TVM_DECLARE_BASE_OBJECT_INFO(TypeName, ParentType)
是辅助宏,用于声明可以被继承的基础对象类型。它接受两个参数TypeName
和ParentType
,分别表示当前类型的名称和父类型的名称。 在宏内部,首先使用static_assert
进行编译时断言,确保父类型没有被标记为 final(即不可继承)。然后定义了名为RuntimeTypeIndex()
的静态函数,用于获取对象的运行时类型索引。在RuntimeTypeIndex()
函数内部,再次使用static_assert
进行编译时断言,确保当父类型指定了子类型插槽数时,当前类型也指定了相应的子类型插槽数。然后通过判断当前类型的索引是否为动态类型来确定是否需要调用_GetOrAllocRuntimeTypeIndex()
函数来获取或分配运行时类型索引。_GetOrAllocRuntimeTypeIndex()
函数内部使用了Object::GetOrAllocRuntimeTypeIndex()
函数来获取或分配运行时类型索引,并返回该索引。TVM_DECLARE_FINAL_OBJECT_INFO(TypeName, ParentType)
是辅助宏,用于在最终类中声明类型信息。它接受两个参数TypeName
和ParentType
,分别表示当前类型的名称和父类型的名称。 在宏内部,首先使用static const constexpr
定义了两个静态常量变量_type_final
和_type_child_slots
,分别表示当前类型是否为最终类型和子类型插槽数。然后,调用TVM_DECLARE_BASE_OBJECT_INFO(TypeName, ParentType)
宏来声明基础对象类型信息。
TVM_ATTRIBUTE_UNUSED
是宏定义,用于消除未使用变量或函数的警告。根据编译器的不同,宏的定义方式也不同。在支持 GCC 编译器的平台上,使用__attribute__((unused))
来定义该宏;在其他平台上,则直接定义为空。TVM_STR_CONCAT_(__x, __y)
和TVM_STR_CONCAT(__x, __y)
是两个字符串连接的宏定义。它们的作用是将两个字符串连接起来,生成新的字符串。TVM_OBJECT_REG_VAR_DEF
是宏定义,用于定义静态的、未使用的变量。它使用了TVM_ATTRIBUTE_UNUSED
宏来消除未使用变量的警告。这个变量的类型为uint32_t
,名称为__make_Object_tid
。
TVM_REGISTER_OBJECT_TYPE(TypeName)
是辅助宏,用于将对象类型注册到运行时。它接受参数TypeName
,表示要注册的对象类型的名称。在宏内部,使用TVM_STR_CONCAT
宏将TVM_OBJECT_REG_VAR_DEF
和__COUNTER__
连接起来,生成新的字符串。然后,将该字符串赋值为TypeName::_GetOrAllocRuntimeTypeIndex()
的返回值,即该对象的运行时类型索引。这个宏的作用是确保每个终端类都被正确地注册到运行时类型表中。TVM_DEFINE_DEFAULT_COPY_MOVE_AND_ASSIGN(TypeName)
是辅助宏,用于定义默认的拷贝/移动构造函数和赋值运算符。它接受参数TypeName
,表示要定义构造函数和赋值运算符的类的名称。在宏内部,使用TypeName(const TypeName& other) = default;
、TypeName(TypeName&& other) = default;
、TypeName& operator=(const TypeName& other) = default;
和TypeName& operator=(TypeName&& other) = default;
语句分别定义了拷贝构造函数、移动构造函数、拷贝赋值运算符和移动赋值运算符的默认实现。TVM_DEFINE_OBJECT_REF_METHODS(TypeName, ParentType, ObjectName)
是辅助宏,用于定义对象引用的方法。它接受三个参数TypeName
、ParentType
和ObjectName
,分别表示对象类型名称、父类型名称和对象名称。在宏内部,首先使用TypeName() = default;
语句定义了默认构造函数。然后,使用explicit TypeName(::tvm::runtime::ObjectPtr<::tvm::runtime::Object> n) : ParentType(n) {}
语句定义了带有::tvm::runtime::ObjectPtr<::tvm::runtime::Object>
参数的构造函数,并将传入的参数赋值给ParentType
成员变量。接着,使用TVM_DEFINE_DEFAULT_COPY_MOVE_AND_ASSIGN(TypeName);
语句重新定义了默认的拷贝/移动构造函数和赋值运算符。最后,使用const ObjectName* operator->() const { return static_cast<const ObjectName*>(data_.get()); }
和const ObjectName* get() const { return operator->(); }
语句定义了对象引用的方法,包括箭头运算符重载和operator->()
方法。