Skip to content

Commit 22b4496

Browse files
authored
[ADT] Fix alignment check in unique_function constructor (llvm#99403)
Right now the check fails for any state-capturing lambda since this expression - `alignof(decltype(StorageUnion.InlineStorage))` - returns 1 for the alignment value and not 4/8 as expected ([MSVC|Clang|GCC](https://godbolt.org/z/eTEdq4xjM)). So this check fails for pretty much any state-capturing callable we try to store into a `unique_function` and we take the out-of-line storage path: \llvm-project\llvm\include\llvm\ADT\FunctionExtras.h, `UniqueFunctionBase` constructor (line ~266): ``` if (sizeof(CallableT) > InlineStorageSize || alignof(CallableT) > alignof(decltype(StorageUnion.InlineStorage))) { // ... } ``` The fix is simply to use an explicit const variable to store the alignment value. There is no easy way to unit-test the fix since inline storage is considered to be an implementation detail so we shouldn't assume how the lambda ends up being stored.
1 parent 92a8ec7 commit 22b4496

File tree

2 files changed

+23
-2
lines changed

2 files changed

+23
-2
lines changed

llvm/include/llvm/ADT/FunctionExtras.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ using EnableIfCallable = std::enable_if_t<std::disjunction<
8080
template <typename ReturnT, typename... ParamTs> class UniqueFunctionBase {
8181
protected:
8282
static constexpr size_t InlineStorageSize = sizeof(void *) * 3;
83+
static constexpr size_t InlineStorageAlign = alignof(void *);
8384

8485
template <typename T, class = void>
8586
struct IsSizeLessThanThresholdT : std::false_type {};
@@ -161,7 +162,8 @@ template <typename ReturnT, typename... ParamTs> class UniqueFunctionBase {
161162
// provide three pointers worth of storage here.
162163
// This is mutable as an inlined `const unique_function<void() const>` may
163164
// still modify its own mutable members.
164-
alignas(void *) mutable std::byte InlineStorage[InlineStorageSize];
165+
alignas(InlineStorageAlign) mutable std::byte
166+
InlineStorage[InlineStorageSize];
165167
} StorageUnion;
166168

167169
// A compressed pointer to either our dispatching callback or our table of
@@ -262,7 +264,7 @@ template <typename ReturnT, typename... ParamTs> class UniqueFunctionBase {
262264
bool IsInlineStorage = true;
263265
void *CallableAddr = getInlineStorage();
264266
if (sizeof(CallableT) > InlineStorageSize ||
265-
alignof(CallableT) > alignof(decltype(StorageUnion.InlineStorage))) {
267+
alignof(CallableT) > InlineStorageAlign) {
266268
IsInlineStorage = false;
267269
// Allocate out-of-line storage. FIXME: Use an explicit alignment
268270
// parameter in C++17 mode.

llvm/unittests/ADT/FunctionExtrasTest.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,23 @@ class Incomplete {};
310310
Incomplete incompleteFunction() { return {}; }
311311
const Incomplete incompleteFunctionConst() { return {}; }
312312

313+
// Check that we can store a pointer-sized payload inline in the unique_function.
314+
TEST(UniqueFunctionTest, InlineStorageWorks) {
315+
// We do assume a couple of implementation details of the unique_function here:
316+
// - It can store certain small-enough payload inline
317+
// - Inline storage size is at least >= sizeof(void*)
318+
void *ptr;
319+
unique_function<void(void *)> UniqueFunctionWithInlineStorage{
320+
[ptr](void *self) {
321+
auto mid = reinterpret_cast<uintptr_t>(&ptr);
322+
auto beg = reinterpret_cast<uintptr_t>(self);
323+
auto end = reinterpret_cast<uintptr_t>(self) +
324+
sizeof(unique_function<void(void *)>);
325+
// Make sure the address of the captured pointer lies somewhere within
326+
// the unique_function object.
327+
EXPECT_TRUE(mid >= beg && mid < end);
328+
}};
329+
UniqueFunctionWithInlineStorage(&UniqueFunctionWithInlineStorage);
330+
}
331+
313332
} // anonymous namespace

0 commit comments

Comments
 (0)