Skip to content

Commit 693a8bd

Browse files
authored
Merge pull request #42037 from jckarter/async-let-work-around-allocator-bug
IRGen: Artificially pad async let entry point contexts.
2 parents d323340 + 6f68eae commit 693a8bd

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)