From: Serge Guelton Date: Wed, 13 Feb 2019 09:31:22 +0000 (+0000) Subject: Make llvm::Optional trivially copyable when T is trivially copyable X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=42f63a9ef4a41690ce523189ce5436f98d947715;p=llvm Make llvm::Optional trivially copyable when T is trivially copyable This is an ever-recurring issue (see https://bugs.llvm.org/show_bug.cgi?id=39427 and https://bugs.llvm.org/show_bug.cgi?id=35978) but I believe that thanks to https://reviews.llvm.org/D54472 we can now ship a decent implementation of this. Basically the fact that llvm::is_trivially_copyable has a consistent behavior across compilers should prevent any ABI issue, and using in-place new instead of memcpy should keep compiler bugs away. Differential Revision: https://reviews.llvm.org/D57097 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@353927 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/llvm/ADT/Optional.h b/include/llvm/ADT/Optional.h index 25a3185064f..35ce0ce7010 100644 --- a/include/llvm/ADT/Optional.h +++ b/include/llvm/ADT/Optional.h @@ -29,36 +29,77 @@ namespace llvm { class raw_ostream; namespace optional_detail { + /// Storage for any type. -template ::value> struct OptionalStorage { +// +template struct OptionalTrivialStorage { AlignedCharArrayUnion storage; bool hasVal = false; + OptionalTrivialStorage() = default; + OptionalTrivialStorage(OptionalTrivialStorage const &) = default; + OptionalTrivialStorage(OptionalTrivialStorage &&) = default; + OptionalTrivialStorage &operator=(OptionalTrivialStorage const &) = default; + OptionalTrivialStorage &operator=(OptionalTrivialStorage &&) = default; + ~OptionalTrivialStorage() = default; + + OptionalTrivialStorage(const T &y) : hasVal(true) { + new (storage.buffer) T(y); + } + OptionalTrivialStorage(T &&y) : hasVal(true) { + new (storage.buffer) T(std::move(y)); + } - OptionalStorage() = default; + OptionalTrivialStorage &operator=(const T &y) { + new (storage.buffer) T(y); + hasVal = true; + return *this; + } + OptionalTrivialStorage &operator=(T &&y) { + new (storage.buffer) T(std::move(y)); + hasVal = true; + return *this; + } - OptionalStorage(const T &y) : hasVal(true) { new (storage.buffer) T(y); } - OptionalStorage(const OptionalStorage &O) : hasVal(O.hasVal) { - if (hasVal) - new (storage.buffer) T(*O.getPointer()); + T *getPointer() { + assert(hasVal); + return reinterpret_cast(storage.buffer); } - OptionalStorage(T &&y) : hasVal(true) { - new (storage.buffer) T(std::forward(y)); + const T *getPointer() const { + assert(hasVal); + return reinterpret_cast(storage.buffer); } - OptionalStorage(OptionalStorage &&O) : hasVal(O.hasVal) { + void reset() { hasVal = false; } +}; + +template struct OptionalStorage : OptionalTrivialStorage { + + OptionalStorage() = default; + + OptionalStorage(const T &y) : OptionalTrivialStorage(y) {} + OptionalStorage(T &&y) : OptionalTrivialStorage(std::move(y)) {} + + OptionalStorage(const OptionalStorage &O) : OptionalTrivialStorage() { + this->hasVal = O.hasVal; + if (this->hasVal) + new (this->storage.buffer) T(*O.getPointer()); + } + + OptionalStorage(OptionalStorage &&O) : OptionalTrivialStorage() { + this->hasVal = O.hasVal; if (O.hasVal) { - new (storage.buffer) T(std::move(*O.getPointer())); + new (this->storage.buffer) T(std::move(*O.getPointer())); } } OptionalStorage &operator=(T &&y) { - if (hasVal) - *getPointer() = std::move(y); + if (this->hasVal) + *this->getPointer() = std::move(y); else { - new (storage.buffer) T(std::move(y)); - hasVal = true; + OptionalTrivialStorage::operator=(std::move(y)); } return *this; } + OptionalStorage &operator=(OptionalStorage &&O) { if (!O.hasVal) reset(); @@ -74,11 +115,10 @@ template ::value> struct OptionalSto // requirements (notably: the existence of a default ctor) when implemented // in that way. Careful SFINAE to avoid such pitfalls would be required. OptionalStorage &operator=(const T &y) { - if (hasVal) - *getPointer() = y; + if (this->hasVal) + *this->getPointer() = y; else { - new (storage.buffer) T(y); - hasVal = true; + OptionalTrivialStorage::operator=(y); } return *this; } @@ -93,26 +133,19 @@ template ::value> struct OptionalSto ~OptionalStorage() { reset(); } void reset() { - if (hasVal) { - (*getPointer()).~T(); - hasVal = false; + if (this->hasVal) { + (*this->getPointer()).~T(); + OptionalTrivialStorage::reset(); } } - - T *getPointer() { - assert(hasVal); - return reinterpret_cast(storage.buffer); - } - const T *getPointer() const { - assert(hasVal); - return reinterpret_cast(storage.buffer); - } }; } // namespace optional_detail template class Optional { - optional_detail::OptionalStorage Storage; + typename std::conditional::value, + optional_detail::OptionalTrivialStorage, + optional_detail::OptionalStorage>::type Storage; public: using value_type = T; diff --git a/unittests/ADT/OptionalTest.cpp b/unittests/ADT/OptionalTest.cpp index 9d6d5d29fe0..8d49b3dc22a 100644 --- a/unittests/ADT/OptionalTest.cpp +++ b/unittests/ADT/OptionalTest.cpp @@ -16,6 +16,12 @@ using namespace llvm; namespace { +static_assert(llvm::is_trivially_copyable>::value, + "trivially copyable"); + +static_assert(llvm::is_trivially_copyable>>::value, + "trivially copyable"); + struct NonDefaultConstructible { static unsigned CopyConstructions; static unsigned Destructions; @@ -43,6 +49,10 @@ unsigned NonDefaultConstructible::CopyConstructions = 0; unsigned NonDefaultConstructible::Destructions = 0; unsigned NonDefaultConstructible::CopyAssignments = 0; +static_assert( + !llvm::is_trivially_copyable>::value, + "not trivially copyable"); + // Test fixture class OptionalTest : public testing::Test { }; @@ -201,6 +211,10 @@ struct MultiArgConstructor { }; unsigned MultiArgConstructor::Destructions = 0; +static_assert( + !llvm::is_trivially_copyable>::value, + "not trivially copyable"); + TEST_F(OptionalTest, Emplace) { MultiArgConstructor::ResetCounts(); Optional A; @@ -248,6 +262,9 @@ unsigned MoveOnly::MoveConstructions = 0; unsigned MoveOnly::Destructions = 0; unsigned MoveOnly::MoveAssignments = 0; +static_assert(!llvm::is_trivially_copyable>::value, + "not trivially copyable"); + TEST_F(OptionalTest, MoveOnlyNull) { MoveOnly::ResetCounts(); Optional O; @@ -349,6 +366,9 @@ private: unsigned Immovable::Constructions = 0; unsigned Immovable::Destructions = 0; +static_assert(!llvm::is_trivially_copyable>::value, + "not trivially copyable"); + TEST_F(OptionalTest, ImmovableEmplace) { Optional A; Immovable::ResetCounts();