Skip to content

Commit d07c3cf

Browse files
[Support] Introduce ThreadSafeAllocator
Add support for ThreadSafeAllocator, which is needed for a CAS implementation, which requires thread safe allocation for data storage. Reviewed By: dblaikie Differential Revision: https://reviews.llvm.org/D133713
1 parent 4c94aff commit d07c3cf

File tree

3 files changed

+202
-0
lines changed

3 files changed

+202
-0
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===- ThreadSafeAllocator.h ------------------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_SUPPORT_THREADSAFEALLOCATOR_H
10+
#define LLVM_SUPPORT_THREADSAFEALLOCATOR_H
11+
12+
#include "llvm/ADT/STLExtras.h"
13+
#include "llvm/ADT/STLFunctionalExtras.h"
14+
#include "llvm/Support/Allocator.h"
15+
#include <atomic>
16+
17+
namespace llvm {
18+
19+
/// Thread-safe allocator adaptor. Uses a spin lock on the assumption that
20+
/// contention here is extremely rare.
21+
///
22+
/// TODO: Using a spin lock on every allocation can be quite expensive when
23+
/// contention is high. Since this is mainly used for BumpPtrAllocator and
24+
/// SpecificBumpPtrAllocator, it'd be better to have a specific thread-safe
25+
/// BumpPtrAllocator implementation that only use a fair lock when allocating a
26+
/// new slab but otherwise using atomic and be lock-free.
27+
template <class AllocatorType> class ThreadSafeAllocator {
28+
struct LockGuard {
29+
LockGuard(std::atomic_flag &Flag) : Flag(Flag) {
30+
if (LLVM_UNLIKELY(Flag.test_and_set(std::memory_order_acquire)))
31+
while (Flag.test_and_set(std::memory_order_acquire)) {
32+
}
33+
}
34+
~LockGuard() { Flag.clear(std::memory_order_release); }
35+
std::atomic_flag &Flag;
36+
};
37+
38+
public:
39+
auto Allocate(size_t N) {
40+
return applyLocked([N](AllocatorType &Alloc) { return Alloc.Allocate(N); });
41+
}
42+
43+
auto Allocate(size_t Size, size_t Align) {
44+
return applyLocked([Size, Align](AllocatorType &Alloc) {
45+
return Alloc.Allocate(Size, Align);
46+
});
47+
}
48+
49+
template <typename FnT,
50+
typename T = typename llvm::function_traits<FnT>::result_t>
51+
T applyLocked(FnT Fn) {
52+
LockGuard Lock(Flag);
53+
return Fn(Alloc);
54+
}
55+
56+
private:
57+
AllocatorType Alloc;
58+
std::atomic_flag Flag = ATOMIC_FLAG_INIT;
59+
};
60+
61+
} // namespace llvm
62+
63+
#endif // LLVM_SUPPORT_THREADSAFEALLOCATOR_H

llvm/unittests/Support/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ add_llvm_unittest(SupportTests
8181
SwapByteOrderTest.cpp
8282
TarWriterTest.cpp
8383
ThreadPool.cpp
84+
ThreadSafeAllocatorTest.cpp
8485
Threading.cpp
8586
TimerTest.cpp
8687
TimeProfilerTest.cpp
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
//===- llvm/unittest/Support/ThreadSafeAllocatorTest.cpp ------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "llvm/Support/ThreadSafeAllocator.h"
10+
#include "llvm/Config/llvm-config.h"
11+
#include "llvm/Support/ThreadPool.h"
12+
#include "gtest/gtest.h"
13+
#include <atomic>
14+
#include <thread>
15+
16+
using namespace llvm;
17+
18+
namespace {
19+
20+
struct AllocCondition {
21+
std::mutex BusyLock, EndLock;
22+
std::condition_variable Busy, End;
23+
bool IsBusy = false, IsEnd = false;
24+
std::atomic<unsigned> BytesAllocated = 0;
25+
26+
void startAllocation() {
27+
{
28+
std::lock_guard<std::mutex> Lock(BusyLock);
29+
IsBusy = true;
30+
}
31+
Busy.notify_all();
32+
}
33+
void waitAllocationStarted() {
34+
std::unique_lock<std::mutex> LBusy(BusyLock);
35+
Busy.wait(LBusy, [&]() { return IsBusy; });
36+
IsBusy = false;
37+
}
38+
void finishAllocation() {
39+
{
40+
std::lock_guard<std::mutex> Lock(EndLock);
41+
IsEnd = true;
42+
}
43+
End.notify_all();
44+
}
45+
void waitAllocationFinished() {
46+
std::unique_lock<std::mutex> LEnd(EndLock);
47+
// Wait for end state.
48+
End.wait(LEnd, [&]() { return IsEnd; });
49+
IsEnd = false;
50+
}
51+
};
52+
53+
class MockAllocator : public AllocatorBase<MockAllocator> {
54+
public:
55+
MockAllocator() = default;
56+
57+
void *Allocate(size_t Size, size_t Alignment) {
58+
C.startAllocation();
59+
C.waitAllocationFinished();
60+
C.BytesAllocated += Size;
61+
return Reserved;
62+
}
63+
64+
AllocCondition &getAllocCondition() { return C; }
65+
66+
private:
67+
char Reserved[16];
68+
AllocCondition C;
69+
};
70+
71+
} // namespace
72+
73+
#if (LLVM_ENABLE_THREADS)
74+
TEST(ThreadSafeAllocatorTest, AllocWait) {
75+
ThreadSafeAllocator<MockAllocator> Alloc;
76+
AllocCondition *C;
77+
// Get the allocation from the allocator first since this requires a lock.
78+
Alloc.applyLocked(
79+
[&](MockAllocator &Alloc) { C = &Alloc.getAllocCondition(); });
80+
ThreadPool Threads;
81+
// First allocation of 1 byte.
82+
Threads.async([&Alloc]() {
83+
char *P = (char *)Alloc.Allocate(1, alignof(char));
84+
P[0] = 0;
85+
});
86+
// No allocation yet.
87+
EXPECT_EQ(C->BytesAllocated, 0u);
88+
C->waitAllocationStarted(); // wait till 1st alloocation starts.
89+
// Second allocation of 2 bytes.
90+
Threads.async([&Alloc]() {
91+
char *P = (char *)Alloc.Allocate(2, alignof(char));
92+
P[1] = 0;
93+
});
94+
C->finishAllocation(); // finish 1st allocation.
95+
96+
C->waitAllocationStarted(); // wait till 2nd allocation starts.
97+
// still 1 byte allocated since 2nd allocation is not finished yet.
98+
EXPECT_EQ(C->BytesAllocated, 1u);
99+
C->finishAllocation(); // finish 2nd allocation.
100+
101+
Threads.wait(); // all allocations done.
102+
EXPECT_EQ(C->BytesAllocated, 3u);
103+
}
104+
105+
TEST(ThreadSafeAllocatorTest, AllocWithAlign) {
106+
ThreadSafeAllocator<BumpPtrAllocator> Alloc;
107+
ThreadPool Threads;
108+
109+
for (unsigned Index = 1; Index < 100; ++Index)
110+
Threads.async(
111+
[&Alloc](unsigned I) {
112+
int *P = (int *)Alloc.Allocate(sizeof(int) * I, alignof(int));
113+
P[I - 1] = I;
114+
},
115+
Index);
116+
117+
Threads.wait();
118+
119+
Alloc.applyLocked([](BumpPtrAllocator &Alloc) {
120+
EXPECT_EQ(4950U * sizeof(int), Alloc.getBytesAllocated());
121+
});
122+
}
123+
124+
TEST(ThreadSafeAllocatorTest, SpecificBumpPtrAllocator) {
125+
ThreadSafeAllocator<SpecificBumpPtrAllocator<int>> Alloc;
126+
ThreadPool Threads;
127+
128+
for (unsigned Index = 1; Index < 100; ++Index)
129+
Threads.async(
130+
[&Alloc](unsigned I) {
131+
int *P = Alloc.Allocate(I);
132+
P[I - 1] = I;
133+
},
134+
Index);
135+
136+
Threads.wait();
137+
}
138+
#endif

0 commit comments

Comments
 (0)