// Copyright 2018 The Dawn Authors // // 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 // // http://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 COMMON_RESULT_H_ #define COMMON_RESULT_H_ #include "common/Assert.h" #include "common/Compiler.h" #include #include #include #include #include // Result is the following sum type (Haskell notation): // // data Result T E = Success T | Error E | Empty // // It is meant to be used as the return type of functions that might fail. The reason for the Empty // case is that a Result should never be discarded, only destructured (its error or success moved // out) or moved into a different Result. The Empty case tags Results that have been moved out and // Result's destructor should ASSERT on it being Empty. // // Since C++ doesn't have efficient sum types for the special cases we care about, we provide // template specializations for them. template class Result; // The interface of Result should look like the following. // public: // Result(T&& success); // Result(std::unique_ptr error); // // Result(Result&& other); // Result& operator=(Result&& other); // // ~Result(); // // bool IsError() const; // bool IsSuccess() const; // // T&& AcquireSuccess(); // std::unique_ptr AcquireError(); // Specialization of Result for returning errors only via pointers. It is basically a pointer // where nullptr is both Success and Empty. template class DAWN_NO_DISCARD Result { public: Result(); Result(std::unique_ptr error); Result(Result&& other); Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; void AcquireSuccess(); std::unique_ptr AcquireError(); private: std::unique_ptr mError; }; // Uses SFINAE to try to get alignof(T) but fallback to Default if T isn't defined. template constexpr size_t alignof_if_defined_else_default = Default; template constexpr size_t alignof_if_defined_else_default = alignof(T); // Specialization of Result when both the error an success are pointers. It is implemented as a // tagged pointer. The tag for Success is 0 so that returning the value is fastest. namespace detail { // Utility functions to manipulate the tagged pointer. Some of them don't need to be templated // but we really want them inlined so we keep them in the headers enum PayloadType { Success = 0, Error = 1, Empty = 2, }; intptr_t MakePayload(const void* pointer, PayloadType type); PayloadType GetPayloadType(intptr_t payload); template static T* GetSuccessFromPayload(intptr_t payload); template static E* GetErrorFromPayload(intptr_t payload); constexpr static intptr_t kEmptyPayload = Empty; } // namespace detail template class DAWN_NO_DISCARD Result { public: static_assert(alignof_if_defined_else_default >= 4, "Result reserves two bits for tagging pointers"); static_assert(alignof_if_defined_else_default >= 4, "Result reserves two bits for tagging pointers"); Result(T* success); Result(std::unique_ptr error); // Support returning a Result from a Result template Result(Result&& other); template Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; T* AcquireSuccess(); std::unique_ptr AcquireError(); private: template friend class Result; intptr_t mPayload = detail::kEmptyPayload; }; template class DAWN_NO_DISCARD Result { public: static_assert(alignof_if_defined_else_default >= 4, "Result reserves two bits for tagging pointers"); static_assert(alignof_if_defined_else_default >= 4, "Result reserves two bits for tagging pointers"); Result(const T* success); Result(std::unique_ptr error); Result(Result&& other); Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; const T* AcquireSuccess(); std::unique_ptr AcquireError(); private: intptr_t mPayload = detail::kEmptyPayload; }; template class Ref; template class DAWN_NO_DISCARD Result, E> { public: static_assert(alignof_if_defined_else_default >= 4, "Result, E> reserves two bits for tagging pointers"); static_assert(alignof_if_defined_else_default >= 4, "Result, E> reserves two bits for tagging pointers"); template Result(Ref&& success); Result(std::unique_ptr error); template Result(Result, E>&& other); template Result, E>& operator=(Result, E>&& other); ~Result(); bool IsError() const; bool IsSuccess() const; Ref AcquireSuccess(); std::unique_ptr AcquireError(); private: template friend class Result; intptr_t mPayload = detail::kEmptyPayload; }; // Catchall definition of Result implemented as a tagged struct. It could be improved to use // a tagged union instead if it turns out to be a hotspot. T and E must be movable and default // constructible. template class DAWN_NO_DISCARD Result { public: Result(T&& success); Result(std::unique_ptr error); Result(Result&& other); Result& operator=(Result&& other); ~Result(); bool IsError() const; bool IsSuccess() const; T&& AcquireSuccess(); std::unique_ptr AcquireError(); private: enum PayloadType { Success = 0, Error = 1, Acquired = 2, }; PayloadType mType; std::unique_ptr mError; T mSuccess; }; // Implementation of Result template Result::Result() { } template Result::Result(std::unique_ptr error) : mError(std::move(error)) { } template Result::Result(Result&& other) : mError(std::move(other.mError)) { } template Result& Result::operator=(Result&& other) { ASSERT(mError == nullptr); mError = std::move(other.mError); return *this; } template Result::~Result() { ASSERT(mError == nullptr); } template bool Result::IsError() const { return mError != nullptr; } template bool Result::IsSuccess() const { return mError == nullptr; } template void Result::AcquireSuccess() { } template std::unique_ptr Result::AcquireError() { return std::move(mError); } // Implementation details of the tagged pointer Results namespace detail { template T* GetSuccessFromPayload(intptr_t payload) { ASSERT(GetPayloadType(payload) == Success); return reinterpret_cast(payload); } template E* GetErrorFromPayload(intptr_t payload) { ASSERT(GetPayloadType(payload) == Error); return reinterpret_cast(payload ^ 1); } } // namespace detail // Implementation of Result template Result::Result(T* success) : mPayload(detail::MakePayload(success, detail::Success)) { } template Result::Result(std::unique_ptr error) : mPayload(detail::MakePayload(error.release(), detail::Error)) { } template template Result::Result(Result&& other) : mPayload(other.mPayload) { other.mPayload = detail::kEmptyPayload; static_assert(std::is_same::value || std::is_base_of::value, ""); } template template Result& Result::operator=(Result&& other) { ASSERT(mPayload == detail::kEmptyPayload); static_assert(std::is_same::value || std::is_base_of::value, ""); mPayload = other.mPayload; other.mPayload = detail::kEmptyPayload; return *this; } template Result::~Result() { ASSERT(mPayload == detail::kEmptyPayload); } template bool Result::IsError() const { return detail::GetPayloadType(mPayload) == detail::Error; } template bool Result::IsSuccess() const { return detail::GetPayloadType(mPayload) == detail::Success; } template T* Result::AcquireSuccess() { T* success = detail::GetSuccessFromPayload(mPayload); mPayload = detail::kEmptyPayload; return success; } template std::unique_ptr Result::AcquireError() { std::unique_ptr error(detail::GetErrorFromPayload(mPayload)); mPayload = detail::kEmptyPayload; return std::move(error); } // Implementation of Result template Result::Result(const T* success) : mPayload(detail::MakePayload(success, detail::Success)) { } template Result::Result(std::unique_ptr error) : mPayload(detail::MakePayload(error.release(), detail::Error)) { } template Result::Result(Result&& other) : mPayload(other.mPayload) { other.mPayload = detail::kEmptyPayload; } template Result& Result::operator=(Result&& other) { ASSERT(mPayload == detail::kEmptyPayload); mPayload = other.mPayload; other.mPayload = detail::kEmptyPayload; return *this; } template Result::~Result() { ASSERT(mPayload == detail::kEmptyPayload); } template bool Result::IsError() const { return detail::GetPayloadType(mPayload) == detail::Error; } template bool Result::IsSuccess() const { return detail::GetPayloadType(mPayload) == detail::Success; } template const T* Result::AcquireSuccess() { T* success = detail::GetSuccessFromPayload(mPayload); mPayload = detail::kEmptyPayload; return success; } template std::unique_ptr Result::AcquireError() { std::unique_ptr error(detail::GetErrorFromPayload(mPayload)); mPayload = detail::kEmptyPayload; return std::move(error); } // Implementation of Result, E> template template Result, E>::Result(Ref&& success) : mPayload(detail::MakePayload(success.Detach(), detail::Success)) { static_assert(std::is_convertible::value, ""); } template Result, E>::Result(std::unique_ptr error) : mPayload(detail::MakePayload(error.release(), detail::Error)) { } template template Result, E>::Result(Result, E>&& other) : mPayload(other.mPayload) { static_assert(std::is_convertible::value, ""); other.mPayload = detail::kEmptyPayload; } template template Result, E>& Result, E>::operator=(Result, E>&& other) { static_assert(std::is_convertible::value, ""); ASSERT(mPayload == detail::kEmptyPayload); mPayload = other.mPayload; other.mPayload = detail::kEmptyPayload; return *this; } template Result, E>::~Result() { ASSERT(mPayload == detail::kEmptyPayload); } template bool Result, E>::IsError() const { return detail::GetPayloadType(mPayload) == detail::Error; } template bool Result, E>::IsSuccess() const { return detail::GetPayloadType(mPayload) == detail::Success; } template Ref Result, E>::AcquireSuccess() { ASSERT(IsSuccess()); Ref success = AcquireRef(detail::GetSuccessFromPayload(mPayload)); mPayload = detail::kEmptyPayload; return success; } template std::unique_ptr Result, E>::AcquireError() { ASSERT(IsError()); std::unique_ptr error(detail::GetErrorFromPayload(mPayload)); mPayload = detail::kEmptyPayload; return std::move(error); } // Implementation of Result template Result::Result(T&& success) : mType(Success), mSuccess(std::move(success)) { } template Result::Result(std::unique_ptr error) : mType(Error), mError(std::move(error)) { } template Result::~Result() { ASSERT(mType == Acquired); } template Result::Result(Result&& other) : mType(other.mType), mError(std::move(other.mError)), mSuccess(std::move(other.mSuccess)) { other.mType = Acquired; } template Result& Result::operator=(Result&& other) { mType = other.mType; mError = std::move(other.mError); mSuccess = std::move(other.mSuccess); other.mType = Acquired; return *this; } template bool Result::IsError() const { return mType == Error; } template bool Result::IsSuccess() const { return mType == Success; } template T&& Result::AcquireSuccess() { ASSERT(mType == Success); mType = Acquired; return std::move(mSuccess); } template std::unique_ptr Result::AcquireError() { ASSERT(mType == Error); mType = Acquired; return std::move(mError); } #endif // COMMON_RESULT_H_