blob: 3b1cfc434643d132a06d44af7e503eb4455e5e6e [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_PROMISE_VALUE_H_
#define BASE_TASK_PROMISE_PROMISE_VALUE_H_
#include "base/base_export.h"
#include "base/memory/scoped_refptr.h"
#include "base/parameter_pack.h"
namespace base {
namespace internal {
class AbstractPromise;
} // namespace internal
// std::variant, std::tuple and other templates can't contain void but they can
// contain the empty type Void. This is the same idea as std::monospace.
struct Void {};
// Signals that a promise doesn't resolve. E.g. Promise<NoResolve, int>
struct NoResolve {};
// Signals that a promise doesn't reject. E.g. Promise<int, NoReject>
struct NoReject {};
// Internally Resolved<> is used to store the result of a promise callback that
// resolved. This lets us disambiguate promises with the same resolve and reject
// type.
template <typename T>
struct Resolved {
using Type = T;
static_assert(!std::is_same<T, NoReject>::value,
"Can't have Resolved<NoReject>");
Resolved() {
static_assert(!std::is_same<T, NoResolve>::value,
"Can't have Resolved<NoResolve>");
}
// Conversion constructor accepts any arguments except Resolved<T>.
template <
typename... Args,
std::enable_if_t<!all_of(
{std::is_same<Resolved, std::decay_t<Args>>::value...})>* = nullptr>
Resolved(Args&&... args) noexcept : value(std::forward<Args>(args)...) {}
T value;
};
template <>
struct Resolved<void> {
using Type = void;
Void value;
};
// Internally Rejected<> is used to store the result of a promise callback that
// rejected. This lets us disambiguate promises with the same resolve and reject
// type.
template <typename T>
struct Rejected {
using Type = T;
T value;
static_assert(!std::is_same<T, NoResolve>::value,
"Can't have Rejected<NoResolve>");
Rejected() {
static_assert(!std::is_same<T, NoReject>::value,
"Can't have Rejected<NoReject>");
}
// Conversion constructor accepts any arguments except Rejected<T>.
template <
typename... Args,
std::enable_if_t<!all_of(
{std::is_same<Rejected, std::decay_t<Args>>::value...})>* = nullptr>
Rejected(Args&&... args) noexcept : value(std::forward<Args>(args)...) {
static_assert(!std::is_same<T, NoReject>::value,
"Can't have Rejected<NoReject>");
}
};
template <>
struct Rejected<void> {
using Type = void;
Void value;
};
namespace internal {
class PromiseExecutor;
struct BASE_EXPORT PromiseValueInternal {
// The state is stored in the bottom three bits of the TypeOps pointer, see
// TaggedTypeOpsPtr.
enum State {
EMPTY,
PROMISE_EXECUTOR,
CURRIED_PROMISE,
RESOLVED,
REJECTED,
// This value is never stored and is used internally for error checking.
INVALID
};
// Where possible we use the small object allocation optimization to avoid
// heap allocations.
struct OutlineAlloc {
void* value; // Holds a T
template <typename T>
T& value_as() {
return *static_cast<T*>(value);
}
template <typename T>
const T& value_as() const {
return *static_cast<const T*>(value);
}
};
struct alignas(sizeof(void*)) InlineAlloc {
// Holds a T if small. Tweaked to hold a promise executor inline.
char bytes[sizeof(void*) * 3];
template <typename T>
T& value_as() {
return *reinterpret_cast<T*>(bytes);
}
template <typename T>
const T& value_as() const {
return *reinterpret_cast<const T*>(bytes);
}
};
template <typename T>
struct InlineStorageHelper {
static constexpr bool kUseInlineStorage =
(sizeof(T) <= sizeof(InlineAlloc));
static_assert(
std::alignment_of<T>::value <= sizeof(T),
"Type T has alignment requirements that preclude it's storage inline.");
};
template <typename T>
constexpr T* GetStorage() {
return static_cast<T*>(
GetStorageHelper<InlineStorageHelper<T>::kUseInlineStorage>::GetStorage(
*this));
}
template <typename T>
constexpr const T* GetStorage() const {
return static_cast<const T*>(
GetStorageHelper<InlineStorageHelper<T>::kUseInlineStorage>::GetStorage(
*this));
}
template <typename T, bool UseInlineStorage>
struct ConstructHelper;
template <bool UseInlineStorage>
struct GetStorageHelper;
template <typename T, bool UseInlineStorage, bool HasMoveConstructor>
struct MoveHelper;
template <typename T, bool UseInlineStorage>
struct DeleteHelper;
template <typename T>
struct TypeToStateHelper;
using MoveFunctionPtr = void (*)(PromiseValueInternal* src,
PromiseValueInternal* dest);
using DeleteFunctionPtr = void (*)(PromiseValueInternal* object);
// Similar to a virtual function but we don't need a dynamic memory
// allocation. One possible design alternative would be to fold these methods
// into T and use T in InlineAlloc (which would now have to
// be bigger to accommodate the vtable pointer).
// Eight byte alignment specified to allow TaggedTypeOpsPtr to store the state
// in the low bits of the pointer.
struct alignas(8) TypeOps {
#if DCHECK_IS_ON()
const char* type_name;
#endif
MoveFunctionPtr move_fn_ptr;
DeleteFunctionPtr delete_fn_ptr;
};
template <typename T>
struct TypeOpsHelper {
static constexpr const char* TypeName() { return PRETTY_FUNCTION; }
static constexpr TypeOps type_ops = {
#if DCHECK_IS_ON()
TypeName(),
#endif
&MoveHelper<T,
InlineStorageHelper<T>::kUseInlineStorage,
std::is_move_constructible<T>::value>::Move,
&DeleteHelper<T, InlineStorageHelper<T>::kUseInlineStorage>::Delete};
};
static void NopMove(PromiseValueInternal* src, PromiseValueInternal* dest);
static void NopDelete(PromiseValueInternal* src);
static constexpr TypeOps null_type_ = {
#if DCHECK_IS_ON()
"EMPTY!",
#endif
&NopMove, &NopDelete};
union {
OutlineAlloc outline_alloc;
InlineAlloc inline_alloc;
} union_;
};
// static
template <typename T>
const PromiseValueInternal::TypeOps
PromiseValueInternal::TypeOpsHelper<T>::type_ops;
template <typename T>
struct PromiseValueInternal::ConstructHelper<T, /* UseInlineStorage */ true> {
template <typename... Args>
static void Construct(PromiseValueInternal* dest, Args&&... args) noexcept {
new (&dest->union_.inline_alloc.bytes) T(std::forward<Args>(args)...);
}
};
template <typename T>
struct PromiseValueInternal::ConstructHelper<T, /* UseInlineStorage */ false> {
template <typename... Args>
static void Construct(PromiseValueInternal* dest, Args&&... args) noexcept {
dest->union_.outline_alloc.value = new T(std::forward<Args>(args)...);
}
};
template <>
struct PromiseValueInternal::GetStorageHelper</* UseInlineStorage */ true> {
static void* GetStorage(PromiseValueInternal& any) {
return &any.union_.inline_alloc.bytes;
}
static const void* GetStorage(const PromiseValueInternal& any) {
return &any.union_.inline_alloc.bytes;
}
};
template <>
struct PromiseValueInternal::GetStorageHelper</* UseInlineStorage */ false> {
static void* GetStorage(PromiseValueInternal& any) {
return any.union_.outline_alloc.value;
}
static const void* GetStorage(const PromiseValueInternal& any) {
return any.union_.outline_alloc.value;
}
};
template <typename T>
struct PromiseValueInternal::
MoveHelper<T, /* UseInlineStorage */ true, /* HasMoveConstructor */ true> {
static void Move(PromiseValueInternal* src, PromiseValueInternal* dest) {
DCHECK_NE(src, dest);
new (&dest->union_.inline_alloc.bytes)
T(std::move(src->union_.inline_alloc.value_as<T>()));
}
};
template <typename T>
struct PromiseValueInternal::
MoveHelper<T, /* UseInlineStorage */ true, /* HasMoveConstructor */ false> {
static void Move(PromiseValueInternal* src, PromiseValueInternal* dest) {
DCHECK_NE(src, dest);
// Fall back to the copy constructor.
new (&dest->union_.inline_alloc.bytes)
T(src->union_.inline_alloc.value_as<T>());
}
};
template <typename T, bool HasMoveConstructor>
struct PromiseValueInternal::
MoveHelper<T, /* UseInlineStorage */ false, HasMoveConstructor> {
static void Move(PromiseValueInternal* src, PromiseValueInternal* dest) {
DCHECK_NE(src, dest);
dest->union_.outline_alloc.value = src->union_.outline_alloc.value;
src->union_.outline_alloc.value = nullptr;
}
};
template <typename T>
struct PromiseValueInternal::DeleteHelper<T, /* UseInlineStorage */ true> {
static void Delete(PromiseValueInternal* any) {
reinterpret_cast<T*>(&any->union_.inline_alloc.bytes)->~T();
}
};
template <typename T>
struct PromiseValueInternal::DeleteHelper<T, /* UseInlineStorage */ false> {
static void Delete(PromiseValueInternal* any) {
delete static_cast<T*>(any->union_.outline_alloc.value);
}
};
template <typename T>
struct PromiseValueInternal::TypeToStateHelper {
static constexpr State state = State::INVALID;
};
template <>
struct PromiseValueInternal::TypeToStateHelper<PromiseExecutor> {
static constexpr State state = State::PROMISE_EXECUTOR;
};
template <>
struct PromiseValueInternal::TypeToStateHelper<scoped_refptr<AbstractPromise>> {
static constexpr State state = State::CURRIED_PROMISE;
};
template <typename T>
struct PromiseValueInternal::TypeToStateHelper<Resolved<T>> {
static constexpr State state = State::RESOLVED;
};
template <typename T>
struct PromiseValueInternal::TypeToStateHelper<Rejected<T>> {
static constexpr State state = State::REJECTED;
};
class TaggedTypeOpsPtr {
public:
using State = PromiseValueInternal::State;
using TypeOps = PromiseValueInternal::TypeOps;
static_assert(static_cast<int>(State::INVALID) <= alignof(TypeOps),
"The state enum must fit in the low bits of the TypeOps "
"address");
void Set(const TypeOps* type_ops, PromiseValueInternal::State state) {
DCHECK_EQ(reinterpret_cast<uintptr_t>(type_ops) & kStateMask, 0u)
<< type_ops;
type_ops_ =
reinterpret_cast<uintptr_t>(type_ops) | static_cast<uintptr_t>(state);
}
TypeOps* get() const {
return reinterpret_cast<TypeOps*>(type_ops_ & ~kStateMask);
}
TypeOps* operator->() const { return get(); }
State GetState() const { return static_cast<State>(type_ops_ & kStateMask); }
private:
static constexpr uintptr_t kStateMask = alignof(TypeOps) - 1;
uintptr_t type_ops_;
};
// Inspired by std::any<> this container is used to hold a Promise's value which
// can be one of: Empty, PromiseExecutor, scoped_refptr<AbstractPromise>,
// Resolved<> or Rejected<>. Unlike std::any PromiseValue can hold move only
// types and it doesn't require exceptions.
class BASE_EXPORT PromiseValue {
private:
using State = PromiseValueInternal::State;
using TypeOps = PromiseValueInternal::TypeOps;
template <typename T>
using TypeOpsHelper = PromiseValueInternal::TypeOpsHelper<T>;
template <typename T>
using Construct = PromiseValueInternal::ConstructHelper<
T,
PromiseValueInternal::InlineStorageHelper<T>::kUseInlineStorage>;
public:
PromiseValue() noexcept { MarkAsEmpty(); }
// Constructs a PromiseValue containing |value| as long as |VT| isn't INVALID
// according to TypeToStateHelper.
// E.g. base::PromiseValue a(Resolved<int>(123));
template <typename T,
typename VT = std::decay_t<T>,
State state = PromiseValueInternal::TypeToStateHelper<VT>::state,
std::enable_if_t<state != State::INVALID>* = nullptr>
explicit PromiseValue(T&& value) noexcept {
Construct<VT>::Construct(&value_, std::move(value));
type_ops_.Set(&TypeOpsHelper<VT>::type_ops, state);
}
// Constructs a PromiseValue containing an object of type T which is
// initialized by std::forward<Args>(args). E.g.
// base::unique_any a(base::in_place_type_t<Resolved<int>>(), 123);
template <typename T,
typename... Args,
State state = PromiseValueInternal::TypeToStateHelper<T>::state,
std::enable_if_t<state != State::INVALID>* = nullptr>
explicit PromiseValue(in_place_type_t<T> /*tag*/, Args&&... args) noexcept {
Construct<T>::Construct(&value_, std::forward<Args>(args)...);
type_ops_.Set(&TypeOpsHelper<T>::type_ops, state);
}
// Constructs a PromiseValue with the value contained by |other| moved into
// it.
PromiseValue(PromiseValue&& other) noexcept {
other.type_ops_->move_fn_ptr(&other.value_, &value_);
type_ops_ = other.type_ops_;
other.MarkAsEmpty();
}
~PromiseValue() { reset(); }
void reset() {
type_ops_->delete_fn_ptr(&value_);
MarkAsEmpty();
}
bool has_value() const noexcept {
return type_ops_.GetState() != State::EMPTY;
}
// Clears the existing value and constructs a an object of type T which is
// initialized by std::forward<Args>(args).
template <typename T,
typename... Args,
typename VT = std::decay_t<T>,
State state = PromiseValueInternal::TypeToStateHelper<VT>::state,
std::enable_if_t<state != State::INVALID>* = nullptr>
void emplace(in_place_type_t<T> /*tag*/, Args&&... args) noexcept {
type_ops_->delete_fn_ptr(&value_);
Construct<VT>::Construct(&value_, std::forward<Args>(args)...);
type_ops_.Set(&TypeOpsHelper<VT>::type_ops, state);
}
// Assigns |t| as long as |VT| isn't INVALID according to TypeToStateHelper.
template <typename T,
typename VT = std::decay_t<T>,
State state = PromiseValueInternal::TypeToStateHelper<VT>::state,
std::enable_if_t<state != State::INVALID>* = nullptr>
void operator=(T&& t) noexcept {
type_ops_->delete_fn_ptr(&value_);
Construct<VT>::Construct(&value_, std::forward<T>(t));
type_ops_.Set(&TypeOpsHelper<VT>::type_ops, state);
}
void operator=(PromiseValue&& other) noexcept {
DCHECK_NE(this, &other);
type_ops_->delete_fn_ptr(&value_);
other.type_ops_->move_fn_ptr(&other.value_, &value_);
type_ops_ = other.type_ops_;
other.MarkAsEmpty();
}
bool ContainsPromiseExecutor() const {
return type_ops_.GetState() == State::PROMISE_EXECUTOR;
}
bool ContainsCurriedPromise() const {
return type_ops_.GetState() == State::CURRIED_PROMISE;
}
bool ContainsResolved() const {
return type_ops_.GetState() == State::RESOLVED;
}
bool ContainsRejected() const {
return type_ops_.GetState() == State::REJECTED;
}
template <typename T,
typename VT = std::decay_t<T>,
State state = PromiseValueInternal::TypeToStateHelper<VT>::state,
std::enable_if_t<state != State::INVALID>* = nullptr>
T* Get() noexcept {
DCHECK_EQ(state, type_ops_.GetState());
// Unfortunately we can't rely on the addresses of the TypeOps being the
// same across .so boundaries unless every part of |VT| is exported so we
// do a string comparison instead to check the right type is used.
#if DCHECK_IS_ON()
DCHECK_EQ(type_ops_->type_name,
std::string(TypeOpsHelper<VT>::type_ops.type_name));
#endif
return static_cast<T*>(value_.GetStorage<VT>());
}
template <typename T,
typename VT = std::decay_t<T>,
State state = PromiseValueInternal::TypeToStateHelper<VT>::state,
std::enable_if_t<state != State::INVALID>* = nullptr>
const T* Get() const noexcept {
DCHECK_EQ(state, type_ops_.GetState());
// Unfortunately we can't rely on the addresses of the TypeOps being the
// same across .so boundaries unless every part of |VT| is exported so we
// do a string comparison instead to check the right type is used.
#if DCHECK_IS_ON()
DCHECK_EQ(type_ops_->type_name,
std::string(TypeOpsHelper<VT>::type_ops.type_name));
#endif
return static_cast<const T*>(value_.GetStorage<VT>());
}
private:
void MarkAsEmpty() {
type_ops_.Set(&PromiseValueInternal::null_type_, State::EMPTY);
}
PromiseValueInternal value_;
TaggedTypeOpsPtr type_ops_;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_PROMISE_VALUE_H_