blob: 9c663182a4b13500bdb70f8828741c34f3c1a315 [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 tvm/ffi/object.h
* \brief A managed object in the TVM FFI.
*/
#ifndef TVM_FFI_OBJECT_H_
#define TVM_FFI_OBJECT_H_
#include <tvm/ffi/base_details.h>
#include <tvm/ffi/c_api.h>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
namespace tvm {
namespace ffi {
/*!
* \brief TypeIndex enum, alias of TVMFFITypeIndex.
*/
using TypeIndex = TVMFFITypeIndex;
/*!
* \brief TypeInfo, alias of TVMFFITypeInfo.
*/
using TypeInfo = TVMFFITypeInfo;
/*!
* \brief Helper tag to explicitly request unsafe initialization.
*
* Constructing an ObjectRefType with UnsafeInit{} will set the data_ member to nullptr.
*
* When initializing Object fields, ObjectRef fields can be set to UnsafeInit.
* This enables the "construct with UnsafeInit then set all fields" pattern
* when the object does not have a default constructor.
*
* Used for initialization in controlled scenarios where such unsafe
* initialization is known to be safe.
*
* Each ObjectRefType should have a constructor that takes an UnsafeInit tag.
*
* \note As the name suggests, do not use it in normal code paths.
*/
struct UnsafeInit {};
/*!
* \brief Known type keys for pre-defined types.
*/
struct StaticTypeKey {
/*! \brief The type key for Any */
static constexpr const char* kTVMFFIAny = "Any";
/*! \brief The type key for None */
static constexpr const char* kTVMFFINone = "None";
/*! \brief The type key for bool */
static constexpr const char* kTVMFFIBool = "bool";
/*! \brief The type key for int */
static constexpr const char* kTVMFFIInt = "int";
/*! \brief The type key for float */
static constexpr const char* kTVMFFIFloat = "float";
/*! \brief The type key for void* */
static constexpr const char* kTVMFFIOpaquePtr = "void*";
/*! \brief The type key for DataType */
static constexpr const char* kTVMFFIDataType = "DataType";
/*! \brief The type key for Device */
static constexpr const char* kTVMFFIDevice = "Device";
/*! \brief The type key for DLTensor* */
static constexpr const char* kTVMFFIDLTensorPtr = "DLTensor*";
/*! \brief The type key for const char* */
static constexpr const char* kTVMFFIRawStr = "const char*";
/*! \brief The type key for TVMFFIByteArray* */
static constexpr const char* kTVMFFIByteArrayPtr = "TVMFFIByteArray*";
/*! \brief The type key for ObjectRValueRef */
static constexpr const char* kTVMFFIObjectRValueRef = "ObjectRValueRef";
/*! \brief The type key for SmallStr */
static constexpr const char* kTVMFFISmallStr = "ffi.SmallStr";
/*! \brief The type key for SmallBytes */
static constexpr const char* kTVMFFISmallBytes = "ffi.SmallBytes";
/*! \brief The type key for Error */
static constexpr const char* kTVMFFIError = "ffi.Error";
/*! \brief The type key for Bytes */
static constexpr const char* kTVMFFIBytes = "ffi.Bytes";
/*! \brief The type key for String */
static constexpr const char* kTVMFFIStr = "ffi.String";
/*! \brief The type key for Shape */
static constexpr const char* kTVMFFIShape = "ffi.Shape";
/*! \brief The type key for Tensor */
static constexpr const char* kTVMFFITensor = "ffi.Tensor";
/*! \brief The type key for Object */
static constexpr const char* kTVMFFIObject = "ffi.Object";
/*! \brief The type key for Function */
static constexpr const char* kTVMFFIFunction = "ffi.Function";
/*! \brief The type key for Array */
static constexpr const char* kTVMFFIArray = "ffi.Array";
/*! \brief The type key for List */
static constexpr const char* kTVMFFIList = "ffi.List";
/*! \brief The type key for Map */
static constexpr const char* kTVMFFIMap = "ffi.Map";
/*! \brief The type key for Module */
static constexpr const char* kTVMFFIModule = "ffi.Module";
/*! \brief The type key for Dict */
static constexpr const char* kTVMFFIDict = "ffi.Dict";
/*! \brief The type key for OpaquePyObject */
static constexpr const char* kTVMFFIOpaquePyObject = "ffi.OpaquePyObject";
};
/*!
* \brief Get type key from type index
* \param type_index The input type index
* \return the type key
*/
inline std::string TypeIndexToTypeKey(int32_t type_index) {
const TypeInfo* type_info = TVMFFIGetTypeInfo(type_index);
return std::string(type_info->type_key.data, type_info->type_key.size);
}
namespace details {
// Helper to perform
// unsafe operations related to object
struct ObjectUnsafe;
/*! \brief One counter for weak reference. */
constexpr uint64_t kCombinedRefCountWeakOne = static_cast<uint64_t>(1) << 32;
/*! \brief One counter for strong reference. */
constexpr uint64_t kCombinedRefCountStrongOne = 1;
/*! \brief Both reference counts. */
constexpr uint64_t kCombinedRefCountBothOne = kCombinedRefCountWeakOne | kCombinedRefCountStrongOne;
/*! \brief Mask to get the lower 32 bits of the combined reference count. */
constexpr uint64_t kCombinedRefCountMaskUInt32 = (static_cast<uint64_t>(1) << 32) - 1;
/*!
* Check if the type_index is an instance of TargetObjectType.
*
* \tparam TargetType The target object type to be checked.
*
* \param object_type_index The type index to be checked, caller
* ensures that the index is already within the object index range.
*
* \return Whether the target type is true.
*/
template <typename TargetType>
TVM_FFI_INLINE bool IsObjectInstance(int32_t object_type_index);
} // namespace details
/*!
* \brief Base class of all object containers.
*
* Sub-class of objects should declare the following static constexpr fields:
*
* - _type_index:
* Static type index of the object, if assigned to TypeIndex::kTVMFFIDynObject
* the type index will be assigned during runtime.
* Runtime type index can be accessed by ObjectType::TypeIndex();
* - _type_key:
* The unique string identifier of the type.
* - _type_final:
* Whether the type is terminal type(there is no subclass of the type in the object system).
* This field is automatically set by macro TVM_FFI_DECLARE_OBJECT_INFO_FINAL
* It is still OK to sub-class a terminal object type T and construct it using make_object.
* But IsInstance check will only show that the object type is T(instead of the sub-class).
* - _type_mutable:
* Whether we would like to expose cast to non-constant pointer
* ObjectType* from Any/AnyView. By default, we set to false so it is not exposed.
*
* The following two fields are necessary for base classes that can be sub-classed.
*
* - _type_child_slots:
* Number of reserved type index slots for child classes.
* Used for runtime optimization for type checking in IsInstance.
* If an object's type_index is within range of [type_index, type_index + _type_child_slots]
* Then the object can be quickly decided as sub-class of the current object class.
* If not, a fallback mechanism is used to check the global type table.
* Recommendation: set to estimate number of children needed.
*
* - _type_child_slots_can_overflow:
* Whether we can add additional child classes even if the number of child classes
* exceeds the _type_child_slots. A fallback mechanism to check type table will be used.
* Recommendation: set to false for optimal runtime speed if we know exact number of children.
*
* Two macros are used to declare helper functions in the object:
* - Use TVM_FFI_DECLARE_OBJECT_INFO for object classes that can be sub-classed.
* - Use TVM_FFI_DECLARE_OBJECT_INFO_FINAL for object classes that cannot be sub-classed.
*
* New objects can be created using make_object function.
* Which will automatically populate the type_index and deleter of the object.
*/
class Object {
protected:
/*! \brief header field that is the common prefix of all objects */
TVMFFIObject header_;
public:
Object() {
header_.combined_ref_count = 0;
header_.type_index = 0;
header_.__padding = 0;
header_.__ensure_align = 0;
}
/*!
* Check if the object is an instance of TargetType.
* \tparam TargetType The target type to be checked.
* \return Whether the target type is true.
*/
template <typename TargetType>
bool IsInstance() const {
return details::IsObjectInstance<TargetType>(header_.type_index);
}
/*! \return The internal runtime type index of the object. */
int32_t type_index() const { return header_.type_index; }
/*!
* \return the type key of the object.
* \note this operation is expensive, can be used for error reporting.
*/
std::string GetTypeKey() const {
// the function checks that the info exists
const TypeInfo* type_info = TVMFFIGetTypeInfo(header_.type_index);
return std::string(type_info->type_key.data, type_info->type_key.size);
}
/*!
* \return A hash value of the return of GetTypeKey.
*/
uint64_t GetTypeKeyHash() const {
// the function checks that the info exists
const TypeInfo* type_info = TVMFFIGetTypeInfo(header_.type_index);
return type_info->type_key_hash;
}
/*!
* \brief Get the type key of the corresponding index from runtime.
* \param tindex The type index.
* \return the result.
*/
static std::string TypeIndex2Key(int32_t tindex) {
const TypeInfo* type_info = TVMFFIGetTypeInfo(tindex);
return std::string(type_info->type_key.data, type_info->type_key.size);
}
/*!
* \return Whether the object.use_count() == 1.
*/
bool unique() const { return use_count() == 1; }
/*!
* \return The usage count of the cell.
* \note We use STL style naming to be consistent with known API in shared_ptr.
*/
uint64_t use_count() const {
// only need relaxed load of counters
#ifdef _MSC_VER
return ((reinterpret_cast<const volatile uint64_t*>(
&header_.combined_ref_count))[0] // NOLINT(*)
) &
kCombinedRefCountMaskUInt32;
#else
return __atomic_load_n(&(header_.combined_ref_count), __ATOMIC_RELAXED) &
kCombinedRefCountMaskUInt32;
#endif
}
//----------------------------------------------------------------------------
// The following fields are configuration flags for subclasses of object
//----------------------------------------------------------------------------
/*! \brief The type key of the class */
static constexpr const char* _type_key = StaticTypeKey::kTVMFFIObject;
/*! \brief Whether the class is final */
static constexpr bool _type_final = false;
/*! \brief Whether allow mutable access to fields */
static constexpr bool _type_mutable = false;
/*! \brief The number of child slots of the class to pre-allocate to this type */
static constexpr uint32_t _type_child_slots = 0;
/*!
* \brief Whether allow additional children beyond pre-specified by _type_child_slots
*/
static constexpr bool _type_child_slots_can_overflow = true;
/*! \brief The static type index of the class */
static constexpr int32_t _type_index = TypeIndex::kTVMFFIObject;
/*! \brief The static depth of the class in the object hierarchy */
static constexpr int32_t _type_depth = 0;
/*! \brief The structural equality and hash kind of the type */
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindUnsupported;
// The following functions are provided by macro
// TVM_FFI_DECLARE_OBJECT_INFO and TVM_FFI_DECLARE_OBJECT_INFO_FINAL
/*!
* \brief Get the runtime allocated type index of the type
* \note Getting this information may need dynamic calls into a global table.
*/
static int32_t RuntimeTypeIndex() { return TypeIndex::kTVMFFIObject; }
/*!
* \brief Internal function to get or allocate a runtime index.
*/
static int32_t _GetOrAllocRuntimeTypeIndex() { // NOLINT(bugprone-reserved-identifier)
return TypeIndex::kTVMFFIObject;
}
private:
// exposing detailed constants to here
static constexpr uint64_t kCombinedRefCountMaskUInt32 = details::kCombinedRefCountMaskUInt32;
static constexpr uint64_t kCombinedRefCountStrongOne = details::kCombinedRefCountStrongOne;
static constexpr uint64_t kCombinedRefCountWeakOne = details::kCombinedRefCountWeakOne;
static constexpr uint64_t kCombinedRefCountBothOne = details::kCombinedRefCountBothOne;
/*! \brief increase strong reference count, the caller must already hold a strong reference */
void IncRef() {
#ifdef _MSC_VER
_InterlockedIncrement64(
reinterpret_cast<volatile __int64*>(&header_.combined_ref_count)); // NOLINT(*)
#else
__atomic_fetch_add(&(header_.combined_ref_count), 1, __ATOMIC_RELAXED);
#endif
}
/*!
* \brief Try to lock the object to increase the strong reference count,
* the caller must already hold a strong reference.
* \return whether the lock call is successful and object is still alive.
*/
bool TryPromoteWeakPtr() {
#ifdef _MSC_VER
uint64_t old_count =
(reinterpret_cast<const volatile __int64*>(&header_.combined_ref_count))[0]; // NOLINT(*)
while ((old_count & kCombinedRefCountMaskUInt32) != 0) {
uint64_t new_count = old_count + kCombinedRefCountStrongOne;
uint64_t old_count_loaded = _InterlockedCompareExchange64(
reinterpret_cast<volatile __int64*>(&header_.combined_ref_count), new_count, old_count);
if (old_count == old_count_loaded) {
return true;
}
old_count = old_count_loaded;
}
return false;
#else
uint64_t old_count = __atomic_load_n(&(header_.combined_ref_count), __ATOMIC_RELAXED);
while ((old_count & kCombinedRefCountMaskUInt32) != 0) {
// must do CAS to ensure that we are the only one that increases the reference count
// avoid condition when two threads tries to promote weak to strong at same time
// or when strong deletion happens between the load and the CAS
uint64_t new_count = old_count + kCombinedRefCountStrongOne;
if (__atomic_compare_exchange_n(&(header_.combined_ref_count), &old_count, new_count, true,
__ATOMIC_ACQ_REL, __ATOMIC_RELAXED)) {
return true;
}
}
return false;
#endif
}
/*! \brief increase weak reference count */
void IncWeakRef() {
#ifdef _MSC_VER
_InlineInterlockedAdd64(
reinterpret_cast<volatile __int64*>(&header_.combined_ref_count), // NOLINT(*)
kCombinedRefCountWeakOne);
#else
__atomic_fetch_add(&(header_.combined_ref_count), kCombinedRefCountWeakOne, __ATOMIC_RELAXED);
#endif
}
/*! \brief decrease strong reference count and delete the object */
void DecRef() {
#ifdef _MSC_VER
// use simpler impl in windows to ensure correctness
uint64_t count_before_sub =
_InterlockedDecrement64( //
reinterpret_cast<volatile __int64*>(&header_.combined_ref_count) // NOLINT(*)
) +
1;
if (count_before_sub == kCombinedRefCountBothOne) { // NOLINT(*)
// fast path: both reference counts will go to zero
if (header_.deleter != nullptr) {
// full barrrier is implicit in InterlockedDecrement
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskBoth);
}
} else if ((count_before_sub & kCombinedRefCountMaskUInt32) == kCombinedRefCountStrongOne) {
// strong reference count becomes zero, we need to first do strong deletion
// then decrease weak reference count
// full barrrier is implicit in InterlockedAdd
if (header_.deleter != nullptr) {
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskStrong);
}
// decrease weak reference count
if (_InlineInterlockedAdd64( //
reinterpret_cast<volatile __int64*>(&header_.combined_ref_count),
-kCombinedRefCountWeakOne) == 0) { // NOLINT(*)
if (header_.deleter != nullptr) {
// full barrrier is implicit in InterlockedAdd
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskWeak);
}
}
}
#else
// first do a release, note we only need to acquire for deleter
// optimization: we only need one atomic to tell the common case
// where both reference counts are zero
uint64_t count_before_sub = __atomic_fetch_sub(&(header_.combined_ref_count),
kCombinedRefCountStrongOne, __ATOMIC_RELEASE);
if (count_before_sub == kCombinedRefCountBothOne) {
// common case, we need to delete both the object and the memory block
// only acquire when we need to call deleter
__atomic_thread_fence(__ATOMIC_ACQUIRE);
if (header_.deleter != nullptr) {
// call deleter once
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskBoth);
}
} else if ((count_before_sub & kCombinedRefCountMaskUInt32) == kCombinedRefCountStrongOne) {
// strong count is already zero
// Slower path: there is still a weak reference left
__atomic_thread_fence(__ATOMIC_ACQUIRE);
// call destructor first, then decrease weak reference count
if (header_.deleter != nullptr) {
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskStrong);
}
// now decrease weak reference count
if (__atomic_fetch_sub(&(header_.combined_ref_count), kCombinedRefCountWeakOne,
__ATOMIC_RELEASE) == kCombinedRefCountWeakOne) {
__atomic_thread_fence(__ATOMIC_ACQUIRE);
if (header_.deleter != nullptr) {
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskWeak);
}
}
}
#endif
}
/*! \brief decrease weak reference count */
void DecWeakRef() {
#ifdef _MSC_VER
if (_InlineInterlockedAdd64( //
reinterpret_cast<volatile __int64*>(&header_.combined_ref_count), // NOLINT(*)
-kCombinedRefCountWeakOne) == 0) {
if (header_.deleter != nullptr) {
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskWeak);
}
}
#else
// now decrease weak reference count
if (__atomic_fetch_sub(&(header_.combined_ref_count), kCombinedRefCountWeakOne,
__ATOMIC_RELEASE) == kCombinedRefCountWeakOne) {
__atomic_thread_fence(__ATOMIC_ACQUIRE);
if (header_.deleter != nullptr) {
header_.deleter(&(this->header_), kTVMFFIObjectDeleterFlagBitMaskWeak);
}
}
#endif
}
// friend classes
template <typename>
friend class ObjectPtr;
template <typename>
friend class WeakObjectPtr;
friend struct tvm::ffi::details::ObjectUnsafe;
};
/*!
* \brief A custom smart pointer for Object.
* \tparam T the content data type.
* \sa make_object
*/
template <typename T>
class ObjectPtr {
public:
/*! \brief default constructor */
ObjectPtr() = default;
/*! \brief default constructor */
ObjectPtr(std::nullptr_t) {} // NOLINT(*)
/*!
* \brief copy constructor
* \param other The value to be moved
*/
ObjectPtr(const ObjectPtr<T>& other) // NOLINT(*)
: ObjectPtr(other.data_) {}
/*!
* \brief copy constructor
* \param other The value to be moved
*/
template <typename U>
ObjectPtr(const ObjectPtr<U>& other) // NOLINT(*)
: ObjectPtr(other.data_) {
static_assert(std::is_base_of_v<T, U>, "can only assign of child class ObjectPtr to parent");
}
/*!
* \brief move constructor
* \param other The value to be moved
*/
ObjectPtr(ObjectPtr<T>&& other) // NOLINT(*)
: data_(other.data_) {
other.data_ = nullptr;
}
/*!
* \brief move constructor
* \param other The value to be moved
*/
template <typename Y>
ObjectPtr(ObjectPtr<Y>&& other) // NOLINT(*)
: data_(other.data_) {
static_assert(std::is_base_of_v<T, Y>, "can only assign of child class ObjectPtr to parent");
other.data_ = nullptr;
}
/*! \brief destructor */
~ObjectPtr() { this->reset(); }
/*!
* \brief Swap this array with another Object
* \param other The other Object
*/
void swap(ObjectPtr<T>& other) { // NOLINT(*)
std::swap(data_, other.data_);
}
/*!
* \return Get the content of the pointer
*/
T* get() const { return static_cast<T*>(data_); }
/*!
* \return The pointer
*/
T* operator->() const { return get(); }
/*!
* \return The reference
*/
T& operator*() const { // NOLINT(*)
return *get();
}
/*!
* \brief copy assignment
* \param other The value to be assigned.
* \return reference to self.
*/
ObjectPtr<T>& operator=(const ObjectPtr<T>& other) { // NOLINT(*)
// takes in plane operator to enable copy elison.
// copy-and-swap idiom
ObjectPtr(other).swap(*this); // NOLINT(*)
return *this;
}
/*!
* \brief move assignment
* \param other The value to be assigned.
* \return reference to self.
*/
ObjectPtr<T>& operator=(ObjectPtr<T>&& other) { // NOLINT(*)
// copy-and-swap idiom
ObjectPtr(std::move(other)).swap(*this); // NOLINT(*)
return *this;
}
/*!
* \brief nullptr check
* \return result of comparison of internal pointer with nullptr.
*/
explicit operator bool() const { return get() != nullptr; }
/*! \brief reset the content of ptr to be nullptr */
void reset() {
if (data_ != nullptr) {
data_->DecRef();
data_ = nullptr;
}
}
/*! \return The use count of the ptr, for debug purposes */
int use_count() const { return data_ != nullptr ? data_->use_count() : 0; }
/*! \return whether the reference is unique */
bool unique() const { return data_ != nullptr && data_->use_count() == 1; }
/*! \return Whether two ObjectPtr do not equal each other */
bool operator==(const ObjectPtr<T>& other) const { return data_ == other.data_; }
/*! \return Whether two ObjectPtr equals each other */
bool operator!=(const ObjectPtr<T>& other) const { return data_ != other.data_; }
/*! \return Whether the pointer is nullptr */
bool operator==(std::nullptr_t) const { return data_ == nullptr; }
/*! \return Whether the pointer is not nullptr */
bool operator!=(std::nullptr_t) const { return data_ != nullptr; }
private:
/*! \brief internal pointer field */
Object* data_{nullptr};
/*!
* \brief constructor from Object
* \param data The data pointer
*/
explicit ObjectPtr(Object* data) : data_(data) {
if (data_ != nullptr) {
data_->IncRef();
}
}
// friend classes
friend class Object;
friend class ObjectRef;
friend struct ObjectPtrHash;
template <typename>
friend class ObjectPtr;
template <typename>
friend class WeakObjectPtr;
friend struct tvm::ffi::details::ObjectUnsafe;
};
/*!
* \brief A custom smart pointer for Object.
* \tparam T the content data type.
* \sa make_object
*/
template <typename T>
class WeakObjectPtr {
public:
/*! \brief default constructor */
WeakObjectPtr() = default;
/*! \brief default constructor */
WeakObjectPtr(std::nullptr_t) {} // NOLINT(*)
/*!
* \brief copy constructor
* \param other The value to be moved
*/
WeakObjectPtr(const WeakObjectPtr<T>& other) // NOLINT(*)
: WeakObjectPtr(other.data_) {}
/*!
* \brief copy constructor
* \param other The value to be moved
*/
WeakObjectPtr(const ObjectPtr<T>& other) // NOLINT(*)
: WeakObjectPtr(other.get()) {}
/*!
* \brief copy constructor
* \param other The value to be moved
*/
template <typename U>
WeakObjectPtr(const WeakObjectPtr<U>& other) // NOLINT(*)
: WeakObjectPtr(other.data_) {
static_assert(std::is_base_of_v<T, U>, "can only assign of child class ObjectPtr to parent");
}
/*!
* \brief copy constructor
* \param other The value to be moved
*/
template <typename U>
WeakObjectPtr(const ObjectPtr<U>& other) // NOLINT(*)
: WeakObjectPtr(other.data_) {
static_assert(std::is_base_of_v<T, U>, "can only assign of child class ObjectPtr to parent");
}
/*!
* \brief move constructor
* \param other The value to be moved
*/
WeakObjectPtr(WeakObjectPtr<T>&& other) // NOLINT(*)
: data_(other.data_) {
other.data_ = nullptr;
}
/*!
* \brief move constructor
* \param other The value to be moved
*/
template <typename Y>
WeakObjectPtr(WeakObjectPtr<Y>&& other) // NOLINT(*)
: data_(other.data_) {
static_assert(std::is_base_of_v<T, Y>, "can only assign of child class ObjectPtr to parent");
other.data_ = nullptr;
}
/*! \brief destructor */
~WeakObjectPtr() { this->reset(); }
/*!
* \brief Swap this array with another Object
* \param other The other Object
*/
void swap(WeakObjectPtr<T>& other) { // NOLINT(*)
std::swap(data_, other.data_);
}
/*!
* \brief copy assignment
* \param other The value to be assigned.
* \return reference to self.
*/
WeakObjectPtr<T>& operator=(const WeakObjectPtr<T>& other) { // NOLINT(*)
// takes in plane operator to enable copy elison.
// copy-and-swap idiom
WeakObjectPtr(other).swap(*this); // NOLINT(*)
return *this;
}
/*!
* \brief move assignment
* \param other The value to be assigned.
* \return reference to self.
*/
WeakObjectPtr<T>& operator=(WeakObjectPtr<T>&& other) { // NOLINT(*)
// copy-and-swap idiom
WeakObjectPtr(std::move(other)).swap(*this); // NOLINT(*)
return *this;
}
/*! \return The internal object pointer if the object is still alive, otherwise nullptr */
ObjectPtr<T> lock() const {
if (data_ != nullptr && data_->TryPromoteWeakPtr()) {
ObjectPtr<T> ret;
// we already increase the reference count, so we don't need to do it again
ret.data_ = data_;
return ret;
}
return nullptr;
}
/*! \brief reset the content of ptr to be nullptr */
void reset() {
if (data_ != nullptr) {
data_->DecWeakRef();
data_ = nullptr;
}
}
/*! \return The use count of the ptr, for debug purposes */
int use_count() const { return data_ != nullptr ? data_->use_count() : 0; }
/*! \return whether the pointer is nullptr */
bool expired() const { return data_ == nullptr || data_->use_count() == 0; }
private:
/*! \brief internal pointer field */
Object* data_{nullptr};
/*!
* \brief constructor from Object
* \param data The data pointer
*/
explicit WeakObjectPtr(Object* data) : data_(data) {
if (data_ != nullptr) {
data_->IncWeakRef();
}
}
template <typename>
friend class WeakObjectPtr;
friend struct tvm::ffi::details::ObjectUnsafe;
};
/*!
* \brief Optional data type in FFI.
* \tparam T The underlying type of the optional.
*
* \note Compared to std::optional, Optional<ObjectRef>
* akes less storage as it used nullptr to represent nullopt.
*/
template <typename T, typename = void>
class Optional;
/*! \brief Base class of all object reference */
class ObjectRef {
public:
/*! \brief default constructor */
ObjectRef() = default;
/*! \brief copy constructor */
ObjectRef(const ObjectRef& other) = default;
/*! \brief move constructor */
ObjectRef(ObjectRef&& other) noexcept : data_(std::move(other.data_)) { other.data_ = nullptr; }
/*! \brief copy assignment */
ObjectRef& operator=(const ObjectRef& other) = default;
/*! \brief move assignment */
ObjectRef& operator=(ObjectRef&& other) noexcept {
data_ = std::move(other.data_);
other.data_ = nullptr;
return *this;
}
/*! \brief Constructor from existing object ptr */
explicit ObjectRef(ObjectPtr<Object> data) : data_(std::move(data)) {}
/*! \brief Constructor from UnsafeInit */
explicit ObjectRef(UnsafeInit) : data_(nullptr) {}
/*!
* \brief Comparator
* \param other Another object ref.
* \return the compare result.
*/
bool same_as(const ObjectRef& other) const { return data_ == other.data_; }
/*!
* \brief Comparator
* \param other Another object ref.
* \return the compare result.
*/
bool operator==(const ObjectRef& other) const { return data_ == other.data_; }
/*!
* \brief Comparator
* \param other Another object ref.
* \return the compare result.
*/
bool operator!=(const ObjectRef& other) const { return data_ != other.data_; }
/*!
* \brief Comparator
* \param other Another object ref by address.
* \return the compare result.
*/
bool operator<(const ObjectRef& other) const { return data_.get() < other.data_.get(); }
/*!
* \return whether the object is defined.
*/
bool defined() const { return data_ != nullptr; }
/*! \return the internal object pointer */
const Object* get() const { return data_.get(); }
/*! \return the internal object pointer */
const Object* operator->() const { return get(); }
/*! \return whether the reference is unique */
bool unique() const { return data_.unique(); }
/*! \return The use count of the ptr, for debug purposes */
int use_count() const { return data_.use_count(); }
/*!
* \brief Try to downcast the internal Object to a
* raw pointer of a corresponding type.
*
* The function will return a nullptr if the cast failed.
*
* if (const AddNode *ptr = node_ref.as<AddNode>()) {
* // This is an add node
* }
*
* \tparam ObjectType the target type, must be a subtype of Object
* \return The pointer to the requested type.
*/
template <typename ObjectType, typename = std::enable_if_t<std::is_base_of_v<Object, ObjectType>>>
TVM_FFI_INLINE const ObjectType* as() const {
if (data_ != nullptr) {
if (data_->IsInstance<ObjectType>()) {
return static_cast<ObjectType*>(data_.get());
}
}
return nullptr;
}
/*!
* \brief Try to downcast the ObjectRef to Optional<T> of the requested type.
*
* The function will return a std::nullopt if the cast or if the pointer is nullptr.
*
* \tparam ObjectRefType the target type, must be a subtype of ObjectRef'
* \return The optional value of the requested type.
*/
template <typename ObjectRefType,
typename = std::enable_if_t<std::is_base_of_v<ObjectRef, ObjectRefType>>>
TVM_FFI_INLINE std::optional<ObjectRefType> as() const {
if (data_ != nullptr) {
if (data_->IsInstance<typename ObjectRefType::ContainerType>()) {
ObjectRefType ref(UnsafeInit{});
ref.data_ = data_;
return ref;
} else {
return std::nullopt;
}
} else {
return std::nullopt;
}
}
/*!
* \brief Get the type index of the ObjectRef
* \return The type index of the ObjectRef
*/
int32_t type_index() const {
return data_ != nullptr ? data_->type_index() : TypeIndex::kTVMFFINone;
}
/*!
* \brief Get the type key of the ObjectRef
* \return The type key of the ObjectRef
*/
std::string GetTypeKey() const {
return data_ != nullptr ? data_->GetTypeKey() : StaticTypeKey::kTVMFFINone;
}
/*! \brief type indicate the container type. */
using ContainerType = Object;
/*! \brief Whether the reference can point to nullptr */
static constexpr bool _type_is_nullable = true;
protected:
/*! \brief Internal pointer that backs the reference. */
ObjectPtr<Object> data_;
/*! \return return a mutable internal ptr, can be used by sub-classes. */
Object* get_mutable() const { return data_.get(); }
// friend classes.
friend struct ObjectPtrHash;
friend struct tvm::ffi::details::ObjectUnsafe;
};
// forward delcare variant
template <typename... V>
class Variant;
/*! \brief ObjectRef hash functor */
struct ObjectPtrHash {
size_t operator()(const ObjectRef& a) const { return operator()(a.data_); }
template <typename T>
size_t operator()(const ObjectPtr<T>& a) const {
return std::hash<Object*>()(a.get());
}
template <typename... V>
TVM_FFI_INLINE size_t operator()(const Variant<V...>& a) const;
};
/*! \brief ObjectRef equal functor */
struct ObjectPtrEqual {
bool operator()(const ObjectRef& a, const ObjectRef& b) const { return a.same_as(b); }
template <typename T>
bool operator()(const ObjectPtr<T>& a, const ObjectPtr<T>& b) const {
return a == b;
}
template <typename... V>
TVM_FFI_INLINE bool operator()(const Variant<V...>& a, const Variant<V...>& b) const;
};
/*!
* \brief Helper macro to declare object information with static type index.
*
* For each custom object, you need to call tvm::ffi::reflection::ObjectDef<TypeName>()
* once in your cc file to register the type index with the runtime.
* Alternatively, you can call TypeName::_GetOrAllocRuntimeTypeIndex() once.
*
* \param TypeKey The type key of the current type.
* \param TypeName The name of the current type.
* \param ParentType The name of the ParentType
*
* \see tvm::ffi::reflection::ObjectDef
*/
#define TVM_FFI_DECLARE_OBJECT_INFO_STATIC(TypeKey, TypeName, ParentType) \
static constexpr int32_t _type_depth = ParentType::_type_depth + 1; \
static int32_t _GetOrAllocRuntimeTypeIndex() { \
static_assert(!ParentType::_type_final, "ParentType marked as final"); \
static_assert(TypeName::_type_child_slots == 0 || ParentType::_type_child_slots == 0 || \
TypeName::_type_child_slots < ParentType::_type_child_slots, \
"Need to set _type_child_slots when parent specifies it."); \
TVMFFIByteArray type_key{TypeName::_type_key, \
std::char_traits<char>::length(TypeName::_type_key)}; \
static int32_t tindex [[maybe_unused]] = TVMFFITypeGetOrAllocIndex( \
&type_key, TypeName::_type_index, TypeName::_type_depth, TypeName::_type_child_slots, \
TypeName::_type_child_slots_can_overflow, ParentType::_GetOrAllocRuntimeTypeIndex()); \
return TypeName::_type_index; \
} \
static int32_t RuntimeTypeIndex() { return TypeName::_type_index; } \
static constexpr const char* _type_key = TypeKey
/*!
* \brief Helper macro to declare object information with type key already defined in class.
*
* \param TypeName The name of the current type.
* \param ParentType The name of the ParentType
*/
#define TVM_FFI_DECLARE_OBJECT_INFO_PREDEFINED_TYPE_KEY(TypeName, ParentType) \
static constexpr int32_t _type_depth = ParentType::_type_depth + 1; \
static int32_t _GetOrAllocRuntimeTypeIndex() { \
static_assert(!ParentType::_type_final, "ParentType marked as final"); \
static_assert(TypeName::_type_child_slots == 0 || ParentType::_type_child_slots == 0 || \
TypeName::_type_child_slots < ParentType::_type_child_slots, \
"Need to set _type_child_slots when parent specifies it."); \
TVMFFIByteArray type_key{TypeName::_type_key, \
std::char_traits<char>::length(TypeName::_type_key)}; \
static int32_t tindex = TVMFFITypeGetOrAllocIndex( \
&type_key, -1, TypeName::_type_depth, TypeName::_type_child_slots, \
TypeName::_type_child_slots_can_overflow, ParentType::_GetOrAllocRuntimeTypeIndex()); \
return tindex; \
} \
static int32_t RuntimeTypeIndex() { return _GetOrAllocRuntimeTypeIndex(); }
/*!
* \brief Helper macro to declare object information with dynamic type index.
*
* For each custom object, you need to call tvm::ffi::reflection::ObjectDef<TypeName>()
* once in your cc file to register the type index with the runtime.
* Alternatively, you can call TypeName::_GetOrAllocRuntimeTypeIndex() once.
*
* \param TypeKey The type key of the current type.
* \param TypeName The name of the current type.
* \param ParentType The name of the ParentType
* \sa tvm::ffi::reflection::ObjectDef
*/
#define TVM_FFI_DECLARE_OBJECT_INFO(TypeKey, TypeName, ParentType) \
static constexpr const char* _type_key = TypeKey; \
TVM_FFI_DECLARE_OBJECT_INFO_PREDEFINED_TYPE_KEY(TypeName, ParentType)
/*!
* \brief Helper macro to declare object information with dynamic type index and is final.
*
* For each custom object, you need to call tvm::ffi::reflection::ObjectDef<TypeName>()
* once in your cc file to register the type index with the runtime.
* Alternatively, you can call TypeName::_GetOrAllocRuntimeTypeIndex() once.
*
* \param TypeKey The type key of the current type.
* \param TypeName The name of the current type.
* \param ParentType The name of the ParentType
* \sa tvm::ffi::reflection::ObjectDef
*/
#define TVM_FFI_DECLARE_OBJECT_INFO_FINAL(TypeKey, TypeName, ParentType) \
static const constexpr int _type_child_slots [[maybe_unused]] = 0; \
static const constexpr bool _type_final [[maybe_unused]] = true; \
TVM_FFI_DECLARE_OBJECT_INFO(TypeKey, TypeName, ParentType)
/*!
* \brief Define object reference methods.
*
* \param TypeName The object type name
* \param ParentType The parent type of the objectref
* \param ObjectName The type name of the object.
*
* \note This macro also defines the default constructor that puts the ObjectRef
* in undefined state initially.
*/
#define TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TypeName, ParentType, ObjectName) \
TypeName() = default; \
explicit TypeName(::tvm::ffi::ObjectPtr<ObjectName> n) : ParentType(std::move(n)) {} \
explicit TypeName(::tvm::ffi::UnsafeInit tag) : ParentType(tag) {} \
TVM_FFI_DEFINE_DEFAULT_COPY_MOVE_AND_ASSIGN(TypeName) \
using __PtrType = std::conditional_t<(ObjectName::_type_mutable), \
ObjectName*, /* NOLINT(bugprone-macro-parentheses) */ \
const ObjectName*>; \
__PtrType operator->() const { return static_cast<__PtrType>(data_.get()); } \
__PtrType get() const { return static_cast<__PtrType>(data_.get()); } \
[[maybe_unused]] static constexpr bool _type_is_nullable = true; \
using ContainerType = ObjectName
/*!
* \brief Define object reference methods do not have undefined state.
*
* \param TypeName The object type name
* \param ParentType The parent type of the objectref
* \param ObjectName The type name of the object.
*/
#define TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TypeName, ParentType, ObjectName) \
explicit TypeName(::tvm::ffi::UnsafeInit tag) : ParentType(tag) {} \
TVM_FFI_DEFINE_DEFAULT_COPY_MOVE_AND_ASSIGN(TypeName) \
using __PtrType = std::conditional_t<(ObjectName::_type_mutable), \
ObjectName*, /* NOLINT(bugprone-macro-parentheses) */ \
const ObjectName*>; \
__PtrType operator->() const { return static_cast<__PtrType>(data_.get()); } \
__PtrType get() const { return static_cast<__PtrType>(data_.get()); } \
[[maybe_unused]] static constexpr bool _type_is_nullable = false; \
using ContainerType = ObjectName
namespace details {
template <typename TargetType>
TVM_FFI_INLINE bool IsObjectInstance(int32_t object_type_index) {
static_assert(std::is_base_of_v<Object, TargetType>);
// Everything is a subclass of object.
if constexpr (std::is_same_v<TargetType, Object>) {
return true;
} else if constexpr (TargetType::_type_final) {
// if the target type is a final type
// then we only need to check the equivalence.
return object_type_index == TargetType::RuntimeTypeIndex();
} else {
// Explicitly enclose in else to eliminate this branch early in compilation.
// if target type is a non-leaf type
// Check if type index falls into the range of reserved slots.
int32_t target_type_index = TargetType::RuntimeTypeIndex();
int32_t begin = target_type_index;
// The condition will be optimized by constant-folding.
if constexpr (TargetType::_type_child_slots != 0) {
// total_slots = child_slots + 1 (including self)
int32_t end = begin + TargetType::_type_child_slots + 1;
if (object_type_index >= begin && object_type_index < end) return true;
} else {
if (object_type_index == begin) return true;
}
if constexpr (TargetType::_type_child_slots_can_overflow) {
// Invariance: parent index is always smaller than the child.
if (object_type_index < target_type_index) return false;
// Do a runtime lookup of type information
// the function checks that the info exists
const TypeInfo* type_info = TVMFFIGetTypeInfo(object_type_index);
return (type_info->type_depth > TargetType::_type_depth &&
type_info->type_ancestors[TargetType::_type_depth]->type_index == target_type_index);
} else {
return false;
}
}
}
/*!
* \brief Namespace to internally manipulate object class.
* \note These functions are only supposed to be used by internal
* implementations and not external users of the tvm::ffi
*/
struct ObjectUnsafe {
// NOTE: get ffi header from an object
TVM_FFI_INLINE static TVMFFIObject* GetHeader(const Object* src) {
return const_cast<TVMFFIObject*>(&(src->header_));
}
// Suppress -Winvalid-offsetof: we intentionally use offsetof on non-standard-layout types
// to avoid undefined behavior from null pointer arithmetic that sanitizers flag.
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
#endif
template <typename Class>
TVM_FFI_INLINE static int64_t GetObjectOffsetToSubclass() {
return static_cast<int64_t>(__builtin_offsetof(Class, header_)) -
static_cast<int64_t>(__builtin_offsetof(Object, header_));
}
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
template <typename T>
TVM_FFI_INLINE static T ObjectRefFromObjectPtr(const ObjectPtr<Object>& ptr) {
T ref(UnsafeInit{});
ref.data_ = ptr;
return ref;
}
template <typename T>
TVM_FFI_INLINE static T ObjectRefFromObjectPtr(ObjectPtr<Object>&& ptr) {
T ref(UnsafeInit{});
ref.data_ = std::move(ptr);
return ref;
}
template <typename T>
TVM_FFI_INLINE static ObjectPtr<T> ObjectPtrFromObjectRef(const ObjectRef& ref) {
if constexpr (std::is_same_v<T, Object>) {
return ref.data_;
} else {
return tvm::ffi::ObjectPtr<T>(ref.data_.data_);
}
}
template <typename T>
TVM_FFI_INLINE static ObjectPtr<T> ObjectPtrFromObjectRef(ObjectRef&& ref) {
if constexpr (std::is_same_v<T, Object>) {
return std::move(ref.data_);
} else {
ObjectPtr<T> result;
result.data_ = std::move(ref.data_.data_);
ref.data_.data_ = nullptr;
return result;
}
}
template <typename T>
TVM_FFI_INLINE static ObjectPtr<T> ObjectPtrFromOwned(Object* raw_ptr) {
tvm::ffi::ObjectPtr<T> ptr;
ptr.data_ = raw_ptr;
return ptr;
}
template <typename T>
TVM_FFI_INLINE static ObjectPtr<T> ObjectPtrFromOwned(TVMFFIObject* obj_ptr) {
return ObjectPtrFromOwned<T>(reinterpret_cast<Object*>(obj_ptr));
}
template <typename T>
TVM_FFI_INLINE static T* RawObjectPtrFromUnowned(TVMFFIObject* obj_ptr) {
// NOTE: this is important to first cast to Object*
// then cast back to T* because objptr and tptr may not be the same
// depending on how sub-class allocates the space.
return static_cast<T*>(reinterpret_cast<Object*>(obj_ptr));
}
// Create ObjectPtr from unowned ptr
template <typename T>
TVM_FFI_INLINE static ObjectPtr<T> ObjectPtrFromUnowned(Object* raw_ptr) {
return tvm::ffi::ObjectPtr<T>(raw_ptr);
}
template <typename T>
TVM_FFI_INLINE static ObjectPtr<T> ObjectPtrFromUnowned(TVMFFIObject* obj_ptr) {
return tvm::ffi::ObjectPtr<T>(reinterpret_cast<Object*>(obj_ptr));
}
TVM_FFI_INLINE static void DecRefObjectHandle(TVMFFIObjectHandle handle) {
if (handle) reinterpret_cast<Object*>(handle)->DecRef();
}
TVM_FFI_INLINE static void IncRefObjectHandle(TVMFFIObjectHandle handle) {
reinterpret_cast<Object*>(handle)->IncRef();
}
TVM_FFI_INLINE static Object* RawObjectPtrFromObjectRef(const ObjectRef& src) {
return src.data_.data_;
}
TVM_FFI_INLINE static TVMFFIObject* TVMFFIObjectPtrFromObjectRef(const ObjectRef& src) {
return GetHeader(src.data_.data_);
}
template <typename T>
TVM_FFI_INLINE static TVMFFIObject* TVMFFIObjectPtrFromObjectPtr(const ObjectPtr<T>& src) {
return GetHeader(src.data_);
}
template <typename T>
TVM_FFI_INLINE static TVMFFIObject* MoveObjectPtrToTVMFFIObjectPtr(ObjectPtr<T>&& src) {
Object* obj_ptr = src.data_;
src.data_ = nullptr;
return GetHeader(obj_ptr);
}
TVM_FFI_INLINE static TVMFFIObject* MoveObjectRefToTVMFFIObjectPtr(ObjectRef&& src) {
Object* obj_ptr = src.data_.data_;
src.data_.data_ = nullptr;
return GetHeader(obj_ptr);
}
};
} // namespace details
} // namespace ffi
} // namespace tvm
#endif // TVM_FFI_OBJECT_H_