blob: 31048161bc1a51458fe5af71043bc65c1ea6a177 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed 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
//
// https://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.
#ifndef IREE_VM_REF_CC_H_
#define IREE_VM_REF_CC_H_
#include <memory>
#include <utility>
#include "absl/base/attributes.h"
#include "iree/base/api.h"
#include "iree/vm/ref.h"
#include "iree/vm/types.h"
#ifndef __cplusplus
#error "This header is meant for use with C++ implementations."
#endif // __cplusplus
namespace iree {
namespace vm {
// 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>
ABSL_ATTRIBUTE_ALWAYS_INLINE void ref_type_retain(T* p) {
iree_vm_ref_object_retain(p, ref_type_descriptor<T>::get());
}
template <typename T>
ABSL_ATTRIBUTE_ALWAYS_INLINE void ref_type_release(T* p) {
iree_vm_ref_object_release(p, ref_type_descriptor<T>::get());
}
// 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:
ABSL_ATTRIBUTE_ALWAYS_INLINE iree_vm_ref_type_t type() const noexcept {
return ref_type_descriptor<T>::get()->type;
}
ABSL_ATTRIBUTE_ALWAYS_INLINE ref() noexcept = default;
ABSL_ATTRIBUTE_ALWAYS_INLINE ref(std::nullptr_t) noexcept {} // NOLINT
ABSL_ATTRIBUTE_ALWAYS_INLINE ref(T* p) noexcept : px_(p) {}
ABSL_ATTRIBUTE_ALWAYS_INLINE ~ref() noexcept {
if (px_) ref_type_release<T>(px_);
}
// 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 : px_(rhs.get()) {
if (px_) ref_type_retain<T>(px_);
}
ref& operator=(const ref&) noexcept = delete;
// Move support to transfer ownership from one ref to another.
ref(ref&& rhs) noexcept : px_(rhs.release()) {}
ref& operator=(ref&& rhs) noexcept {
if (px_ != rhs.px_) {
if (px_) ref_type_release<T>(px_);
px_ = rhs.release();
}
return *this;
}
// Move support from another compatible type.
template <typename U>
ref(ref<U>&& rhs) noexcept : px_(rhs.release()) {} // NOLINT
template <typename U>
ref& operator=(ref<U>&& rhs) noexcept {
if (px_ != rhs.get()) {
if (px_) ref_type_release<T>(px_);
px_ = rhs.release();
}
return *this;
}
// Resets the object to nullptr and decrements the reference count, possibly
// deleting it.
void reset() noexcept {
if (px_) {
ref_type_release<T>(px_);
px_ = 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().
ABSL_ATTRIBUTE_ALWAYS_INLINE T* release() noexcept {
T* p = px_;
px_ = nullptr;
return p;
}
// Assigns a pointer.
// The pointer will be accepted by the ref and its reference count will
// not be incremented.
ABSL_ATTRIBUTE_ALWAYS_INLINE void assign(T* value) noexcept {
reset();
px_ = 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 px_; }
constexpr T& operator*() const noexcept { return *px_; }
constexpr T* operator->() const noexcept { return px_; }
// 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 { return &px_; } // NOLINT
// 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 {
return px_ ? &this_type::px_ : nullptr;
}
// Supports unary expression evaluation.
constexpr bool operator!() const noexcept { return !px_; }
// Swap support.
void swap(ref& rhs) { std::swap(px_, rhs.px_); }
private:
T* px_ = nullptr;
};
// 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(ref<T>& value) {
if (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) {
if (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);
}
// 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.
struct opaque_ref {
iree_vm_ref_t value = {0};
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); }
};
} // namespace vm
} // namespace iree
#endif // IREE_VM_REF_CC_H_