Skip to content

Commit db94dc7

Browse files
committed
[Concurrency] Fix memory leak around mismatched refcounting in Concurrency
1 parent 8f66bf1 commit db94dc7

File tree

8 files changed

+133
-21
lines changed

8 files changed

+133
-21
lines changed

include/swift/ABI/Task.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ class ContinuationAsyncContext;
4848
// _swift_concurrency_debug_internal_layout_version and add a comment describing
4949
// the new version.
5050

51-
extern FullMetadata<DispatchClassMetadata> jobHeapMetadata;
51+
extern const HeapMetadata *jobHeapMetadataPtr;
52+
extern const HeapMetadata *taskHeapMetadataPtr;
5253

5354
/// A schedulable job.
5455
class alignas(2 * alignof(void*)) Job :
@@ -106,14 +107,14 @@ class alignas(2 * alignof(void*)) Job :
106107
};
107108

108109
Job(JobFlags flags, JobInvokeFunction *invoke,
109-
const HeapMetadata *metadata = &jobHeapMetadata)
110+
const HeapMetadata *metadata = jobHeapMetadataPtr)
110111
: HeapObject(metadata), Flags(flags), RunJob(invoke) {
111112
Voucher = voucher_copy();
112113
assert(!isAsyncTask() && "wrong constructor for a task");
113114
}
114115

115116
Job(JobFlags flags, TaskContinuationFunction *invoke,
116-
const HeapMetadata *metadata = &jobHeapMetadata,
117+
const HeapMetadata *metadata = jobHeapMetadataPtr,
117118
bool captureCurrentVoucher = true)
118119
: HeapObject(metadata), Flags(flags), ResumeTask(invoke) {
119120
if (captureCurrentVoucher)

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
273273
-Xfrontend -emit-empty-object-file
274274
${SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS}
275275
C_COMPILE_FLAGS
276-
${extra_c_compile_flags} ${SWIFT_RUNTIME_CONCURRENCY_C_FLAGS} -DSWIFT_CONCURRENCY_EMBEDDED=1
276+
${extra_c_compile_flags} ${SWIFT_RUNTIME_CONCURRENCY_C_FLAGS} -DSWIFT_CONCURRENCY_EMBEDDED=1 -DSWIFT_RUNTIME_EMBEDDED=1
277277
MODULE_DIR "${CMAKE_BINARY_DIR}/lib/swift/embedded"
278278
SDK "embedded"
279279
ARCHITECTURE "${mod}"

stdlib/public/Concurrency/ExecutorChecks.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ static_assert((SwiftJobPriority)swift::JobPriority::Unspecified
5454
// Job (has additional fields not exposed via SwiftJob)
5555
static_assert(sizeof(swift::Job) >= sizeof(SwiftJob));
5656
static_assert(offsetof(swift::Job, metadata) == offsetof(SwiftJob, metadata));
57+
#if !SWIFT_CONCURRENCY_EMBEDDED
5758
static_assert(offsetof(swift::Job, refCounts) == offsetof(SwiftJob, refCounts));
59+
#else
60+
static_assert(offsetof(swift::Job, embeddedRefcount) == offsetof(SwiftJob, refCounts));
61+
#endif
5862
static_assert(offsetof(swift::Job, SchedulerPrivate) == offsetof(SwiftJob, schedulerPrivate));
5963
static_assert(offsetof(swift::Job, SchedulerPrivate[0]) == offsetof(SwiftJob, schedulerPrivate[0]));
6064
static_assert(offsetof(swift::Job, SchedulerPrivate[1]) == offsetof(SwiftJob, schedulerPrivate[1]));

stdlib/public/Concurrency/Task.cpp

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ InitialTaskExecutorOwnedPreferenceTaskOptionRecord::getExecutorRefFromUnownedTas
215215
return executorRef;
216216
}
217217

218-
219218
void NullaryContinuationJob::process(Job *_job) {
220219
auto *job = cast<NullaryContinuationJob>(_job);
221220

@@ -393,7 +392,9 @@ static void jobInvoke(void *obj, void *unused, uint32_t flags) {
393392
// Magic constant to identify Swift Job vtables to Dispatch.
394393
static const unsigned long dispatchSwiftObjectType = 1;
395394

396-
FullMetadata<DispatchClassMetadata> swift::jobHeapMetadata = {
395+
#if !SWIFT_CONCURRENCY_EMBEDDED
396+
397+
FullMetadata<DispatchClassMetadata> jobHeapMetadata = {
397398
{
398399
{
399400
/*type layout*/ nullptr,
@@ -432,10 +433,35 @@ static FullMetadata<DispatchClassMetadata> taskHeapMetadata = {
432433
}
433434
};
434435

436+
#else // SWIFT_CONCURRENCY_EMBEDDED
437+
438+
// This matches the embedded class metadata layout in IRGen and in
439+
// EmbeddedRuntime.swift.
440+
typedef struct EmbeddedClassMetadata {
441+
void *superclass;
442+
HeapObjectDestroyer *__ptrauth_swift_heap_object_destructor destroy;
443+
void *ivar_destroyer;
444+
} EmbeddedHeapObject;
445+
446+
static EmbeddedClassMetadata jobHeapMetadata = {
447+
0, &destroyJob, 0,
448+
};
449+
450+
static EmbeddedClassMetadata taskHeapMetadata = {
451+
0, &destroyTask, 0,
452+
};
453+
454+
#endif
455+
435456
const void *const swift::_swift_concurrency_debug_jobMetadata =
436-
static_cast<Metadata *>(&jobHeapMetadata);
457+
(Metadata *)(&jobHeapMetadata);
437458
const void *const swift::_swift_concurrency_debug_asyncTaskMetadata =
438-
static_cast<Metadata *>(&taskHeapMetadata);
459+
(Metadata *)(&taskHeapMetadata);
460+
461+
const HeapMetadata *swift::jobHeapMetadataPtr =
462+
(HeapMetadata *)(&jobHeapMetadata);
463+
const HeapMetadata *swift::taskHeapMetadataPtr =
464+
(HeapMetadata *)(&taskHeapMetadata);
439465

440466
static void completeTaskImpl(AsyncTask *task,
441467
AsyncContext *context,
@@ -959,14 +985,14 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
959985
if (asyncLet) {
960986
// Initialize the refcount bits to "immortal", so that
961987
// ARC operations don't have any effect on the task.
962-
task = new(allocation) AsyncTask(&taskHeapMetadata,
963-
InlineRefCounts::Immortal, jobFlags,
964-
function, initialContext,
965-
captureCurrentVoucher);
988+
task = new (allocation)
989+
AsyncTask(reinterpret_cast<ClassMetadata *>(&taskHeapMetadata),
990+
InlineRefCounts::Immortal, jobFlags, function, initialContext,
991+
captureCurrentVoucher);
966992
} else {
967-
task = new(allocation) AsyncTask(&taskHeapMetadata, jobFlags,
968-
function, initialContext,
969-
captureCurrentVoucher);
993+
task = new (allocation)
994+
AsyncTask(reinterpret_cast<ClassMetadata *>(&taskHeapMetadata), jobFlags,
995+
function, initialContext, captureCurrentVoucher);
970996
}
971997

972998
// Initialize the child fragment if applicable.

stdlib/public/SwiftShims/swift/shims/HeapObject.h

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ struct HeapObject {
5353
/// This is always a valid pointer to a metadata object.
5454
HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
5555

56+
#if !SWIFT_RUNTIME_EMBEDDED
57+
5658
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
5759

5860
#ifndef __swift__
@@ -70,11 +72,40 @@ struct HeapObject {
7072
: metadata(newMetadata)
7173
, refCounts(InlineRefCounts::Immortal)
7274
{ }
75+
#endif // __swift__
76+
77+
#else // SWIFT_RUNTIME_EMBEDDED
78+
uintptr_t embeddedRefcount;
79+
80+
// Note: The immortal refcount value is also hard-coded in IRGen in
81+
// `irgen::emitConstantObject`, and in EmbeddedRuntime.swift.
82+
#if __POINTER_WIDTH__ == 64
83+
static const uintptr_t EmbeddedImmortalRefCount = 0x7fffffffffffffffull;
84+
#elif __POINTER_WIDTH__ == 32
85+
static const uintptr_t EmbeddedImmortalRefCount = 0x7fffffff;
86+
#elif __POINTER_WIDTH__ == 16
87+
static const uintptr_t EmbeddedImmortalRefCount = 0x7fff;
88+
#endif
7389

90+
#ifndef __swift__
91+
HeapObject() = default;
92+
93+
// Initialize a HeapObject header as appropriate for a newly-allocated object.
94+
constexpr HeapObject(HeapMetadata const *newMetadata)
95+
: metadata(newMetadata), embeddedRefcount(1) {}
96+
97+
// Initialize a HeapObject header for an immortal object
98+
constexpr HeapObject(HeapMetadata const *newMetadata,
99+
InlineRefCounts::Immortal_t immortal)
100+
: metadata(newMetadata), embeddedRefcount(EmbeddedImmortalRefCount) {}
101+
#endif // __swift__
102+
103+
#endif // SWIFT_RUNTIME_EMBEDDED
104+
105+
#ifndef __swift__
74106
#ifndef NDEBUG
75107
void dump() const SWIFT_USED;
76108
#endif
77-
78109
#endif // __swift__
79110
};
80111

stdlib/public/core/EmbeddedRuntime.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public struct HeapObject {
9999
// to think about supporting (or banning) weak and/or unowned references.
100100
var refcount: Int
101101

102-
// Note: The immortalRefCount value is also hard-coded in IRGen in `irgen::emitConstantObject`.
102+
// Note: The immortalRefCount value is also hard-coded in IRGen in `irgen::emitConstantObject`, and in HeapObject.h.
103103
#if _pointerBitWidth(_64)
104104
static let doNotFreeBit = Int(bitPattern: 0x8000_0000_0000_0000)
105105
static let refcountMask = Int(bitPattern: 0x7fff_ffff_ffff_ffff)

test/embedded/Inputs/debug-malloc.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
#include <stdlib.h>
22
#include <stdio.h>
33

4-
#define HEAP_SIZE (8 * 1024)
4+
#define HEAP_SIZE (128 * 1024)
55

66
__attribute__((aligned(16)))
77
char heap[HEAP_SIZE] = {};
88

99
size_t next_heap_index = 0;
1010

1111
void *calloc(size_t count, size_t size) {
12-
printf("malloc(%ld)", count);
12+
size_t total_size = count * size;
13+
printf("malloc(%ld)", total_size);
1314

14-
if (next_heap_index + count * size > HEAP_SIZE) {
15+
if (total_size % 16 != 0)
16+
total_size = total_size + (16 - total_size % 16);
17+
18+
if (next_heap_index + total_size > HEAP_SIZE) {
1519
puts("HEAP EXHAUSTED\n");
1620
__builtin_trap();
1721
}
1822
void *p = &heap[next_heap_index];
19-
next_heap_index += count * size;
23+
next_heap_index += total_size;
2024
printf("-> %p\n", p);
2125
return p;
2226
}

test/embedded/concurrency-leaks.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -enable-experimental-feature Embedded -parse-as-library %s -c -o %t/a.o -g -O
3+
// RUN: %target-clang -x c -std=c11 -c %S/Inputs/debug-malloc.c -o %t/debug-malloc.o -g
4+
// RUN: %target-clang %t/a.o %t/debug-malloc.o -o %t/a.out -L%swift_obj_root/lib/swift/embedded/%target-cpu-apple-macos -lswift_Concurrency -lswift_ConcurrencyDefaultExecutor -dead_strip -g
5+
// RUN: %target-run %t/a.out | %FileCheck %s
6+
7+
// REQUIRES: executable_test
8+
// REQUIRES: swift_in_compiler
9+
// REQUIRES: optimized_stdlib
10+
// REQUIRES: OS=macosx
11+
12+
import _Concurrency
13+
14+
@main
15+
struct Main {
16+
static func main() async {
17+
print("start")
18+
// CHECK: start
19+
do {
20+
let x = Task {
21+
return 42
22+
}
23+
_ = await x.value
24+
}
25+
// There should be exactly 5 allocations involved:
26+
// - 2x 32 bytes ... closure context for swift_task_create, closure object to pass to Task.init
27+
// - 1x 320 bytes ... malloc(amountToAllocate) in swift_task_create_common for the Task heap object itself
28+
// - 1x 1016 bytes ... the initial StackAllocator slab in the task-specific allocator
29+
// - 1x 40 bytes ... task status record
30+
// Check that they are all accounted for and free'd.
31+
32+
// CHECK: malloc({{[0-9]+}})-> [[M1:0x[0-9a-f]+]]
33+
// CHECK: malloc({{[0-9]+}})-> [[M2:0x[0-9a-f]+]]
34+
// CHECK: malloc({{[0-9]+}})-> [[M3:0x[0-9a-f]+]]
35+
// CHECK: malloc({{[0-9]+}})-> [[M4:0x[0-9a-f]+]]
36+
// CHECK: free([[M1]])
37+
// CHECK: free([[M2]])
38+
// CHECK: malloc({{[0-9]+}})-> [[M5:0x[0-9a-f]+]]
39+
// CHECK: free([[M5]])
40+
// CHECK: free([[M4]])
41+
// CHECK: free([[M3]])
42+
43+
print("done")
44+
// CHECK: done
45+
}
46+
}

0 commit comments

Comments
 (0)