Skip to content

Commit

Permalink
Bug 1850710 - Add PackingStrategy::ZeroIsEmptyError for mozilla::Resu…
Browse files Browse the repository at this point in the history
…lt. r=emilio,glandium

In order to use mozilla::Result with integral, pointer, or enum, without
consuming extra space or introducing extra instruction, reserve 0 as error value
in the underlying representation.

Differential Revision: https://phabricator.services.mozilla.com/D191537
  • Loading branch information
arai-a committed Nov 3, 2023
1 parent 57ed807 commit 42681ab
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 1 deletion.
41 changes: 40 additions & 1 deletion mfbt/Result.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ enum class PackingStrategy {
NullIsOk,
LowBitTagIsError,
PackedVariant,
ZeroIsEmptyError,
};

template <typename T>
Expand Down Expand Up @@ -185,6 +186,42 @@ class ResultImplementationNullIsOk<V, E, false>
}
};

/**
* Specialization for when the success type is one of integral, pointer, or
* enum, where 0 is unused, and the error type is an empty struct.
*/
template <typename V, typename E>
class ResultImplementation<V, E, PackingStrategy::ZeroIsEmptyError> {
static_assert(std::is_integral_v<V> || std::is_pointer_v<V> ||
std::is_enum_v<V>);
static_assert(std::is_empty_v<E>);

V mValue;

public:
static constexpr PackingStrategy Strategy = PackingStrategy::ZeroIsEmptyError;

explicit constexpr ResultImplementation(V aValue) : mValue(aValue) {}
explicit constexpr ResultImplementation(E aErrorValue) : mValue(V(0)) {}

constexpr bool isOk() const { return mValue != V(0); }

constexpr V inspect() const { return mValue; }
constexpr V unwrap() { return inspect(); }

constexpr E inspectErr() const { return E(); }
constexpr E unwrapErr() { return inspectErr(); }

constexpr void updateAfterTracing(V&& aValue) {
this->~ResultImplementation();
new (this) ResultImplementation(std::move(aValue));
}
constexpr void updateErrorAfterTracing(E&& aErrorValue) {
this->~ResultImplementation();
new (this) ResultImplementation(std::move(aErrorValue));
}
};

/**
* Specialization for when the success type is default-constructible and the
* error type is a value type which can never have the value 0 (as determined by
Expand Down Expand Up @@ -407,7 +444,9 @@ struct HasFreeLSB<T*> {
template <typename V, typename E>
struct SelectResultImpl {
static const PackingStrategy value =
(HasFreeLSB<V>::value && HasFreeLSB<E>::value)
(UnusedZero<V>::value && std::is_empty_v<E>)
? PackingStrategy::ZeroIsEmptyError
: (HasFreeLSB<V>::value && HasFreeLSB<E>::value)
? PackingStrategy::LowBitTagIsError
: (UnusedZero<E>::value && sizeof(E) <= sizeof(uintptr_t))
? PackingStrategy::NullIsOk
Expand Down
151 changes: 151 additions & 0 deletions mfbt/tests/TestResult.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <stdint.h>
#include <string.h>
#include "mozilla/ResultVariant.h"
#include "mozilla/Try.h"
Expand Down Expand Up @@ -656,6 +657,155 @@ static void UniquePtrTest() {
}
}

struct ZeroIsUnusedStructForPointer {
int x = 1;
};
enum class ZeroIsUnusedEnum1 : uint8_t {
V1 = 1,
V2 = 2,
};
enum class ZeroIsUnusedEnum2 : uint16_t {
V1 = 1,
V2 = 2,
};
enum class ZeroIsUnusedEnum4 : uint32_t {
V1 = 1,
V2 = 2,
};
enum class ZeroIsUnusedEnum8 : uint64_t {
V1 = 1,
V2 = 2,
};
struct EmptyErrorStruct {};

template <>
struct mozilla::detail::UnusedZero<ZeroIsUnusedStructForPointer*> {
static const bool value = true;
};
template <>
struct mozilla::detail::UnusedZero<ZeroIsUnusedEnum1> {
static const bool value = true;
};
template <>
struct mozilla::detail::UnusedZero<ZeroIsUnusedEnum2> {
static const bool value = true;
};
template <>
struct mozilla::detail::UnusedZero<ZeroIsUnusedEnum4> {
static const bool value = true;
};
template <>
struct mozilla::detail::UnusedZero<ZeroIsUnusedEnum8> {
static const bool value = true;
};

static void ZeroIsEmptyErrorTest() {
{
ZeroIsUnusedStructForPointer s;

using V = ZeroIsUnusedStructForPointer*;

mozilla::Result<V, EmptyErrorStruct> result(&s);
MOZ_RELEASE_ASSERT(sizeof(result) == sizeof(V));

MOZ_RELEASE_ASSERT(result.isOk());
MOZ_RELEASE_ASSERT(result.inspect() == &s);
}

{
using V = ZeroIsUnusedStructForPointer*;

mozilla::Result<V, EmptyErrorStruct> result(Err(EmptyErrorStruct{}));

MOZ_RELEASE_ASSERT(result.isErr());
MOZ_RELEASE_ASSERT(*reinterpret_cast<V*>(&result) == nullptr);
}

{
ZeroIsUnusedEnum1 e = ZeroIsUnusedEnum1::V1;

using V = ZeroIsUnusedEnum1;

mozilla::Result<V, EmptyErrorStruct> result(e);
MOZ_RELEASE_ASSERT(sizeof(result) == sizeof(V));

MOZ_RELEASE_ASSERT(result.isOk());
MOZ_RELEASE_ASSERT(result.inspect() == e);
}

{
using V = ZeroIsUnusedEnum1;

mozilla::Result<V, EmptyErrorStruct> result(Err(EmptyErrorStruct()));

MOZ_RELEASE_ASSERT(result.isErr());
MOZ_RELEASE_ASSERT(*reinterpret_cast<uint8_t*>(&result) == 0);
}

{
ZeroIsUnusedEnum2 e = ZeroIsUnusedEnum2::V1;

using V = ZeroIsUnusedEnum2;

mozilla::Result<V, EmptyErrorStruct> result(e);
MOZ_RELEASE_ASSERT(sizeof(result) == sizeof(V));

MOZ_RELEASE_ASSERT(result.isOk());
MOZ_RELEASE_ASSERT(result.inspect() == e);
}

{
using V = ZeroIsUnusedEnum2;

mozilla::Result<V, EmptyErrorStruct> result(Err(EmptyErrorStruct()));

MOZ_RELEASE_ASSERT(result.isErr());
MOZ_RELEASE_ASSERT(*reinterpret_cast<uint16_t*>(&result) == 0);
}

{
ZeroIsUnusedEnum4 e = ZeroIsUnusedEnum4::V1;

using V = ZeroIsUnusedEnum4;

mozilla::Result<V, EmptyErrorStruct> result(e);
MOZ_RELEASE_ASSERT(sizeof(result) == sizeof(V));

MOZ_RELEASE_ASSERT(result.isOk());
MOZ_RELEASE_ASSERT(result.inspect() == e);
}

{
using V = ZeroIsUnusedEnum4;

mozilla::Result<V, EmptyErrorStruct> result(Err(EmptyErrorStruct()));

MOZ_RELEASE_ASSERT(result.isErr());
MOZ_RELEASE_ASSERT(*reinterpret_cast<uint32_t*>(&result) == 0);
}

{
ZeroIsUnusedEnum8 e = ZeroIsUnusedEnum8::V1;

using V = ZeroIsUnusedEnum8;

mozilla::Result<V, EmptyErrorStruct> result(e);
MOZ_RELEASE_ASSERT(sizeof(result) == sizeof(V));

MOZ_RELEASE_ASSERT(result.isOk());
MOZ_RELEASE_ASSERT(result.inspect() == e);
}

{
using V = ZeroIsUnusedEnum8;

mozilla::Result<V, EmptyErrorStruct> result(Err(EmptyErrorStruct()));

MOZ_RELEASE_ASSERT(result.isErr());
MOZ_RELEASE_ASSERT(*reinterpret_cast<uint64_t*>(&result) == 0);
}
}

/* * */

int main() {
Expand All @@ -668,5 +818,6 @@ int main() {
OrElseTest();
AndThenTest();
UniquePtrTest();
ZeroIsEmptyErrorTest();
return 0;
}

0 comments on commit 42681ab

Please sign in to comment.