// 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 struct NoResolve {}; // Signals that a promise doesn't reject. E.g. Promise 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 struct Resolved { using Type = T; static_assert(!std::is_same::value, "Can't have Resolved"); Resolved() { static_assert(!std::is_same::value, "Can't have Resolved"); } // Conversion constructor accepts any arguments except Resolved. template < typename... Args, std::enable_if_t>::value...})>* = nullptr> Resolved(Args&&... args) noexcept : value(std::forward(args)...) {} T value; }; template <> struct Resolved { 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 struct Rejected { using Type = T; T value; static_assert(!std::is_same::value, "Can't have Rejected"); Rejected() { static_assert(!std::is_same::value, "Can't have Rejected"); } // Conversion constructor accepts any arguments except Rejected. template < typename... Args, std::enable_if_t>::value...})>* = nullptr> Rejected(Args&&... args) noexcept : value(std::forward(args)...) { static_assert(!std::is_same::value, "Can't have Rejected"); } }; template <> struct Rejected { 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 T& value_as() { return *static_cast(value); } template const T& value_as() const { return *static_cast(value); } }; struct alignas(sizeof(void*)) InlineAlloc { // Holds a T if small. Tweaked to hold a promise executor inline. char bytes[sizeof(void*) * 3]; template T& value_as() { return *reinterpret_cast(bytes); } template const T& value_as() const { return *reinterpret_cast(bytes); } }; template struct InlineStorageHelper { static constexpr bool kUseInlineStorage = (sizeof(T) <= sizeof(InlineAlloc)); static_assert( std::alignment_of::value <= sizeof(T), "Type T has alignment requirements that preclude it's storage inline."); }; template constexpr T* GetStorage() { return static_cast( GetStorageHelper::kUseInlineStorage>::GetStorage( *this)); } template constexpr const T* GetStorage() const { return static_cast( GetStorageHelper::kUseInlineStorage>::GetStorage( *this)); } template struct ConstructHelper; template struct GetStorageHelper; template struct MoveHelper; template struct DeleteHelper; template 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 struct TypeOpsHelper { static constexpr const char* TypeName() { return PRETTY_FUNCTION; } static constexpr TypeOps type_ops = { #if DCHECK_IS_ON() TypeName(), #endif &MoveHelper::kUseInlineStorage, std::is_move_constructible::value>::Move, &DeleteHelper::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 constexpr PromiseValueInternal::TypeOps PromiseValueInternal::TypeOpsHelper::type_ops; template struct PromiseValueInternal::ConstructHelper { template static void Construct(PromiseValueInternal* dest, Args&&... args) noexcept { new (&dest->union_.inline_alloc.bytes) T(std::forward(args)...); } }; template struct PromiseValueInternal::ConstructHelper { template static void Construct(PromiseValueInternal* dest, Args&&... args) noexcept { dest->union_.outline_alloc.value = new T(std::forward(args)...); } }; template <> struct PromiseValueInternal::GetStorageHelper { 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 { 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 struct PromiseValueInternal:: MoveHelper { 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())); } }; template struct PromiseValueInternal:: MoveHelper { 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()); } }; template struct PromiseValueInternal:: MoveHelper { 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 struct PromiseValueInternal::DeleteHelper { static void Delete(PromiseValueInternal* any) { reinterpret_cast(&any->union_.inline_alloc.bytes)->~T(); } }; template struct PromiseValueInternal::DeleteHelper { static void Delete(PromiseValueInternal* any) { delete static_cast(any->union_.outline_alloc.value); } }; template struct PromiseValueInternal::TypeToStateHelper { static constexpr State state = State::INVALID; }; template <> struct PromiseValueInternal::TypeToStateHelper { static constexpr State state = State::PROMISE_EXECUTOR; }; template <> struct PromiseValueInternal::TypeToStateHelper> { static constexpr State state = State::CURRIED_PROMISE; }; template struct PromiseValueInternal::TypeToStateHelper> { static constexpr State state = State::RESOLVED; }; template struct PromiseValueInternal::TypeToStateHelper> { static constexpr State state = State::REJECTED; }; class TaggedTypeOpsPtr { public: using State = PromiseValueInternal::State; using TypeOps = PromiseValueInternal::TypeOps; static_assert(static_cast(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(type_ops) & kStateMask, 0u) << type_ops; type_ops_ = reinterpret_cast(type_ops) | static_cast(state); } TypeOps* get() const { return reinterpret_cast(type_ops_ & ~kStateMask); } TypeOps* operator->() const { return get(); } State GetState() const { return static_cast(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, // 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 using TypeOpsHelper = PromiseValueInternal::TypeOpsHelper; template using Construct = PromiseValueInternal::ConstructHelper< T, PromiseValueInternal::InlineStorageHelper::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(123)); template , State state = PromiseValueInternal::TypeToStateHelper::state, std::enable_if_t* = nullptr> explicit PromiseValue(T&& value) noexcept { Construct::Construct(&value_, std::move(value)); type_ops_.Set(&TypeOpsHelper::type_ops, state); } // Constructs a PromiseValue containing an object of type T which is // initialized by std::forward(args). E.g. // base::unique_any a(base::in_place_type_t>(), 123); template ::state, std::enable_if_t* = nullptr> explicit PromiseValue(in_place_type_t /*tag*/, Args&&... args) noexcept { Construct::Construct(&value_, std::forward(args)...); type_ops_.Set(&TypeOpsHelper::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). template , State state = PromiseValueInternal::TypeToStateHelper::state, std::enable_if_t* = nullptr> void emplace(in_place_type_t /*tag*/, Args&&... args) noexcept { type_ops_->delete_fn_ptr(&value_); Construct::Construct(&value_, std::forward(args)...); type_ops_.Set(&TypeOpsHelper::type_ops, state); } // Assigns |t| as long as |VT| isn't INVALID according to TypeToStateHelper. template , State state = PromiseValueInternal::TypeToStateHelper::state, std::enable_if_t* = nullptr> void operator=(T&& t) noexcept { type_ops_->delete_fn_ptr(&value_); Construct::Construct(&value_, std::forward(t)); type_ops_.Set(&TypeOpsHelper::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 , State state = PromiseValueInternal::TypeToStateHelper::state, std::enable_if_t* = 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::type_ops.type_name)); #endif return static_cast(value_.GetStorage()); } template , State state = PromiseValueInternal::TypeToStateHelper::state, std::enable_if_t* = 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::type_ops.type_name)); #endif return static_cast(value_.GetStorage()); } 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_