Skip to content

Commit a62f08e

Browse files
committed
[Concurrency] Add environment variable for disabling async stack slab allocator.
Add SWIFT_DEBUG_ENABLE_TASK_SLAB_ALLOCATOR, which is on by default. When turned off, async stack allocations call through to malloc/free. This allows memory debugging tools to be used on async stack allocations.
1 parent dad2637 commit a62f08e

File tree

11 files changed

+466
-10
lines changed

11 files changed

+466
-10
lines changed

include/swift/Runtime/EnvironmentVariables.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
//
1515
//===----------------------------------------------------------------------===//
1616

17+
#ifndef SWIFT_RUNTIME_ENVIRONMENTVARIABLES_H
18+
#define SWIFT_RUNTIME_ENVIRONMENTVARIABLES_H
19+
1720
#include "swift/Threading/Once.h"
1821
#include "swift/shims/Visibility.h"
1922

@@ -63,6 +66,12 @@ SWIFT_RUNTIME_STDLIB_SPI bool concurrencyValidateUncheckedContinuations();
6366
// Concurrency library can call.
6467
SWIFT_RUNTIME_STDLIB_SPI const char *concurrencyIsCurrentExecutorLegacyModeOverride();
6568

69+
// Wrapper around SWIFT_DEBUG_ENABLE_TASK_SLAB_ALLOCATOR that the Concurrency
70+
// library can call.
71+
SWIFT_RUNTIME_STDLIB_SPI bool concurrencyEnableTaskSlabAllocator();
72+
6673
} // end namespace environment
6774
} // end namespace runtime
6875
} // end namespace swift
76+
77+
#endif // SWIFT_RUNTIME_ENVIRONMENTVARIABLES_H

stdlib/public/Concurrency/TaskAlloc.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,8 @@ void swift::swift_job_deallocate(Job *job, void *ptr) {
8484

8585
allocator(static_cast<AsyncTask *>(job)).dealloc(ptr);
8686
}
87+
88+
#if !SWIFT_CONCURRENCY_EMBEDDED
89+
std::atomic<TaskAllocatorConfiguration::EnableState>
90+
TaskAllocatorConfiguration::enableState = {EnableState::Uninitialized};
91+
#endif

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "swift/Runtime/Atomic.h"
2727
#include "swift/Runtime/Concurrency.h"
2828
#include "swift/Runtime/DispatchShims.h"
29+
#include "swift/Runtime/EnvironmentVariables.h"
2930
#include "swift/Runtime/Error.h"
3031
#include "swift/Runtime/Exclusivity.h"
3132
#include "swift/Runtime/HeapObject.h"
@@ -744,14 +745,48 @@ class alignas(2 * sizeof(void*)) ActiveTaskStatus {
744745
static_assert(sizeof(ActiveTaskStatus) == ACTIVE_TASK_STATUS_SIZE,
745746
"ActiveTaskStatus is of incorrect size");
746747

748+
struct TaskAllocatorConfiguration {
749+
#if SWIFT_CONCURRENCY_EMBEDDED
750+
751+
// Slab allocator is always enabled on embedded.
752+
bool enableSlabAllocator() { return true; }
753+
754+
#else
755+
756+
enum class EnableState : uint8_t {
757+
Uninitialized,
758+
Enabled,
759+
Disabled,
760+
};
761+
762+
static std::atomic<EnableState> enableState;
763+
764+
bool enableSlabAllocator() {
765+
auto state = enableState.load(std::memory_order_relaxed);
766+
if (SWIFT_UNLIKELY(state == EnableState::Uninitialized)) {
767+
state = runtime::environment::concurrencyEnableTaskSlabAllocator()
768+
? EnableState::Enabled
769+
: EnableState::Disabled;
770+
enableState.store(state, std::memory_order_relaxed);
771+
}
772+
773+
return SWIFT_UNLIKELY(state == EnableState::Enabled);
774+
}
775+
776+
#endif // SWIFT_CONCURRENCY_EMBEDDED
777+
};
778+
747779
/// The size of an allocator slab. We want the full allocation to fit into a
748780
/// 1024-byte malloc quantum. We subtract off the slab header size, plus a
749781
/// little extra to stay within our limits even when there's overhead from
750782
/// malloc stack logging.
751-
static constexpr size_t SlabCapacity = 1024 - StackAllocator<0, nullptr>::slabHeaderSize() - 8;
783+
static constexpr size_t SlabCapacity =
784+
1024 - 8 -
785+
StackAllocator<0, nullptr, TaskAllocatorConfiguration>::slabHeaderSize();
752786
extern Metadata TaskAllocatorSlabMetadata;
753787

754-
using TaskAllocator = StackAllocator<SlabCapacity, &TaskAllocatorSlabMetadata>;
788+
using TaskAllocator = StackAllocator<SlabCapacity, &TaskAllocatorSlabMetadata,
789+
TaskAllocatorConfiguration>;
755790

756791
/// Private storage in an AsyncTask object.
757792
struct AsyncTask::PrivateStorage {

stdlib/public/runtime/EnvironmentVariables.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,7 @@ SWIFT_RUNTIME_STDLIB_SPI bool concurrencyValidateUncheckedContinuations() {
319319
SWIFT_RUNTIME_STDLIB_SPI const char *concurrencyIsCurrentExecutorLegacyModeOverride() {
320320
return runtime::environment::SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE();
321321
}
322+
323+
SWIFT_RUNTIME_STDLIB_SPI bool concurrencyEnableTaskSlabAllocator() {
324+
return runtime::environment::SWIFT_DEBUG_ENABLE_TASK_SLAB_ALLOCATOR();
325+
}

stdlib/public/runtime/EnvironmentVariables.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,9 @@ VARIABLE(SWIFT_DUMP_ACCESSIBLE_FUNCTIONS, bool, false,
147147
"record names, e.g. by the Distributed runtime to invoke distributed "
148148
"functions.")
149149

150+
VARIABLE(SWIFT_DEBUG_ENABLE_TASK_SLAB_ALLOCATOR, bool, true,
151+
"Use a slab allocator for the async stack. If not enabled, directly "
152+
"call malloc/free. Direct use of malloc/free enables use of memory "
153+
"debugging tools for async stack allocations.")
154+
150155
#undef VARIABLE

stdlib/public/runtime/StackAllocator.h

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,15 @@ namespace swift {
5757
///
5858
/// SlabMetadataPtr specifies a fake metadata pointer to place at the beginning
5959
/// of slab allocations, so analysis tools can identify them.
60-
template <size_t SlabCapacity, Metadata *SlabMetadataPtr>
60+
///
61+
/// SlabAllocatorConfiguration allows customizing behavior. It currently allows
62+
/// one customization point: bool enableSlabAllocator():
63+
/// returns false - the stack allocator directly calls malloc/free
64+
/// returns true - the slab allocator is used
65+
/// This function MUST return the same value throughout the lifetime the stack
66+
/// allocator.
67+
template <size_t SlabCapacity, Metadata *SlabMetadataPtr,
68+
typename SlabAllocatorConfiguration>
6169
class StackAllocator {
6270
private:
6371

@@ -77,6 +85,8 @@ class StackAllocator {
7785
/// Used for unit testing.
7886
uint32_t numAllocatedSlabs:31;
7987

88+
/// The configuration object.
89+
[[no_unique_address]] SlabAllocatorConfiguration configuration;
8090

8191
/// The minimal alignment of allocated memory.
8292
static constexpr size_t alignment = MaximumAlignment;
@@ -227,9 +237,10 @@ class StackAllocator {
227237
};
228238

229239
// Return a slab which is suitable to allocate \p size memory.
240+
SWIFT_ALWAYS_INLINE
230241
Slab *getSlabForAllocation(size_t size) {
231242
Slab *slab = (lastAllocation ? lastAllocation->slab : firstSlab);
232-
if (slab) {
243+
if (SWIFT_LIKELY(slab)) {
233244
// Is there enough space in the current slab?
234245
if (slab->canAllocate(size))
235246
return slab;
@@ -249,6 +260,12 @@ class StackAllocator {
249260
size = std::max(size, alreadyAllocatedCapacity);
250261
}
251262
}
263+
264+
// This is only checked on the path that allocates a new slab, to minimize
265+
// overhead when the slab allocator is enabled.
266+
if (SWIFT_UNLIKELY(!configuration.enableSlabAllocator()))
267+
return nullptr;
268+
252269
size_t capacity = std::max(SlabCapacity,
253270
Allocation::includingHeader(size));
254271
void *slabBuffer = malloc(Slab::includingHeader(capacity));
@@ -281,13 +298,15 @@ class StackAllocator {
281298
/// Construct a StackAllocator without a pre-allocated first slab.
282299
StackAllocator()
283300
: firstSlab(nullptr), firstSlabIsPreallocated(false),
284-
numAllocatedSlabs(0) {}
301+
numAllocatedSlabs(0), configuration() {}
285302

286303
/// Construct a StackAllocator with a pre-allocated first slab.
287304
StackAllocator(void *firstSlabBuffer, size_t bufferCapacity) : StackAllocator() {
288305
// If the pre-allocated buffer can't hold a slab header, ignore it.
289306
if (bufferCapacity <= Slab::headerSize())
290307
return;
308+
if (SWIFT_UNLIKELY(!configuration.enableSlabAllocator()))
309+
return;
291310
char *start = (char *)llvm::alignAddr(firstSlabBuffer,
292311
llvm::Align(alignment));
293312
char *end = (char *)firstSlabBuffer + bufferCapacity;
@@ -317,6 +336,17 @@ class StackAllocator {
317336
size += sizeof(uintptr_t);
318337
size_t alignedSize = llvm::alignTo(size, llvm::Align(alignment));
319338
Slab *slab = getSlabForAllocation(alignedSize);
339+
340+
// If getSlabForAllocation returns null, that means that the slab allocator
341+
// is disabled, and we should directly call malloc. We get this info
342+
// indirectly rather than directly calling enableSlabAllocator() in order
343+
// to minimize the overhead in the case where the slab allocator is enabled.
344+
// When getSlabForAllocation gets inlined into this code, this ends up
345+
// getting folded into its enableSlabAllocator() call, and the fast path
346+
// where `slab` is non-null ends up with no extra conditionals at all.
347+
if (SWIFT_UNLIKELY(!slab))
348+
return malloc(size);
349+
320350
Allocation *allocation = slab->allocate(alignedSize, lastAllocation);
321351
lastAllocation = allocation;
322352
assert(llvm::isAddrAligned(llvm::Align(alignment),
@@ -326,7 +356,10 @@ class StackAllocator {
326356

327357
/// Deallocate memory \p ptr.
328358
void dealloc(void *ptr) {
329-
if (!lastAllocation || lastAllocation->getAllocatedMemory() != ptr) {
359+
if (SWIFT_UNLIKELY(!lastAllocation ||
360+
lastAllocation->getAllocatedMemory() != ptr)) {
361+
if (!configuration.enableSlabAllocator())
362+
return free(ptr);
330363
SWIFT_FATAL_ERROR(0, "freed pointer was not the last allocation");
331364
}
332365

stdlib/toolchain/Compatibility56/include/Concurrency/TaskPrivate.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
#include "swift/Runtime/Error.h"
2626

2727
#define SWIFT_FATAL_ERROR swift_Concurrency_fatalError
28-
#include "public/runtime/StackAllocator.h"
28+
#include "Runtime/StackAllocator.h"
2929

3030
namespace swift {
3131

0 commit comments

Comments
 (0)