[llvm] r336156 - [ADT] Add llvm::unique_function which is like std::function but
Justin Bogner via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 3 16:19:46 PDT 2018
Chandler Carruth via llvm-commits <llvm-commits at lists.llvm.org> writes:
> On Tue, Jul 3, 2018 at 12:24 PM Aditya Nandakumar <
> aditya_nandakumar at apple.com> wrote:
>
>> Hi Chandler,
>>
>> This seems to be failing on MacOS (The UnitTest segfaults consistently on
>> our bots)
>> I see this
>>
>> Assertion failed: ((reinterpret_cast<uintptr_t>(P) & ~((uintptr_t)-1 <<
>> NumLowBitsAvailable)) == 0 && “Alignment not satisfied for an actual
>> function pointer!"), function getAsVoidPointer, file
>> llvm/include/llvm/Support/PointerLikeTypeTraits.h, line 129
>>
>> Can you please take a look?
>>
>
> Which bots btw? I can't find this failure on any of the build bot
> dashboards I know how to find....
I think Aditya must have been looking at some internal bots, because I
don't see this on any public bots either.
I do see a segfault if I build with -Os (-DCMAKE_BUILD_TYPE=MinSizeRel)
though, and asan points at a null dereference:
==71025==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x0001064449c8 bp 0x7ffee9b42a70 sp 0x7ffee9b429a0 T0)
==71025==The signal is caused by a READ memory access.
==71025==Hint: address points to the zero page.
#0 0x1064449c7 in llvm::unique_function<int (int, int)>::unique_function(llvm::unique_function<int (int, int)>&&) FunctionExtras.h:204
#1 0x106443351 in llvm::unique_function<int (int, int)>::operator=(llvm::unique_function<int (int, int)>&&) FunctionExtras.h:225
#2 0x1064418b3 in (anonymous namespace)::UniqueFunctionTest_Basic_Test::TestBody() FunctionExtrasTest.cpp:30
#3 0x106a5b7c6 in testing::Test::Run() gtest.cc:2474
#4 0x106a5d4a9 in testing::TestInfo::Run() gtest.cc:2656
#5 0x106a5ef5f in testing::TestCase::Run() gtest.cc:2774
#6 0x106a71a22 in testing::internal::UnitTestImpl::RunAllTests() gtest.cc:4649
#7 0x106a712a1 in testing::UnitTest::Run() gtest.cc:4257
#8 0x106a46cf1 in main gtest.h:2233
#9 0x7fff74aeb014 in start (libdyld.dylib:x86_64+0x1014)
==71025==Register values:
rax = 0x00007ffee9b42e80 rbx = 0x00007ffee9b429e0 rcx = 0x0000100000000000 rdx = 0x0000100020c88971
rdi = 0x00007ffee9b42d00 rsi = 0x00007ffee9b42e80 rbp = 0x00007ffee9b42a70 rsp = 0x00007ffee9b429a0
r8 = 0x00007ffee9b42e30 r9 = 0xf2f200f2f2f2f8f2 r10 = 0xf2f2f8f2f8f2f2f2 r11 = 0xffffffffffffff40
r12 = 0x00001fffdd3685d3 r13 = 0x74db8548fb894853 r14 = 0x00007ffee9b42d00 r15 = 0x00007ffee9b429a0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV FunctionExtras.h:204 in llvm::unique_function<int (int, int)>::unique_function(llvm::unique_function<int (int, int)>&&
>>
>> > On Jul 2, 2018, at 4:57 PM, Chandler Carruth via llvm-commits <
>> llvm-commits at lists.llvm.org> wrote:
>> >
>> > Author: chandlerc
>> > Date: Mon Jul 2 16:57:29 2018
>> > New Revision: 336156
>> >
>> > URL: http://llvm.org/viewvc/llvm-project?rev=336156&view=rev
>> > Log:
>> > [ADT] Add llvm::unique_function which is like std::function but
>> > supporting move-only closures.
>> >
>> > Most of the core optimizations for std::function are here plus
>> > a potentially novel one that detects trivially movable and destroyable
>> > functors and implements those with fewer indirections.
>> >
>> > This is especially useful as we start trying to add concurrency
>> > primitives as those often end up with move-only types (futures,
>> > promises, etc) and wanting them to work through lambdas.
>> >
>> > As further work, we could add better support for things like
>> const-qualified
>> > operator()s to support more algorithms, and r-value ref qualified
>> operator()s
>> > to model call-once. None of that is here though.
>> >
>> > We can also provide our own llvm::function that has some of the
>> optimizations
>> > used in this class, but with copy semantics instead of move semantics.
>> >
>> > This is motivated by increasing usage of things like executors and the
>> task
>> > queue where it is useful to embed move-only types like a std::promise
>> within
>> > a type erased function. That isn't possible without this version of a
>> type
>> > erased function.
>> >
>> > Differential Revision: https://reviews.llvm.org/D48349
>> >
>> > Added:
>> > llvm/trunk/include/llvm/ADT/FunctionExtras.h
>> > llvm/trunk/unittests/ADT/FunctionExtrasTest.cpp
>> > Modified:
>> > llvm/trunk/include/llvm/Support/Compiler.h
>> > llvm/trunk/include/llvm/Support/PointerLikeTypeTraits.h
>> > llvm/trunk/unittests/ADT/CMakeLists.txt
>> >
>> > Added: llvm/trunk/include/llvm/ADT/FunctionExtras.h
>> > URL:
>>
> http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/ADT/FunctionExtras.h?rev=336156&view=auto
>> >
>> ==============================================================================
>> > --- llvm/trunk/include/llvm/ADT/FunctionExtras.h (added)
>> > +++ llvm/trunk/include/llvm/ADT/FunctionExtras.h Mon Jul 2 16:57:29 2018
>> > @@ -0,0 +1,274 @@
>> > +//===- FunctionExtras.h - Function type erasure utilities -------*- C++
>> -*-===//
>> > +//
>> > +// The LLVM Compiler Infrastructure
>> > +//
>> > +// This file is distributed under the University of Illinois Open Source
>> > +// License. See LICENSE.TXT for details.
>> > +//
>> >
>>
> +//===----------------------------------------------------------------------===//
>> > +/// \file
>> > +/// This file provides a collection of function (or more generally,
>> callable)
>> > +/// type erasure utilities supplementing those provided by the standard
>> library
>> > +/// in `<function>`.
>> > +///
>> > +/// It provides `unique_function`, which works like `std::function` but
>> supports
>> > +/// move-only callable objects.
>> > +///
>> > +/// Future plans:
>> > +/// - Add a `function` that provides const, volatile, and ref-qualified
>> support,
>> > +/// which doesn't work with `std::function`.
>> > +/// - Provide support for specifying multiple signatures to type erase
>> callable
>> > +/// objects with an overload set, such as those produced by generic
>> lambdas.
>> > +/// - Expand to include a copyable utility that directly replaces
>> std::function
>> > +/// but brings the above improvements.
>> > +///
>> > +/// Note that LLVM's utilities are greatly simplified by not supporting
>> > +/// allocators.
>> > +///
>> > +/// If the standard library ever begins to provide comparable
>> facilities we can
>> > +/// consider switching to those.
>> > +///
>> >
>>
> +//===----------------------------------------------------------------------===//
>> > +
>> > +#ifndef LLVM_ADT_FUNCTION_EXTRAS_H
>> > +#define LLVM_ADT_FUNCTION_EXTRAS_H
>> > +
>> > +#include "llvm/ADT/PointerIntPair.h"
>> > +#include "llvm/ADT/PointerUnion.h"
>> > +#include <memory>
>> > +#include <type_traits>
>> > +
>> > +namespace llvm {
>> > +
>> > +template <typename FunctionT> class unique_function;
>> > +
>> > +template <typename ReturnT, typename... ParamTs>
>> > +class unique_function<ReturnT(ParamTs...)> {
>> > + static constexpr int InlineStorageSize = sizeof(void *) * 3;
>> > +
>> > + // Provide a type function to map parameters that won't observe extra
>> copies
>> > + // or moves and which are small enough to likely pass in register to
>> values
>> > + // and all other types to l-value reference types. We use this to
>> compute the
>> > + // types used in our erased call utility to minimize copies and moves
>> unless
>> > + // doing so would force things unnecessarily into memory.
>> > + //
>> > + // The heuristic used is related to common ABI register passing
>> conventions.
>> > + // It doesn't have to be exact though, and in one way it is more
>> strict
>> > + // because we want to still be able to observe either moves *or*
>> copies.
>> > + template <typename T>
>> > + using AdjustedParamT = typename std::conditional<
>> > + !std::is_reference<T>::value &&
>> > + std::is_trivially_copy_constructible<T>::value &&
>> > + std::is_trivially_move_constructible<T>::value &&
>> > + sizeof(T) <= (2 * sizeof(void *)),
>> > + T, T &>::type;
>> > +
>> > + // The type of the erased function pointer we use as a callback to
>> dispatch to
>> > + // the stored callable when it is trivial to move and destroy.
>> > + using CallPtrT = ReturnT (*)(void *CallableAddr,
>> > + AdjustedParamT<ParamTs>... Params);
>> > + using MovePtrT = void (*)(void *LHSCallableAddr, void
>> *RHSCallableAddr);
>> > + using DestroyPtrT = void (*)(void *CallableAddr);
>> > +
>> > + /// A struct we use to aggregate three callbacks when we need full
>> set of
>> > + /// operations.
>> > + struct NonTrivialCallbacks {
>> > + CallPtrT CallPtr;
>> > + MovePtrT MovePtr;
>> > + DestroyPtrT DestroyPtr;
>> > + };
>> > +
>> > + // Now we can create a pointer union between either a direct, trivial
>> call
>> > + // pointer and a pointer to a static struct of the call, move, and
>> destroy
>> > + // pointers. We do this to keep the footprint in this object a single
>> pointer
>> > + // while supporting all the necessary type-erased operation.
>> > + using CallbackPointerUnionT = PointerUnion<CallPtrT,
>> NonTrivialCallbacks *>;
>> > +
>> > + // The main storage buffer. This will either have a pointer to
>> out-of-line
>> > + // storage or an inline buffer storing the callable.
>> > + union StorageUnionT {
>> > + // For out-of-line storage we keep a pointer to the underlying
>> storage and
>> > + // the size. This is enough to deallocate the memory.
>> > + struct OutOfLineStorageT {
>> > + void *StoragePtr;
>> > + size_t Size;
>> > + size_t Alignment;
>> > + } OutOfLineStorage;
>> > + static_assert(
>> > + sizeof(OutOfLineStorageT) <= InlineStorageSize,
>> > + "Should always use all of the out-of-line storage for inline
>> storage!");
>> > +
>> > + // For in-line storage, we just provide an aligned character
>> buffer. We
>> > + // provide three pointers worth of storage here.
>> > + typename std::aligned_storage<InlineStorageSize, alignof(void
>> *)>::type
>> > + InlineStorage;
>> > + } StorageUnion;
>> > +
>> > + // A compressed pointer to either our dispatching callback or our
>> table of
>> > + // dispatching callbacks and the flag for whether the callable itself
>> is
>> > + // stored inline or not.
>> > + PointerIntPair<CallbackPointerUnionT, 1, bool> CallbackAndInlineFlag;
>> > +
>> > + bool isInlineStorage() const { return CallbackAndInlineFlag.getInt();
>> }
>> > +
>> > + bool isTrivialCallback() const {
>> > + return CallbackAndInlineFlag.getPointer().template is<CallPtrT>();
>> > + }
>> > +
>> > + CallPtrT getTrivialCallback() const {
>> > + return CallbackAndInlineFlag.getPointer().template get<CallPtrT>();
>> > + }
>> > +
>> > + NonTrivialCallbacks *getNonTrivialCallbacks() const {
>> > + return CallbackAndInlineFlag.getPointer()
>> > + .template get<NonTrivialCallbacks *>();
>> > + }
>> > +
>> > + void *getInlineStorage() { return &StorageUnion.InlineStorage; }
>> > +
>> > + void *getOutOfLineStorage() {
>> > + return StorageUnion.OutOfLineStorage.StoragePtr;
>> > + }
>> > + size_t getOutOfLineStorageSize() const {
>> > + return StorageUnion.OutOfLineStorage.Size;
>> > + }
>> > + size_t getOutOfLineStorageAlignment() const {
>> > + return StorageUnion.OutOfLineStorage.Alignment;
>> > + }
>> > +
>> > + void setOutOfLineStorage(void *Ptr, size_t Size, size_t Alignment) {
>> > + StorageUnion.OutOfLineStorage = {Ptr, Size, Alignment};
>> > + }
>> > +
>> > + template <typename CallableT>
>> > + static ReturnT CallImpl(void *CallableAddr,
>> > + AdjustedParamT<ParamTs>... Params) {
>> > + return (*reinterpret_cast<CallableT *>(CallableAddr))(
>> > + std::forward<ParamTs>(Params)...);
>> > + }
>> > +
>> > + template <typename CallableT>
>> > + static void MoveImpl(void *LHSCallableAddr, void *RHSCallableAddr)
>> noexcept {
>> > + new (LHSCallableAddr)
>> > + CallableT(std::move(*reinterpret_cast<CallableT
>> *>(RHSCallableAddr)));
>> > + }
>> > +
>> > + template <typename CallableT>
>> > + static void DestroyImpl(void *CallableAddr) noexcept {
>> > + reinterpret_cast<CallableT *>(CallableAddr)->~CallableT();
>> > + }
>> > +
>> > +public:
>> > + unique_function() = default;
>> > + unique_function(std::nullptr_t /*null_callable*/) {}
>> > +
>> > + ~unique_function() {
>> > + if (!CallbackAndInlineFlag.getPointer())
>> > + return;
>> > +
>> > + // Cache this value so we don't re-check it after type-erased
>> operations.
>> > + bool IsInlineStorage = isInlineStorage();
>> > +
>> > + if (!isTrivialCallback())
>> > + getNonTrivialCallbacks()->DestroyPtr(
>> > + IsInlineStorage ? getInlineStorage() : getOutOfLineStorage());
>> > +
>> > + if (!IsInlineStorage)
>> > + deallocate_buffer(getOutOfLineStorage(),
>> getOutOfLineStorageSize(),
>> > + getOutOfLineStorageAlignment());
>> > + }
>> > +
>> > + unique_function(unique_function &&RHS) noexcept {
>> > + // Copy the callback and inline flag.
>> > + CallbackAndInlineFlag = RHS.CallbackAndInlineFlag;
>> > +
>> > + // If the RHS is empty, just copying the above is sufficient.
>> > + if (!RHS)
>> > + return;
>> > +
>> > + if (!isInlineStorage()) {
>> > + // The out-of-line case is easiest to move.
>> > + StorageUnion.OutOfLineStorage = RHS.StorageUnion.OutOfLineStorage;
>> > + } else if (isTrivialCallback()) {
>> > + // Move is trivial, just memcpy the bytes across.
>> > + memcpy(getInlineStorage(), RHS.getInlineStorage(),
>> InlineStorageSize);
>> > + } else {
>> > + // Non-trivial move, so dispatch to a type-erased implementation.
>> > + getNonTrivialCallbacks()->MovePtr(getInlineStorage(),
>> > + RHS.getInlineStorage());
>> > + }
>> > +
>> > + // Clear the old callback and inline flag to get back to as-if-null.
>> > + RHS.CallbackAndInlineFlag = {};
>> > +
>> > +#ifndef NDEBUG
>> > + // In debug builds, we also scribble across the rest of the storage.
>> > + memset(RHS.getInlineStorage(), 0xAD, InlineStorageSize);
>> > +#endif
>> > + }
>> > +
>> > + unique_function &operator=(unique_function &&RHS) noexcept {
>> > + if (this == &RHS)
>> > + return *this;
>> > +
>> > + // Because we don't try to provide any exception safety guarantees
>> we can
>> > + // implement move assignment very simply by first destroying the
>> current
>> > + // object and then move-constructing over top of it.
>> > + this->~unique_function();
>> > + new (this) unique_function(std::move(RHS));
>> > + return *this;
>> > + }
>> > +
>> > + template <typename CallableT> unique_function(CallableT Callable) {
>> > + bool IsInlineStorage = true;
>> > + void *CallableAddr = getInlineStorage();
>> > + if (sizeof(CallableT) > InlineStorageSize ||
>> > + alignof(CallableT) >
>> alignof(decltype(StorageUnion.InlineStorage))) {
>> > + IsInlineStorage = false;
>> > + // Allocate out-of-line storage. FIXME: Use an explicit alignment
>> > + // parameter in C++17 mode.
>> > + auto Size = sizeof(CallableT);
>> > + auto Alignment = alignof(CallableT);
>> > + CallableAddr = allocate_buffer(Size, Alignment);
>> > + setOutOfLineStorage(CallableAddr, Size, Alignment);
>> > + }
>> > +
>> > + // Now move into the storage.
>> > + new (CallableAddr) CallableT(std::move(Callable));
>> > +
>> > + // See if we can create a trivial callback.
>> > + // FIXME: we should use constexpr if here and below to avoid
>> instantiating
>> > + // the non-trivial static objects when unnecessary. While the
>> linker should
>> > + // remove them, it is still wasteful.
>> > + if (std::is_trivially_move_constructible<CallableT>::value &&
>> > + std::is_trivially_destructible<CallableT>::value) {
>> > + CallbackAndInlineFlag = {&CallImpl<CallableT>, IsInlineStorage};
>> > + return;
>> > + }
>> > +
>> > + // Otherwise, we need to point at an object with a vtable that
>> contains all
>> > + // the different type erased behaviors needed. Create a static
>> instance of
>> > + // the derived type here and then use a pointer to that.
>> > + static NonTrivialCallbacks Callbacks = {
>> > + &CallImpl<CallableT>, &MoveImpl<CallableT>,
>> &DestroyImpl<CallableT>};
>> > +
>> > + CallbackAndInlineFlag = {&Callbacks, IsInlineStorage};
>> > + }
>> > +
>> > + ReturnT operator()(ParamTs... Params) {
>> > + void *CallableAddr =
>> > + isInlineStorage() ? getInlineStorage() : getOutOfLineStorage();
>> > +
>> > + return (isTrivialCallback()
>> > + ? getTrivialCallback()
>> > + : getNonTrivialCallbacks()->CallPtr)(CallableAddr,
>> Params...);
>> > + }
>> > +
>> > + explicit operator bool() const {
>> > + return (bool)CallbackAndInlineFlag.getPointer();
>> > + }
>> > +};
>> > +
>> > +} // end namespace llvm
>> > +
>> > +#endif // LLVM_ADT_FUNCTION_H
>> >
>> > Modified: llvm/trunk/include/llvm/Support/Compiler.h
>> > URL:
>>
> http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Support/Compiler.h?rev=336156&r1=336155&r2=336156&view=diff
>> >
>> ==============================================================================
>> > --- llvm/trunk/include/llvm/Support/Compiler.h (original)
>> > +++ llvm/trunk/include/llvm/Support/Compiler.h Mon Jul 2 16:57:29 2018
>> > @@ -17,6 +17,9 @@
>> >
>> > #include "llvm/Config/llvm-config.h"
>> >
>> > +#include <new>
>> > +#include <stddef.h>
>> > +
>> > #if defined(_MSC_VER)
>> > #include <sal.h>
>> > #endif
>> > @@ -503,4 +506,46 @@ void AnnotateIgnoreWritesEnd(const char
>> > #define LLVM_ENABLE_EXCEPTIONS 1
>> > #endif
>> >
>> > +namespace llvm {
>> > +
>> > +/// Allocate a buffer of memory with the given size and alignment.
>> > +///
>> > +/// When the compiler supports aligned operator new, this will use it
>> to to
>> > +/// handle even over-aligned allocations.
>> > +///
>> > +/// However, this doesn't make any attempt to leverage the fancier
>> techniques
>> > +/// like posix_memalign due to portability. It is mostly intended to
>> allow
>> > +/// compatibility with platforms that, after aligned allocation was
>> added, use
>> > +/// reduced default alignment.
>> > +inline void *allocate_buffer(size_t Size, size_t Alignment) {
>> > + return ::operator new(Size
>> > +#if __cpp_aligned_new
>> > + ,
>> > + std::align_val_t(Alignment)
>> > +#endif
>> > + );
>> > +}
>> > +
>> > +/// Deallocate a buffer of memory with the given size and alignment.
>> > +///
>> > +/// If supported, this will used the sized delete operator. Also if
>> supported,
>> > +/// this will pass the alignment to the delete operator.
>> > +///
>> > +/// The pointer must have been allocated with the corresponding new
>> operator,
>> > +/// most likely using the above helper.
>> > +inline void deallocate_buffer(void *Ptr, size_t Size, size_t Alignment)
>> {
>> > + ::operator delete(Ptr
>> > +#if __cpp_sized_deallocation
>> > + ,
>> > + Size
>> > +#endif
>> > +#if __cpp_aligned_new
>> > + ,
>> > + std::align_val_t(Alignment)
>> > +#endif
>> > + );
>> > +}
>> > +
>> > +} // End namespace llvm
>> > +
>> > #endif
>> >
>> > Modified: llvm/trunk/include/llvm/Support/PointerLikeTypeTraits.h
>> > URL:
>>
> http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Support/PointerLikeTypeTraits.h?rev=336156&r1=336155&r2=336156&view=diff
>> >
>> ==============================================================================
>> > --- llvm/trunk/include/llvm/Support/PointerLikeTypeTraits.h (original)
>> > +++ llvm/trunk/include/llvm/Support/PointerLikeTypeTraits.h Mon Jul 2
>> 16:57:29 2018
>> > @@ -16,6 +16,7 @@
>> > #define LLVM_SUPPORT_POINTERLIKETYPETRAITS_H
>> >
>> > #include "llvm/Support/DataTypes.h"
>> > +#include <assert.h>
>> > #include <type_traits>
>> >
>> > namespace llvm {
>> > @@ -111,6 +112,39 @@ template <> struct PointerLikeTypeTraits
>> > enum { NumLowBitsAvailable = 0 };
>> > };
>> >
>> > +/// Provide suitable custom traits struct for function pointers.
>> > +///
>> > +/// Function pointers can't be directly given these traits as functions
>> can't
>> > +/// have their alignment computed with `alignof` and we need different
>> casting.
>> > +///
>> > +/// To rely on higher alignment for a specialized use, you can provide a
>> > +/// customized form of this template explicitly with higher alignment,
>> and
>> > +/// potentially use alignment attributes on functions to satisfy that.
>> > +template <int Alignment, typename FunctionPointerT>
>> > +struct FunctionPointerLikeTypeTraits {
>> > + enum { NumLowBitsAvailable = detail::ConstantLog2<Alignment>::value };
>> > + static inline void *getAsVoidPointer(FunctionPointerT P) {
>> > + assert((reinterpret_cast<uintptr_t>(P) &
>> > + ~((uintptr_t)-1 << NumLowBitsAvailable)) == 0 &&
>> > + "Alignment not satisfied for an actual function pointer!");
>> > + return reinterpret_cast<void *>(P);
>> > + }
>> > + static inline FunctionPointerT getFromVoidPointer(void *P) {
>> > + return reinterpret_cast<FunctionPointerT>(P);
>> > + }
>> > +};
>> > +
>> > +/// Provide a default specialization for function pointers that assumes
>> 4-byte
>> > +/// alignment.
>> > +///
>> > +/// We assume here that functions used with this are always at least
>> 4-byte
>> > +/// aligned. This means that, for example, thumb functions won't work
>> or systems
>> > +/// with weird unaligned function pointers won't work. But all
>> practical systems
>> > +/// we support satisfy this requirement.
>> > +template <typename ReturnT, typename... ParamTs>
>> > +struct PointerLikeTypeTraits<ReturnT (*)(ParamTs...)>
>> > + : FunctionPointerLikeTypeTraits<4, ReturnT (*)(ParamTs...)> {};
>> > +
>> > } // end namespace llvm
>> >
>> > #endif
>> >
>> > Modified: llvm/trunk/unittests/ADT/CMakeLists.txt
>> > URL:
>>
> http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/ADT/CMakeLists.txt?rev=336156&r1=336155&r2=336156&view=diff
>> >
>> ==============================================================================
>> > --- llvm/trunk/unittests/ADT/CMakeLists.txt (original)
>> > +++ llvm/trunk/unittests/ADT/CMakeLists.txt Mon Jul 2 16:57:29 2018
>> > @@ -18,6 +18,7 @@ add_llvm_unittest(ADTTests
>> > DepthFirstIteratorTest.cpp
>> > EquivalenceClassesTest.cpp
>> > FoldingSet.cpp
>> > + FunctionExtrasTest.cpp
>> > FunctionRefTest.cpp
>> > HashingTest.cpp
>> > IListBaseTest.cpp
>> >
>> > Added: llvm/trunk/unittests/ADT/FunctionExtrasTest.cpp
>> > URL:
>>
> http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/ADT/FunctionExtrasTest.cpp?rev=336156&view=auto
>> >
>> ==============================================================================
>> > --- llvm/trunk/unittests/ADT/FunctionExtrasTest.cpp (added)
>> > +++ llvm/trunk/unittests/ADT/FunctionExtrasTest.cpp Mon Jul 2 16:57:29
>> 2018
>> > @@ -0,0 +1,228 @@
>> > +//===- FunctionExtrasTest.cpp - Unit tests for function type erasure
>> ------===//
>> > +//
>> > +// The LLVM Compiler Infrastructure
>> > +//
>> > +// This file is distributed under the University of Illinois Open Source
>> > +// License. See LICENSE.TXT for details.
>> > +//
>> >
>>
> +//===----------------------------------------------------------------------===//
>> > +
>> > +#include "llvm/ADT/FunctionExtras.h"
>> > +#include "gtest/gtest.h"
>> > +
>> > +#include <memory>
>> > +
>> > +using namespace llvm;
>> > +
>> > +namespace {
>> > +
>> > +TEST(UniqueFunctionTest, Basic) {
>> > + unique_function<int(int, int)> Sum = [](int A, int B) { return A + B;
>> };
>> > + EXPECT_EQ(Sum(1, 2), 3);
>> > +
>> > + unique_function<int(int, int)> Sum2 = std::move(Sum);
>> > + EXPECT_EQ(Sum2(1, 2), 3);
>> > +
>> > + unique_function<int(int, int)> Sum3 = [](int A, int B) { return A +
>> B; };
>> > + Sum2 = std::move(Sum3);
>> > + EXPECT_EQ(Sum2(1, 2), 3);
>> > +
>> > + Sum2 = unique_function<int(int, int)>([](int A, int B) { return A +
>> B; });
>> > + EXPECT_EQ(Sum2(1, 2), 3);
>> > +
>> > + // Explicit self-move test.
>> > + *&Sum2 = std::move(Sum2);
>> > + EXPECT_EQ(Sum2(1, 2), 3);
>> > +
>> > + Sum2 = unique_function<int(int, int)>();
>> > + EXPECT_FALSE(Sum2);
>> > +
>> > + // Make sure we can forward through l-value reference parameters.
>> > + unique_function<void(int &)> Inc = [](int &X) { ++X; };
>> > + int X = 42;
>> > + Inc(X);
>> > + EXPECT_EQ(X, 43);
>> > +
>> > + // Make sure we can forward through r-value reference parameters with
>> > + // move-only types.
>> > + unique_function<int(std::unique_ptr<int> &&)> ReadAndDeallocByRef =
>> > + [](std::unique_ptr<int> &&Ptr) {
>> > + int V = *Ptr;
>> > + Ptr.reset();
>> > + return V;
>> > + };
>> > + std::unique_ptr<int> Ptr{new int(13)};
>> > + EXPECT_EQ(ReadAndDeallocByRef(std::move(Ptr)), 13);
>> > + EXPECT_FALSE((bool)Ptr);
>> > +
>> > + // Make sure we can pass a move-only temporary as opposed to a local
>> variable.
>> > + EXPECT_EQ(ReadAndDeallocByRef(std::unique_ptr<int>(new int(42))), 42);
>> > +
>> > + // Make sure we can pass a move-only type by-value.
>> > + unique_function<int(std::unique_ptr<int>)> ReadAndDeallocByVal =
>> > + [](std::unique_ptr<int> Ptr) {
>> > + int V = *Ptr;
>> > + Ptr.reset();
>> > + return V;
>> > + };
>> > + Ptr.reset(new int(13));
>> > + EXPECT_EQ(ReadAndDeallocByVal(std::move(Ptr)), 13);
>> > + EXPECT_FALSE((bool)Ptr);
>> > +
>> > + EXPECT_EQ(ReadAndDeallocByVal(std::unique_ptr<int>(new int(42))), 42);
>> > +}
>> > +
>> > +TEST(UniqueFunctionTest, Captures) {
>> > + long A = 1, B = 2, C = 3, D = 4, E = 5;
>> > +
>> > + unique_function<long()> Tmp;
>> > +
>> > + unique_function<long()> C1 = [A]() { return A; };
>> > + EXPECT_EQ(C1(), 1);
>> > + Tmp = std::move(C1);
>> > + EXPECT_EQ(Tmp(), 1);
>> > +
>> > + unique_function<long()> C2 = [A, B]() { return A + B; };
>> > + EXPECT_EQ(C2(), 3);
>> > + Tmp = std::move(C2);
>> > + EXPECT_EQ(Tmp(), 3);
>> > +
>> > + unique_function<long()> C3 = [A, B, C]() { return A + B + C; };
>> > + EXPECT_EQ(C3(), 6);
>> > + Tmp = std::move(C3);
>> > + EXPECT_EQ(Tmp(), 6);
>> > +
>> > + unique_function<long()> C4 = [A, B, C, D]() { return A + B + C + D; };
>> > + EXPECT_EQ(C4(), 10);
>> > + Tmp = std::move(C4);
>> > + EXPECT_EQ(Tmp(), 10);
>> > +
>> > + unique_function<long()> C5 = [A, B, C, D, E]() { return A + B + C + D
>> + E; };
>> > + EXPECT_EQ(C5(), 15);
>> > + Tmp = std::move(C5);
>> > + EXPECT_EQ(Tmp(), 15);
>> > +}
>> > +
>> > +TEST(UniqueFunctionTest, MoveOnly) {
>> > + struct SmallCallable {
>> > + std::unique_ptr<int> A{new int(1)};
>> > +
>> > + int operator()(int B) { return *A + B; }
>> > + };
>> > + unique_function<int(int)> Small = SmallCallable();
>> > + EXPECT_EQ(Small(2), 3);
>> > + unique_function<int(int)> Small2 = std::move(Small);
>> > + EXPECT_EQ(Small2(2), 3);
>> > +
>> > + struct LargeCallable {
>> > + std::unique_ptr<int> A{new int(1)};
>> > + std::unique_ptr<int> B{new int(2)};
>> > + std::unique_ptr<int> C{new int(3)};
>> > + std::unique_ptr<int> D{new int(4)};
>> > + std::unique_ptr<int> E{new int(5)};
>> > +
>> > + int operator()() { return *A + *B + *C + *D + *E; }
>> > + };
>> > + unique_function<int()> Large = LargeCallable();
>> > + EXPECT_EQ(Large(), 15);
>> > + unique_function<int()> Large2 = std::move(Large);
>> > + EXPECT_EQ(Large2(), 15);
>> > +}
>> > +
>> > +TEST(UniqueFunctionTest, CountForwardingCopies) {
>> > + struct CopyCounter {
>> > + int &CopyCount;
>> > +
>> > + CopyCounter(int &CopyCount) : CopyCount(CopyCount) {}
>> > + CopyCounter(const CopyCounter &Arg) : CopyCount(Arg.CopyCount) {
>> > + ++CopyCount;
>> > + }
>> > + };
>> > +
>> > + unique_function<void(CopyCounter)> ByValF = [](CopyCounter) {};
>> > + int CopyCount = 0;
>> > + ByValF(CopyCounter(CopyCount));
>> > + EXPECT_EQ(1, CopyCount);
>> > +
>> > + CopyCount = 0;
>> > + {
>> > + CopyCounter Counter{CopyCount};
>> > + ByValF(Counter);
>> > + }
>> > + EXPECT_EQ(2, CopyCount);
>> > +
>> > + // Check that we don't generate a copy at all when we can bind a
>> reference all
>> > + // the way down, even if that reference could *in theory* allow
>> copies.
>> > + unique_function<void(const CopyCounter &)> ByRefF = [](const
>> CopyCounter &) {
>> > + };
>> > + CopyCount = 0;
>> > + ByRefF(CopyCounter(CopyCount));
>> > + EXPECT_EQ(0, CopyCount);
>> > +
>> > + CopyCount = 0;
>> > + {
>> > + CopyCounter Counter{CopyCount};
>> > + ByRefF(Counter);
>> > + }
>> > + EXPECT_EQ(0, CopyCount);
>> > +
>> > + // If we use a reference, we can make a stronger guarantee that *no*
>> copy
>> > + // occurs.
>> > + struct Uncopyable {
>> > + Uncopyable() = default;
>> > + Uncopyable(const Uncopyable &) = delete;
>> > + };
>> > + unique_function<void(const Uncopyable &)> UncopyableF =
>> > + [](const Uncopyable &) {};
>> > + UncopyableF(Uncopyable());
>> > + Uncopyable X;
>> > + UncopyableF(X);
>> > +}
>> > +
>> > +TEST(UniqueFunctionTest, CountForwardingMoves) {
>> > + struct MoveCounter {
>> > + int &MoveCount;
>> > +
>> > + MoveCounter(int &MoveCount) : MoveCount(MoveCount) {}
>> > + MoveCounter(MoveCounter &&Arg) : MoveCount(Arg.MoveCount) {
>> ++MoveCount; }
>> > + };
>> > +
>> > + unique_function<void(MoveCounter)> ByValF = [](MoveCounter) {};
>> > + int MoveCount = 0;
>> > + ByValF(MoveCounter(MoveCount));
>> > + EXPECT_EQ(1, MoveCount);
>> > +
>> > + MoveCount = 0;
>> > + {
>> > + MoveCounter Counter{MoveCount};
>> > + ByValF(std::move(Counter));
>> > + }
>> > + EXPECT_EQ(2, MoveCount);
>> > +
>> > + // Check that when we use an r-value reference we get no spurious
>> copies.
>> > + unique_function<void(MoveCounter &&)> ByRefF = [](MoveCounter &&) {};
>> > + MoveCount = 0;
>> > + ByRefF(MoveCounter(MoveCount));
>> > + EXPECT_EQ(0, MoveCount);
>> > +
>> > + MoveCount = 0;
>> > + {
>> > + MoveCounter Counter{MoveCount};
>> > + ByRefF(std::move(Counter));
>> > + }
>> > + EXPECT_EQ(0, MoveCount);
>> > +
>> > + // If we use an r-value reference we can in fact make a stronger
>> guarantee
>> > + // with an unmovable type.
>> > + struct Unmovable {
>> > + Unmovable() = default;
>> > + Unmovable(Unmovable &&) = delete;
>> > + };
>> > + unique_function<void(const Unmovable &)> UnmovableF = [](const
>> Unmovable &) {
>> > + };
>> > + UnmovableF(Unmovable());
>> > + Unmovable X;
>> > + UnmovableF(X);
>> > +}
>> > +
>> > +} // anonymous namespace
>> >
>> >
>> > _______________________________________________
>> > llvm-commits mailing list
>> > llvm-commits at lists.llvm.org
>> > http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
>>
>>
> _______________________________________________
> llvm-commits mailing list
> llvm-commits at lists.llvm.org
> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-commits
More information about the llvm-commits
mailing list