From f204cf2c4fdea8d2209aa6fb555672e696efcb8c Mon Sep 17 00:00:00 2001 From: Rafael Cintron Date: Tue, 21 Apr 2020 17:27:00 +0000 Subject: [PATCH] Add Ref specialization for Result Ref specialization will allow us to, in a future change, return Result> instances from Create methods while still keeping the tagged pointer optimization. Change-Id: I20c764358af22ba1dc53458d59b0b2b4770a0c6a Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/19801 Reviewed-by: Corentin Wallez Commit-Queue: Rafael Cintron --- src/common/Result.h | 84 ++++++++++++++++++++ src/tests/unittests/ResultTests.cpp | 118 +++++++++++++++++++++++++--- 2 files changed, 189 insertions(+), 13 deletions(-) diff --git a/src/common/Result.h b/src/common/Result.h index 7dc5e2df9e..85095684f3 100644 --- a/src/common/Result.h +++ b/src/common/Result.h @@ -166,6 +166,35 @@ class DAWN_NO_DISCARD Result { 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"); + + Result(Ref&& success); + Result(std::unique_ptr error); + + Result(Result, E>&& other); + Result, E>& operator=(Result, E>&& other); + + ~Result(); + + bool IsError() const; + bool IsSuccess() const; + + Ref AcquireSuccess(); + std::unique_ptr AcquireError(); + + private: + 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. @@ -368,6 +397,61 @@ std::unique_ptr Result::AcquireError() { return std::move(error); } +// Implementation of Result, E> +template +Result, E>::Result(Ref&& success) + : mPayload(detail::MakePayload(success.Detach(), detail::Success)) { +} + +template +Result, E>::Result(std::unique_ptr error) + : mPayload(detail::MakePayload(error.release(), detail::Error)) { +} + +template +Result, E>::Result(Result, E>&& other) : mPayload(other.mPayload) { + other.mPayload = detail::kEmptyPayload; +} + +template +Result, E>& Result, E>::operator=(Result, E>&& other) { + 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)) { diff --git a/src/tests/unittests/ResultTests.cpp b/src/tests/unittests/ResultTests.cpp index dd87e22a93..8e2f05f211 100644 --- a/src/tests/unittests/ResultTests.cpp +++ b/src/tests/unittests/ResultTests.cpp @@ -14,32 +14,71 @@ #include +#include "common/RefCounted.h" #include "common/Result.h" namespace { template void TestError(Result* result, E expectedError) { - ASSERT_TRUE(result->IsError()); - ASSERT_FALSE(result->IsSuccess()); + EXPECT_TRUE(result->IsError()); + EXPECT_FALSE(result->IsSuccess()); std::unique_ptr storedError = result->AcquireError(); - ASSERT_EQ(*storedError, expectedError); + EXPECT_EQ(*storedError, expectedError); } template void TestSuccess(Result* result, T expectedSuccess) { - ASSERT_FALSE(result->IsError()); - ASSERT_TRUE(result->IsSuccess()); + EXPECT_FALSE(result->IsError()); + EXPECT_TRUE(result->IsSuccess()); - T storedSuccess = result->AcquireSuccess(); - ASSERT_EQ(storedSuccess, expectedSuccess); + const T storedSuccess = result->AcquireSuccess(); + EXPECT_EQ(storedSuccess, expectedSuccess); + + // Once the success is acquired, result has an empty + // payload and is neither in the success nor error state. + EXPECT_FALSE(result->IsError()); + EXPECT_FALSE(result->IsSuccess()); } static int dummyError = 0xbeef; static float dummySuccess = 42.0f; static const float dummyConstSuccess = 42.0f; +class AClass : public RefCounted { + public: + int a = 0; +}; + +// Tests using the following overload of TestSuccess make +// local Ref instances to dummySuccessObj. Tests should +// ensure any local Ref objects made along the way continue +// to point to dummySuccessObj. +template +void TestSuccess(Result, E>* result, T* expectedSuccess) { + EXPECT_FALSE(result->IsError()); + EXPECT_TRUE(result->IsSuccess()); + + // AClass starts with a reference count of 1 and stored + // on the stack in the caller. The result parameter should + // hold the only other reference to the object. + EXPECT_EQ(expectedSuccess->GetRefCountForTesting(), 2u); + + const Ref storedSuccess = result->AcquireSuccess(); + EXPECT_EQ(storedSuccess.Get(), expectedSuccess); + + // Once the success is acquired, result has an empty + // payload and is neither in the success nor error state. + EXPECT_FALSE(result->IsError()); + EXPECT_FALSE(result->IsSuccess()); + + // Once we call AcquireSuccess, result no longer stores + // the object. storedSuccess should contain the only other + // reference to the object. + EXPECT_EQ(storedSuccess->GetRefCountForTesting(), 2u); +} + // Result // Test constructing an error Result @@ -66,16 +105,16 @@ TEST(ResultOnlyPointerError, ReturningError) { // Test constructing a success Result TEST(ResultOnlyPointerError, ConstructingSuccess) { Result result; - ASSERT_TRUE(result.IsSuccess()); - ASSERT_FALSE(result.IsError()); + EXPECT_TRUE(result.IsSuccess()); + EXPECT_FALSE(result.IsError()); } // Test moving a success Result TEST(ResultOnlyPointerError, MovingSuccess) { Result result; Result movedResult(std::move(result)); - ASSERT_TRUE(movedResult.IsSuccess()); - ASSERT_FALSE(movedResult.IsError()); + EXPECT_TRUE(movedResult.IsSuccess()); + EXPECT_FALSE(movedResult.IsError()); } // Test returning a success Result @@ -83,8 +122,8 @@ TEST(ResultOnlyPointerError, ReturningSuccess) { auto CreateError = []() -> Result { return {}; }; Result result = CreateError(); - ASSERT_TRUE(result.IsSuccess()); - ASSERT_FALSE(result.IsError()); + EXPECT_TRUE(result.IsSuccess()); + EXPECT_FALSE(result.IsError()); } // Result @@ -204,6 +243,59 @@ TEST(ResultBothPointerWithConstResult, ReturningSuccess) { TestSuccess(&result, &dummyConstSuccess); } +// Result, E> + +// Test constructing an error Result, E> +TEST(ResultRefT, ConstructingError) { + Result, int> result(std::make_unique(dummyError)); + TestError(&result, dummyError); +} + +// Test moving an error Result, E> +TEST(ResultRefT, MovingError) { + Result, int> result(std::make_unique(dummyError)); + Result, int> movedResult(std::move(result)); + TestError(&movedResult, dummyError); +} + +// Test returning an error Result, E> +TEST(ResultRefT, ReturningError) { + auto CreateError = []() -> Result, int> { + return {std::make_unique(dummyError)}; + }; + + Result, int> result = CreateError(); + TestError(&result, dummyError); +} + +// Test constructing a success Result, E> +TEST(ResultRefT, ConstructingSuccess) { + AClass success; + + Ref refObj(&success); + Result, int> result(std::move(refObj)); + TestSuccess(&result, &success); +} + +// Test moving a success Result, E> +TEST(ResultRefT, MovingSuccess) { + AClass success; + + Ref refObj(&success); + Result, int> result(std::move(refObj)); + Result, int> movedResult(std::move(result)); + TestSuccess(&movedResult, &success); +} + +// Test returning a success Result, E> +TEST(ResultRefT, ReturningSuccess) { + AClass success; + auto CreateSuccess = [&success]() -> Result, int> { return Ref(&success); }; + + Result, int> result = CreateSuccess(); + TestSuccess(&result, &success); +} + // Result // Test constructing an error Result