Jit/ref.h (142 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#pragma once
#include "Python.h"
#include <functional>
#include <type_traits>
template <typename T>
class RefBase {
public:
RefBase() = default;
RefBase(std::nullptr_t) {}
operator T*() const {
return ptr_;
}
template <typename X = T>
operator std::enable_if_t<!std::is_same_v<X, PyObject>, PyObject*>() const {
return reinterpret_cast<PyObject*>(ptr_);
}
T* release() {
auto ref = ptr_;
ptr_ = nullptr;
return ref;
}
T* get() const {
return ptr_;
}
T* operator->() const {
return ptr_;
}
bool operator==(std::nullptr_t) const {
return ptr_ == nullptr;
}
bool operator!=(std::nullptr_t) const {
return ptr_ != nullptr;
}
protected:
T* ptr_{nullptr};
};
/*
* BorrowedRef owns a borrowed reference to a PyObject.
*
* It is intended to be used in place of a raw PyObject* to codify
* the ownership semantics of the reference explicity in the type system
* (as opposed to in a comment).
*
*/
template <
typename T = PyObject,
typename = std::enable_if_t<!std::is_pointer_v<T>>>
class BorrowedRef : public RefBase<T> {
public:
using RefBase<T>::RefBase;
BorrowedRef(T* obj) {
ptr_ = obj;
}
template <
typename X = T,
typename = std::enable_if_t<!std::is_same_v<X, PyObject>>>
BorrowedRef(PyObject* ptr) : BorrowedRef(reinterpret_cast<X*>(ptr)) {}
// Allow conversion from any BorrowedRef to BorrowedRef<PyObject>
template <
typename V,
typename X = T,
typename = std::enable_if_t<std::is_same_v<X, PyObject>>>
BorrowedRef(const BorrowedRef<V>& other)
: BorrowedRef(reinterpret_cast<PyObject*>(other.get())) {}
BorrowedRef(const RefBase<T>& other) {
ptr_ = other.get();
}
BorrowedRef& operator=(const RefBase<T>& other) {
ptr_ = other.get();
return *this;
}
void reset(T* obj = nullptr) {
ptr_ = obj;
}
private:
using RefBase<T>::ptr_;
};
template <typename T>
struct std::hash<BorrowedRef<T>> {
size_t operator()(const BorrowedRef<T>& ref) const {
std::hash<T*> hasher;
return hasher(ref.get());
}
};
/*
* Ref owns a reference to a PyObject.
*
* It is intended to be a drop-in replacement for a PyObject* with the added
* benefit that it automatically decrefs the underlying PyObject* when the
* Ref is destroyed.
*
* A Ref cannot be copied; it uniquely owns its reference. Ownership can be
* transfered via a move, or a BorrowedRef can be constructed from a Ref.
*
* One common use case is to use a Ref to create a new reference from a
* borrowed reference that was returned from a call to the runtime, e.g.
*
* Ref<> new_ref(PyDict_GetItemString(d, "key"));
*
* In many cases we want to use a Ref to manage a new reference that is
* returned as a raw PyObject* from the runtime. To do so, we steal the
* reference that was returned by the runtime and store it in a Ref:
*
* auto stolen_ref = Ref<>::steal(PyLong_FromLong(100));
*
* Refs should also be used to indicate the ownership semantics of functions
* w.r.t their arguments. Arguments that will be stolen should be Refs, whereas
* arguments that will be borrowed should either be a BorrowedRef or a
* reference to a Ref (discouraged).
*
* For example, consider `MyPyTuple_SetItem`, modeled after `PyTuple_SetItem`,
* which steals a reference to the value being stored. We would write it as:
*
* void MyPyTuple_SetItem(BorrowedRef<> tup, Py_ssize_t pos, Ref<> val);
*
* and call it via:
*
* auto tup = Ref<>::steal(PyTuple_New(1));
* auto val = Ref<>::steal(PyLong_AsLong(100));
* MyPyTuple_SetItem(tup, 0, std::move(val));
*
* It's clear that we're transferring ownership of the reference in `val` to
* `MyPyTuple_SetItem`.
*
*/
template <
typename T = PyObject,
typename = std::enable_if_t<!std::is_pointer_v<T>>>
class Ref : public RefBase<T> {
public:
using RefBase<T>::RefBase;
explicit Ref(T* obj) {
Py_XINCREF(obj);
ptr_ = obj;
}
template <
typename X = T,
typename = std::enable_if_t<!std::is_same_v<X, PyObject>>>
explicit Ref(PyObject* ptr) : Ref(reinterpret_cast<X*>(ptr)) {}
~Ref() {
Py_XDECREF(ptr_);
ptr_ = nullptr;
}
Ref(Ref&& other) {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
}
Ref& operator=(Ref&& other) {
if (this == &other) {
return *this;
}
Py_XDECREF(ptr_);
ptr_ = other.ptr_;
other.ptr_ = nullptr;
return *this;
}
void reset(T* obj = nullptr) {
Py_XINCREF(obj);
Py_XDECREF(ptr_);
ptr_ = obj;
}
template <
typename X = T,
typename = std::enable_if_t<!std::is_same_v<X, PyObject>>>
void reset(PyObject* obj) {
reset(reinterpret_cast<T*>(obj));
}
static Ref steal(T* obj) {
return Ref(obj, StealTag{});
}
template <
typename X = T,
typename = std::enable_if_t<!std::is_same_v<X, PyObject>>>
static Ref steal(PyObject* obj) {
return Ref(reinterpret_cast<T*>(obj), StealTag{});
}
private:
Ref(const Ref&) = delete;
Ref& operator=(const Ref&) = delete;
enum class StealTag {};
Ref(T* obj, StealTag) {
ptr_ = obj;
}
using RefBase<T>::ptr_;
};
template <typename T>
struct std::hash<Ref<T>> {
size_t operator()(const Ref<T>& ref) const {
std::hash<T*> hasher;
return hasher(ref.get());
}
};