blob: ac5fe02a629fa13ad35540a3001ccb3c2e4944e1 [file]
// Copyright 2019 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef IREE_VM_REF_CC_H_
#define IREE_VM_REF_CC_H_
#include <atomic>
#include <memory>
#include <utility>
#include "iree/base/api.h"
#include "iree/base/attributes.h"
#include "iree/vm/ref.h"
#ifndef __cplusplus
#error "This header is meant for use with C++ implementations."
#endif // __cplusplus
namespace iree {
namespace vm {
//===----------------------------------------------------------------------===//
// iree::vm::RefObject C++ base type equivalent of iree_vm_ref_t
//===----------------------------------------------------------------------===//
// TODO(benvanik): make this automatic for most types, or use type lookup.
// This could be done with SFINAE to detect iree_vm_ref_object_t or RefObject
// types. We may still need the iree_vm_ref_type_t exposed but that's relatively
// simple compared to getting the typed retain/release functions.
// Users may override this with their custom types to allow the packing code to
// access their registered type ID at runtime.
template <typename T>
IREE_ATTRIBUTE_ALWAYS_INLINE void ref_type_retain(T* p) {
iree_vm_ref_object_retain(p, ref_type_descriptor<T>::get());
}
template <typename T>
IREE_ATTRIBUTE_ALWAYS_INLINE void ref_type_release(T* p) {
iree_vm_ref_object_release(p, ref_type_descriptor<T>::get());
}
// Base class for reference counted objects.
// Reference counted objects should be used with the iree::vm::ref<T> pointer
// type. As reference counting can be tricky always prefer to use unique_ptr and
// avoid this type. Only use this when unique_ptr is not possible, such as
// when round-tripping objects through marshaling boundaries (v8/Java) or
// any objects that may have their lifetime tied to a garbage collected
// object.
//
// Subclasses should protect their dtor so that reference counting must
// be used.
//
// This is designed to avoid the need for extra vtable space or for adding
// methods to the vtable of subclasses. This differs from the boost Pointable
// version of this object.
// Inspiration for this comes from Peter Weinert's Dr. Dobb's article:
// http://www.drdobbs.com/cpp/a-base-class-for-intrusively-reference-c/229218807
//
// RefObjects are thread safe and may be used with iree::vm::ref<T>s from
// multiple threads.
//
// Subclasses may implement a custom Delete operator to handle their
// deallocation. It should be thread safe as it may be called from any thread.
//
// Usage:
// class MyRefObject : public RefObject<MyRefObject> {
// public:
// MyRefObject() = default;
// // Optional; can be used to return to pool/etc - must be public:
// static void Delete(MyRefObject* ptr) {
// ::operator delete(ptr);
// }
// };
template <class T>
class RefObject {
static_assert(!std::is_array<T>::value, "T must not be an array");
// value is true if a static Delete(T*) function is present.
struct has_custom_deleter {
template <typename C>
static auto Test(C* p) -> decltype(C::Delete(nullptr), std::true_type());
template <typename>
static std::false_type Test(...);
static constexpr bool value =
std::is_same<std::true_type, decltype(Test<T>(nullptr))>::value;
};
template <typename V, bool has_custom_deleter>
struct delete_thunk {
static void Delete(V* p) {
auto ref_obj = static_cast<RefObject<V>*>(p);
int previous_count = ref_obj->counter_.fetch_sub(1);
if (previous_count == 1) {
// We delete type T pointer here to avoid the need for a virtual dtor.
V::Delete(p);
}
}
static void Destroy(V* p) { V::Delete(p); }
};
template <typename V>
struct delete_thunk<V, false> {
static void Delete(V* p) {
auto ref_obj = static_cast<RefObject<V>*>(p);
int previous_count = ref_obj->counter_.fetch_sub(1);
if (previous_count == 1) {
// We delete type T pointer here to avoid the need for a virtual dtor.
delete p;
}
}
static void Destroy(V* p) { delete p; }
};
public:
// Adds a reference; used by ref_ptr.
friend void ref_ptr_add_ref(T* p) {
auto ref_obj = static_cast<RefObject*>(p);
++ref_obj->counter_;
}
// Releases a reference, potentially deleting the object; used by ref_ptr.
friend void ref_ptr_release_ref(T* p) {
delete_thunk<T, has_custom_deleter::value>::Delete(p);
}
// Deletes the object (precondition: ref count is zero).
friend void ref_ptr_destroy_ref(T* p) {
delete_thunk<T, has_custom_deleter::value>::Destroy(p);
}
// Deletes the object (precondition: ref count is zero).
static void DirectDestroy(void* p) {
ref_ptr_destroy_ref(reinterpret_cast<T*>(p));
}
// Adds a reference.
// ref_ptr should be used instead of this in most cases. This is required
// for when interoperating with marshaling APIs.
void AddReference() { ref_ptr_add_ref(static_cast<T*>(this)); }
// Releases a reference, potentially deleting the object.
// ref_ptr should be used instead of this in most cases. This is required
// for when interoperating with marshaling APIs.
void ReleaseReference() { ref_ptr_release_ref(static_cast<T*>(this)); }
// Returns the offset of the reference counter field from the start of the
// type T.
//
// This is generally unsafe to use and is here for support of the
// iree_vm_ref_t glue that allows RefObject-derived types to be round-tripped
// through the VM.
//
// For simple POD types or non-virtual classes we expect this to return 0.
// If the type has virtual methods (dtors/etc) then it should be 4 or 8
// (depending on pointer width). It may be other things, and instead of too
// much crazy magic we just rely on offsetof doing the right thing here.
static constexpr size_t offsetof_counter() { return offsetof(T, counter_); }
protected:
RefObject() { ref_ptr_add_ref(static_cast<T*>(this)); }
RefObject(const RefObject&) = default;
RefObject& operator=(const RefObject&) { return *this; }
// TODO(benvanik): replace this with just iree_vm_ref_object_t.
// That would allow us to remove a lot of these methods and reuse the C ones.
std::atomic<int32_t> counter_{0};
};
//===----------------------------------------------------------------------===//
// iree::vm::ref<T> RAII equivalent of iree_vm_ref_t
//===----------------------------------------------------------------------===//
// Reference counted pointer container wrapping iree_vm_ref_t.
// This is modeled on boost::instrusive_ptr in that it requires no
// extra storage over the pointer type and should compile to almost
// no additional code. It also allows us to round-trip object pointers
// through regular pointers, which is critical when having to round-trip
// them through JNI/etc where we can't use things like unique_ptr/shared_ptr.
//
// The ref wrapper calls the iree_vm_ref_* functions and uses the
// iree_vm_ref_type_descriptor_t registered for the type T to manipulate the
// reference counter and, when needed, destroy the object using
// iree_vm_ref_destroy_t. Any iree_vm_ref_t can be used interchangably with
// ref<T> when RAII is needed.
//
// Example:
// ref<Foo> p1(new Foo()); // ref count 1
// ref<Foo> p2(p1); // ref count 2
// p1.reset(); // ref count 1
// p2.reset(); // ref count 0, deleted
//
// When round-tripping the pointer through external APIs, use release():
// ref<Foo> p1(new Foo()); // ref count 1
// Foo* raw_p = p1.release(); // ref count 1
// // pass to API
// ref<Foo> p2(raw_p); // ref count 1 (don't add ref)
// p2.reset(); // ref count 0, deleted
//
// See the boost intrusive_ptr docs for details of behavior:
// http://www.boost.org/doc/libs/1_55_0/libs/smart_ptr/intrusive_ptr.html
//
// The retain_ref and assign_ref helpers can be used to make it easier to
// declare and use ref types:
// ref<Foo> p = assign_ref(new Foo()); // ref count 1
// PassRefWithRetain(retain_ref(p));
// PassRefWithMove(std::move(p)); // ala unique_ptr/shared_ptr
//
// ref manages the target objects in a thread-safe way, though you'll want
// to take care with objects that may have pinned threads for deallocation. If
// you release the last reference to an object on a thread other than what it
// was expecting you're gonna have a bad time.
//
// Compatible only with types that implement the following methods:
// ref_type_retain(T*)
// ref_type_release(T*)
// ref_type_descriptor<T>::get()
//
// If you get link errors pertaining to ref_type_descriptor then ensure that you
// have included the header file containing the IREE_VM_DECLARE_TYPE_ADAPTERS
// for the given type.
//
// TODO(benvanik): reconcile RefObject, iree_vm_ref_t, and this.
template <typename T>
class ref {
private:
typedef ref this_type;
typedef T* this_type::*unspecified_bool_type;
public:
IREE_ATTRIBUTE_ALWAYS_INLINE iree_vm_ref_type_t type() const noexcept {
return ref_type_descriptor<T>::get()->type;
}
IREE_ATTRIBUTE_ALWAYS_INLINE ref() noexcept
: ref_({
0,
ref_type_descriptor<T>::get()->offsetof_counter,
ref_type_descriptor<T>::get()->type,
}) {}
IREE_ATTRIBUTE_ALWAYS_INLINE ref(std::nullptr_t) noexcept // NOLINT
: ref_({
0,
ref_type_descriptor<T>::get()->offsetof_counter,
ref_type_descriptor<T>::get()->type,
}) {}
IREE_ATTRIBUTE_ALWAYS_INLINE ref(T* p) noexcept // NOLINT
: ref_({
p,
ref_type_descriptor<T>::get()->offsetof_counter,
ref_type_descriptor<T>::get()->type,
}) {}
IREE_ATTRIBUTE_ALWAYS_INLINE ~ref() noexcept { ref_type_release<T>(get()); }
// Don't use implicit ref copying; use retain_ref instead to make things more
// readable. We can't delete the ctor (or, I couldn't find a way not to)
// because the templated parameter packing magic needs it.
ref(const ref& rhs) noexcept : ref_(rhs.ref_) { ref_type_retain<T>(get()); }
ref& operator=(const ref&) noexcept = delete;
// Move support to transfer ownership from one ref to another.
ref(ref&& rhs) noexcept : ref_(rhs.ref_) { rhs.release(); }
ref& operator=(ref&& rhs) noexcept {
if (get() != rhs.get()) {
ref_type_release<T>(get());
ref_ = rhs.ref_;
rhs.release();
}
return *this;
}
// Move support from another compatible type.
template <typename U>
ref(ref<U>&& rhs) noexcept { // NOLINT
ref_.ptr = static_cast<T*>(rhs.release());
ref_.offsetof_counter = rhs.ref_.offsetof_counter;
ref_.type = rhs.ref_.type;
}
template <typename U>
ref& operator=(ref<U>&& rhs) noexcept {
if (get() != rhs.get()) {
ref_type_release<T>(get());
ref_.ptr = static_cast<T*>(rhs.release());
}
return *this;
}
// Resets the object to nullptr and decrements the reference count, possibly
// deleting it.
void reset() noexcept {
ref_type_release<T>(get());
ref_.ptr = nullptr;
}
// Releases a pointer.
// Returns the current pointer held by this object without having
// its reference count decremented and resets the ref to empty.
// Returns nullptr if the ref holds no value.
// To re-wrap in a ref use either ref<T>(value) or assign().
IREE_ATTRIBUTE_ALWAYS_INLINE T* release() noexcept {
T* p = get();
ref_.ptr = nullptr;
return p;
}
// Assigns a pointer.
// The pointer will be accepted by the ref and its reference count will
// not be incremented.
IREE_ATTRIBUTE_ALWAYS_INLINE void assign(T* value) noexcept {
reset();
ref_.ptr = value;
}
// Gets the pointer referenced by this instance.
// operator* and operator-> will assert() if there is no current object.
constexpr T* get() const noexcept { return reinterpret_cast<T*>(ref_.ptr); }
constexpr T& operator*() const noexcept { return *get(); }
constexpr T* operator->() const noexcept { return get(); }
// Returns a pointer to the inner pointer storage.
// This allows passing a pointer to the ref as an output argument to C-style
// creation functions.
constexpr T** operator&() noexcept { // NOLINT
return reinterpret_cast<T**>(&ref_.ptr);
}
// Support boolean expression evaluation ala unique_ptr/shared_ptr:
// https://en.cppreference.com/w/cpp/memory/shared_ptr/operator_bool
constexpr operator unspecified_bool_type() const noexcept { // NOLINT
return get() ? reinterpret_cast<unspecified_bool_type>(&this_type::ref_.ptr)
: nullptr;
}
// Supports unary expression evaluation.
constexpr bool operator!() const noexcept { return !get(); }
// Swap support.
void swap(ref& rhs) { std::swap(ref_.ptr, rhs.ref_.ptr); }
// Allows directly passing the ref to a C-API function for creation.
// Example:
// iree::vm::ref<my_type_t> value;
// my_type_create(..., &value);
constexpr operator iree_vm_ref_t*() const noexcept { // NOLINT
return &ref_;
}
private:
mutable iree_vm_ref_t ref_;
};
// Adds a reference to the given ref and returns the same ref.
//
// Usage:
// ref<MyType> a = AcquireRefFromSomewhere();
// ref<MyType> b = retain_ref(a); // ref count + 1
// retain_ref(b); // ref count + 1
template <typename T>
inline ref<T> retain_ref(const ref<T>& value) {
ref_type_retain<T>(value.get());
return ref<T>(value.get());
}
// Adds a reference to the given raw pointer and returns it wrapped in a ref.
//
// Usage:
// MyType* raw_ptr = AcquirePointerFromSomewhere();
// ref<MyType> p = retain_ref(raw_ptr); // ref count + 1
template <typename T>
inline ref<T> retain_ref(T* value) {
ref_type_retain<T>(value);
return ref<T>(value);
}
// Assigns a raw pointer to a ref without adding a reference.
//
// Usage:
// ref<MyType> p = assign_ref(new MyType()); // ref count untouched
template <typename T>
inline ref<T> assign_ref(T* value) {
return ref<T>(value);
}
template <class T, class U>
inline bool operator==(ref<T> const& a, ref<U> const& b) {
return a.get() == b.get();
}
template <class T, class U>
inline bool operator!=(ref<T> const& a, ref<U> const& b) {
return a.get() != b.get();
}
template <class T, class U>
inline bool operator==(ref<T> const& a, U* b) {
return a.get() == b;
}
template <class T, class U>
inline bool operator!=(ref<T> const& a, U* b) {
return a.get() != b;
}
template <class T, class U>
inline bool operator==(T* a, ref<U> const& b) {
return a == b.get();
}
template <class T, class U>
inline bool operator!=(T* a, ref<U> const& b) {
return a != b.get();
}
template <class T>
inline bool operator<(ref<T> const& a, ref<T> const& b) {
return a.get() < b.get();
}
// Swaps the pointers of two refs.
template <class T>
void swap(ref<T>& lhs, ref<T>& rhs) {
lhs.swap(rhs);
}
//===----------------------------------------------------------------------===//
// iree::opaque_ref utility for type-erased ref values
//===----------------------------------------------------------------------===//
// An opaque reference that does not make any assertions about the type of the
// ref contained. This can be used to accept arbitrary ref objects that are then
// dynamically handled based on type.
class opaque_ref {
public:
opaque_ref() = default;
opaque_ref(const opaque_ref&) = delete;
opaque_ref& operator=(const opaque_ref&) = delete;
opaque_ref(opaque_ref&& rhs) noexcept {
iree_vm_ref_move(&rhs.value_, &value_);
}
opaque_ref& operator=(opaque_ref&& rhs) noexcept {
iree_vm_ref_move(&rhs.value_, &value_);
return *this;
}
~opaque_ref() { iree_vm_ref_release(&value_); }
constexpr iree_vm_ref_t* get() const noexcept { return &value_; }
constexpr operator iree_vm_ref_t*() const noexcept { // NOLINT
return &value_;
}
constexpr bool operator!() const noexcept { return !value_.ptr; }
// Returns a pointer to the inner pointer storage.
// This allows passing a pointer to the ref as an output argument to C-style
// creation functions.
constexpr iree_vm_ref_t* operator&() noexcept { return &value_; } // NOLINT
private:
mutable iree_vm_ref_t value_ = {0};
};
} // namespace vm
} // namespace iree
#endif // IREE_VM_REF_CC_H_