Skip to content

Commit 6f68eae

Browse files
committed
IRGen: Artificially pad async let entry point contexts.
We fixed a bug in the concurrency runtime (rdar://problem/90357994) that would lead to memory corruption when a new `async let` child task tried to use the last 16 bytes of the preallocated slab from its parent to seed its own task allocator. To work around this bug in older OSes that shipped with the bug, artificially pad the async coroutine context size for any async functions used as an `async let` entry point, which will prevent the runtime from going down the path of trying to use the preallocated storage at all. rdar://90506708
1 parent 32fceb5 commit 6f68eae

File tree

4 files changed

+71
-3
lines changed

4 files changed

+71
-3
lines changed

lib/IRGen/GenCall.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3903,14 +3903,17 @@ void irgen::emitAsyncFunctionEntry(IRGenFunction &IGF,
39033903
unsigned asyncContextIndex) {
39043904
auto &IGM = IGF.IGM;
39053905
auto size = layout.getSize();
3906+
auto asyncFuncPointerVar = cast<llvm::GlobalVariable>(IGM.getAddrOfAsyncFunctionPointer(asyncFunction));
39063907
auto asyncFuncPointer = IGF.Builder.CreateBitOrPointerCast(
3907-
IGM.getAddrOfAsyncFunctionPointer(asyncFunction), IGM.Int8PtrTy);
3908+
asyncFuncPointerVar, IGM.Int8PtrTy);
39083909
auto *id = IGF.Builder.CreateIntrinsicCall(
39093910
llvm::Intrinsic::coro_id_async,
39103911
{llvm::ConstantInt::get(IGM.Int32Ty, size.getValue()),
39113912
llvm::ConstantInt::get(IGM.Int32Ty, 16),
39123913
llvm::ConstantInt::get(IGM.Int32Ty, asyncContextIndex),
39133914
asyncFuncPointer});
3915+
auto inserted = IGM.AsyncCoroIDs.insert({asyncFuncPointerVar, id});
3916+
assert(inserted.second);
39143917
// Call 'llvm.coro.begin', just for consistency with the normal pattern.
39153918
// This serves as a handle that we can pass around to other intrinsics.
39163919
auto hdl = IGF.Builder.CreateIntrinsicCall(

lib/IRGen/GenConcurrency.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "IRGenModule.h"
2727
#include "LoadableTypeInfo.h"
2828
#include "ScalarPairTypeInfo.h"
29+
#include "swift/AST/ASTContext.h"
2930
#include "swift/AST/ProtocolConformanceRef.h"
3031
#include "swift/ABI/MetadataValues.h"
3132

@@ -192,6 +193,44 @@ llvm::Value *irgen::emitBuiltinStartAsyncLet(IRGenFunction &IGF,
192193
auto futureResultType = subs.getReplacementTypes()[0]->getCanonicalType();
193194
auto futureResultTypeMetadata = IGF.emitAbstractTypeMetadataRef(futureResultType);
194195

196+
// The concurrency runtime for older Apple OSes has a bug in task formation
197+
// for `async let`s that may manifest when trying to use room in the
198+
// parent task's preallocated `async let` buffer for the child task's
199+
// initial task allocator slab. If targeting those older OSes, pad the
200+
// context size for async let entry points to never fit in the preallocated
201+
// space, so that we don't run into that bug. We leave a note on the
202+
// declaration so that coroutine splitting can pad out the final context
203+
// size after splitting.
204+
auto deploymentAvailability
205+
= AvailabilityContext::forDeploymentTarget(IGF.IGM.Context);
206+
if (!deploymentAvailability.isContainedIn(
207+
IGF.IGM.Context.getSwift57Availability())) {
208+
auto taskAsyncFunctionPointer
209+
= cast<llvm::GlobalVariable>(taskFunction->stripPointerCasts());
210+
211+
auto taskAsyncIDIter = IGF.IGM.AsyncCoroIDs.find(taskAsyncFunctionPointer);
212+
assert(taskAsyncIDIter != IGF.IGM.AsyncCoroIDs.end()
213+
&& "async let entry point not emitted locally");
214+
auto taskAsyncID = taskAsyncIDIter->second;
215+
216+
// Pad out the initial context size in the async function pointer record
217+
// and ID intrinsic so that it will never fit in the preallocated space.
218+
uint64_t origSize = cast<llvm::ConstantInt>(taskAsyncID->getArgOperand(0))
219+
->getValue().getLimitedValue();
220+
221+
uint64_t paddedSize = std::max(origSize,
222+
(NumWords_AsyncLet * IGF.IGM.getPointerSize()).getValue());
223+
auto paddedSizeVal = llvm::ConstantInt::get(IGF.IGM.Int32Ty, paddedSize);
224+
taskAsyncID->setArgOperand(0, paddedSizeVal);
225+
226+
auto origInit = taskAsyncFunctionPointer->getInitializer();
227+
auto newInit = llvm::ConstantStruct::get(
228+
cast<llvm::StructType>(origInit->getType()),
229+
origInit->getAggregateElement(0u),
230+
paddedSizeVal);
231+
taskAsyncFunctionPointer->setInitializer(newInit);
232+
}
233+
195234
llvm::CallInst *call;
196235
if (localResultBuffer) {
197236
// This is @_silgen_name("swift_asyncLet_begin")

lib/IRGen/IRGenModule.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,10 @@ class IRGenerator {
328328

329329
/// The queue of IRGenModules for multi-threaded compilation.
330330
SmallVector<IRGenModule *, 8> Queue;
331-
331+
332332
std::atomic<int> QueueIndex;
333333

334-
friend class CurrentIGMPtr;
334+
friend class CurrentIGMPtr;
335335
public:
336336
explicit IRGenerator(const IRGenOptions &opts, SILModule &module);
337337

@@ -893,6 +893,10 @@ class IRGenModule {
893893
std::string GetObjCSectionName(StringRef Section, StringRef MachOAttributes);
894894
void SetCStringLiteralSection(llvm::GlobalVariable *GV, ObjCLabelType Type);
895895

896+
// Mapping of AsyncFunctionPointer records to their corresponding
897+
// `@llvm.coro.id.async` intrinsic tag in the function implementation.
898+
llvm::DenseMap<llvm::GlobalVariable*, llvm::CallInst*> AsyncCoroIDs;
899+
896900
private:
897901
Size PtrSize;
898902
Size AtomicBoolSize;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-frontend -emit-ir -target x86_64-apple-macos99.99 %s | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-sans-workaround %s
2+
// RUN: %target-swift-frontend -emit-ir -target x86_64-apple-macos12.3 %s | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-with-workaround %s
3+
4+
// REQUIRES: OS=macosx
5+
6+
// rdar://90506708: Prior to Swift 5.7, the Swift concurrency runtime had a bug
7+
// that led to memory corruption in cases when an `async let` child task
8+
// would try to use the last 16 bytes of the preallocated slab from the parent
9+
// to seed its own task allocator. When targeting older Apple OSes that shipped
10+
// with this bug in their runtime, we work around the bug by inflating the
11+
// initial context sizes of any async functions used as `async let` entry points
12+
// to ensure that the preallocated space is never used for the initial context.
13+
14+
// CHECK: [[ASYNC_LET_ENTRY:@"\$sSiIeghHd_Sis5Error_pIegHrzo_TRTATu"]]
15+
// CHECK-with-workaround-SAME: = internal {{.*}} %swift.async_func_pointer <{ {{.*}}, i32 6{{[0-9][0-9]}} }>
16+
// CHECK-sans-workaround-SAME: = internal {{.*}} %swift.async_func_pointer <{ {{.*}}, i32 {{[0-9][0-9]}} }>
17+
18+
// CHECK: swift_asyncLet_begin{{.*}}[[ASYNC_LET_ENTRY]]
19+
public func foo(x: Int, y: Int) async -> Int {
20+
async let z = x + y
21+
return await z
22+
}

0 commit comments

Comments
 (0)