Skip to content

Commit 68bc33f

Browse files
committed
IRGen: initial pass to support async inheritance on Windows
With PE/COFF, one cannot reference a data symbol directly across the binary module boundary. Instead, the reference must be indirected through the Import Address Table (IAT) to allow for position independence. When generating a reference to a AsyncFunctionPointer ({i8*, i32}), we tag the pointer as being indirected by tagging bit 1 (with the assumption that native alignment will ensure 4/8 byte alignment, freeing the bottom 2 bits at least for bit-packing). We tweak the v-table/witness table emission such that all references to the AsyncFunctionPointer are replaced with the linker synthetic import symbol with the bit packing: ~~~ .quad __imp_$s1L1CC1yyYaKFTu+1 ~~~ rather than ~~~ .quad $s1L1CC1yyYaKFTu ~~~ Upon access of the async function pointer reference, we open-code the check for the following: ~~~ pointer = (pointer & 1) ? *(void **)(pointer & ~1) : pointer; ~~~ Thanks to @DougGregor for the discussion and the suggestion for the pointer tagging. Thanks to @aschwaighofer for pointers to the code that I had missed. Also, thanks to @SeanROlszewski for the original code sample that led to the reduced test case. Fixes: SR-15399
1 parent 31e8933 commit 68bc33f

File tree

9 files changed

+132
-12
lines changed

9 files changed

+132
-12
lines changed

include/swift/AST/IRGenOptions.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ class IRGenOptions {
311311
/// Used on Windows to avoid cross-module references.
312312
unsigned LazyInitializeClassMetadata : 1;
313313
unsigned LazyInitializeProtocolConformances : 1;
314+
unsigned IndirectAsyncFunctionPointer : 1;
314315

315316
/// Normally if the -read-legacy-type-info flag is not specified, we look for
316317
/// a file named "legacy-<arch>.yaml" in SearchPathOpts.RuntimeLibraryPath.
@@ -417,7 +418,8 @@ class IRGenOptions {
417418
ValueNames(false), EnableReflectionMetadata(true),
418419
EnableReflectionNames(true), EnableAnonymousContextMangledNames(false),
419420
ForcePublicLinkage(false), LazyInitializeClassMetadata(false),
420-
LazyInitializeProtocolConformances(false), DisableLegacyTypeInfo(false),
421+
LazyInitializeProtocolConformances(false),
422+
IndirectAsyncFunctionPointer(false), DisableLegacyTypeInfo(false),
421423
PrespecializeGenericMetadata(false), UseIncrementalLLVMCodeGen(true),
422424
UseTypeLayoutValueHandling(true), ForceStructTypeLayouts(false),
423425
GenerateProfile(false), EnableDynamicReplacementChaining(false),

lib/Frontend/CompilerInvocation.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1876,6 +1876,11 @@ static bool ParseIRGenArgs(IRGenOptions &Opts, ArgList &Args,
18761876
// witness.
18771877
Opts.LazyInitializeProtocolConformances = Triple.isOSBinFormatCOFF();
18781878

1879+
// PE/COFF cannot deal with the cross-module reference to the
1880+
// AsyncFunctionPointer data block. Force the use of indirect
1881+
// AsyncFunctionPointer access.
1882+
Opts.IndirectAsyncFunctionPointer = Triple.isOSBinFormatCOFF();
1883+
18791884
if (Args.hasArg(OPT_disable_legacy_type_info)) {
18801885
Opts.DisableLegacyTypeInfo = true;
18811886
}

lib/IRGen/GenCall.cpp

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,6 +1986,46 @@ void IRGenFunction::emitAllExtractValues(llvm::Value *value,
19861986
out.add(Builder.CreateExtractValue(value, i));
19871987
}
19881988

1989+
namespace {
1990+
// TODO(compnerd) analyze if this should be out-lined via a runtime call rather
1991+
// than be open-coded. This needs to account for the fact that we are able to
1992+
// statically optimize this often times due to CVP changing the select to a
1993+
// `select i1 true, ...`.
1994+
llvm::Value *emitIndirectAsyncFunctionPointer(IRGenFunction &IGF,
1995+
llvm::Value *pointer) {
1996+
llvm::IntegerType *IntPtrTy = IGF.IGM.IntPtrTy;
1997+
llvm::Type *AsyncFunctionPointerPtrTy = IGF.IGM.AsyncFunctionPointerPtrTy;
1998+
llvm::Constant *Zero =
1999+
llvm::Constant::getIntegerValue(IntPtrTy, APInt(IntPtrTy->getBitWidth(),
2000+
0));
2001+
llvm::Constant *One =
2002+
llvm::Constant::getIntegerValue(IntPtrTy, APInt(IntPtrTy->getBitWidth(),
2003+
1));
2004+
llvm::Constant *NegativeOne =
2005+
llvm::Constant::getIntegerValue(IntPtrTy, APInt(IntPtrTy->getBitWidth(),
2006+
-2));
2007+
swift::irgen::Alignment PointerAlignment = IGF.IGM.getPointerAlignment();
2008+
2009+
llvm::Value *PtrToInt = IGF.Builder.CreatePtrToInt(pointer, IntPtrTy);
2010+
llvm::Value *And = IGF.Builder.CreateAnd(PtrToInt, One);
2011+
llvm::Value *ICmp = IGF.Builder.CreateICmpEQ(And, Zero);
2012+
2013+
llvm::Value *BitCast =
2014+
IGF.Builder.CreateBitCast(pointer, AsyncFunctionPointerPtrTy);
2015+
2016+
llvm::Value *UntaggedPointer = IGF.Builder.CreateAnd(PtrToInt, NegativeOne);
2017+
llvm::Value *IntToPtr =
2018+
IGF.Builder.CreateIntToPtr(UntaggedPointer,
2019+
AsyncFunctionPointerPtrTy->getPointerTo());
2020+
llvm::Value *Load = IGF.Builder.CreateLoad(IntToPtr, PointerAlignment);
2021+
2022+
// (select (icmp eq, (and (ptrtoint %AsyncFunctionPointer), 1), 0),
2023+
// (%AsyncFunctionPointer),
2024+
// (inttoptr (and (ptrtoint %AsyncFunctionPointer), -2)))
2025+
return IGF.Builder.CreateSelect(ICmp, BitCast, Load);
2026+
}
2027+
}
2028+
19892029
std::pair<llvm::Value *, llvm::Value *> irgen::getAsyncFunctionAndSize(
19902030
IRGenFunction &IGF, SILFunctionTypeRepresentation representation,
19912031
FunctionPointer functionPointer, llvm::Value *thickContext,
@@ -2007,9 +2047,11 @@ std::pair<llvm::Value *, llvm::Value *> irgen::getAsyncFunctionAndSize(
20072047
if (auto authInfo = functionPointer.getAuthInfo()) {
20082048
ptr = emitPointerAuthAuth(IGF, ptr, authInfo);
20092049
}
2010-
auto *afpPtr =
2011-
IGF.Builder.CreateBitCast(ptr, IGF.IGM.AsyncFunctionPointerPtrTy);
2012-
afpPtrValue = afpPtr;
2050+
afpPtrValue =
2051+
(IGF.IGM.getOptions().IndirectAsyncFunctionPointer)
2052+
? emitIndirectAsyncFunctionPointer(IGF, ptr)
2053+
: IGF.Builder.CreateBitCast(ptr,
2054+
IGF.IGM.AsyncFunctionPointerPtrTy);
20132055
}
20142056
return *afpPtrValue;
20152057
};
@@ -4831,6 +4873,8 @@ llvm::Value *FunctionPointer::getPointer(IRGenFunction &IGF) const {
48314873
auto *fnPtr = Value;
48324874
if (auto authInfo = AuthInfo) {
48334875
fnPtr = emitPointerAuthAuth(IGF, fnPtr, authInfo);
4876+
if (IGF.IGM.getOptions().IndirectAsyncFunctionPointer)
4877+
fnPtr = emitIndirectAsyncFunctionPointer(IGF, fnPtr);
48344878
}
48354879
auto *descriptorPtr =
48364880
IGF.Builder.CreateBitCast(fnPtr, IGF.IGM.AsyncFunctionPointerPtrTy);

lib/IRGen/GenThunk.cpp

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,34 @@ void IRGenModule::emitDispatchThunk(SILDeclRef declRef) {
344344

345345
llvm::Constant *
346346
IRGenModule::getAddrOfAsyncFunctionPointer(LinkEntity entity) {
347-
return getAddrOfLLVMVariable(
348-
LinkEntity::forAsyncFunctionPointer(entity),
349-
NotForDefinition, DebugTypeInfo());
347+
llvm::Constant *Pointer =
348+
getAddrOfLLVMVariable(LinkEntity::forAsyncFunctionPointer(entity),
349+
NotForDefinition, DebugTypeInfo());
350+
if (!getOptions().IndirectAsyncFunctionPointer)
351+
return Pointer;
352+
353+
// When the symbol does not have DLL Import storage, we must directly address
354+
// it. Otherwise, we will form an invalid reference.
355+
if (!Pointer->isDLLImportDependent())
356+
return Pointer;
357+
358+
llvm::Constant *PointerPointer =
359+
getOrCreateGOTEquivalent(Pointer,
360+
LinkEntity::forAsyncFunctionPointer(entity));
361+
llvm::Constant *PointerPointerConstant =
362+
llvm::ConstantExpr::getPtrToInt(PointerPointer, IntPtrTy);
363+
llvm::Constant *Marker =
364+
llvm::Constant::getIntegerValue(IntPtrTy, APInt(IntPtrTy->getBitWidth(),
365+
1));
366+
// TODO(compnerd) ensure that the pointer alignment guarantees that bit-0 is
367+
// cleared. We cannot use an `getOr` here as it does not form a relocatable
368+
// expression.
369+
llvm::Constant *Address =
370+
llvm::ConstantExpr::getAdd(PointerPointerConstant, Marker);
371+
372+
IndirectAsyncFunctionPointers[entity] = Address;
373+
return llvm::ConstantExpr::getIntToPtr(Address,
374+
AsyncFunctionPointerTy->getPointerTo());
350375
}
351376

352377
llvm::Constant *
@@ -373,6 +398,15 @@ IRGenModule::getSILFunctionForAsyncFunctionPointer(llvm::Constant *afp) {
373398
return entity.getSILFunction();
374399
}
375400
}
401+
for (auto &entry : IndirectAsyncFunctionPointers) {
402+
if (entry.getSecond() == afp) {
403+
auto entity = entry.getFirst();
404+
assert(getOptions().IndirectAsyncFunctionPointer &&
405+
"indirect async function found for non-indirect async function"
406+
" target?");
407+
return entity.getSILFunction();
408+
}
409+
}
376410
return nullptr;
377411
}
378412

lib/IRGen/IRGenModule.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,7 @@ class IRGenModule {
11051105
LinkEntity entity);
11061106

11071107
llvm::DenseMap<LinkEntity, llvm::Constant*> GlobalVars;
1108+
llvm::DenseMap<LinkEntity, llvm::Constant*> IndirectAsyncFunctionPointers;
11081109
llvm::DenseMap<LinkEntity, llvm::Constant*> GlobalGOTEquivalents;
11091110
llvm::DenseMap<LinkEntity, llvm::Function*> GlobalFuncs;
11101111
llvm::DenseSet<const clang::Decl *> GlobalClangDecls;

test/IRGen/async-inheritance.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift-dylib(%t/%target-library-name(L)) -module-name L -emit-module -emit-module-path %t/L.swiftmodule %s -DL
3+
// RUN: %target-build-swift -I%t -L%t -lL -parse-as-library %s -module-name E -o %t/E %target-rpath(%t)
4+
// RUN: %target-codesign %t/E
5+
// RUN: %target-run %t/E %t/%target-library-name(L) | %FileCheck %s
6+
7+
#if L
8+
open class C {
9+
public init() {}
10+
open func f() async {
11+
print("\(#function)")
12+
}
13+
}
14+
#else
15+
import L
16+
class D: C {
17+
}
18+
19+
@main
20+
struct S {
21+
public static func main() async {
22+
await D().f()
23+
}
24+
}
25+
#endif
26+
27+
// CHECK: f()

test/IRGen/async/class_resilience.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-swift-frontend -emit-module -disable-availability-checking -enable-library-evolution -emit-module-path=%t/resilient_class.swiftmodule -module-name=resilient_class %S/Inputs/resilient_class.swift
3-
// RUN: %target-swift-frontend -I %t -emit-ir -disable-availability-checking -enable-library-evolution %s | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-%target-cpu %s
3+
// RUN: %target-swift-frontend -I %t -emit-ir -disable-availability-checking -enable-library-evolution %s | %FileCheck -check-prefix CHECK -check-prefix CHECK-%target-cpu -check-prefix CHECK-%target-import-type %s
44
// REQUIRES: concurrency
55

66
import resilient_class
@@ -42,7 +42,11 @@ open class MyBaseClass<T> {
4242
// CHECK-SAME: %swift.async_func_pointer* @"$s16class_resilience9MyDerivedC4waitSiyYaF010resilient_A09BaseClassCADxyYaFTVTu"
4343

4444
// CHECK-LABEL: define {{(dllexport )?}}{{(protected )?}}swift{{(tail)?}}cc void @"$s16class_resilience14callsAwaitableyx010resilient_A09BaseClassCyxGYalF"(%swift.opaque* noalias nocapture %0, %swift.context* swiftasync %1{{.*}})
45-
// CHECK: %swift.async_func_pointer* @"$s15resilient_class9BaseClassC4waitxyYaFTjTu"
45+
// CHECK-DIRECT: %swift.async_func_pointer* @"$s15resilient_class9BaseClassC4waitxyYaFTjTu"
46+
// CHECK-INDIRECT: [[LOAD:%[0-9]+]] = load %swift.async_func_pointer*, %swift.async_func_pointer** inttoptr (i64 and (i64 add (i64 ptrtoint (%swift.async_func_pointer** @"\01__imp_$s15resilient_class9BaseClassC4waitxyYaFTjTu" to i64), i64 1), i64 -2) to %swift.async_func_pointer**), align {{4|8}}
47+
// CHECK-INDIRECT-NEXT: %14 = select i1 icmp eq (i64 and (i64 add (i64 ptrtoint (%swift.async_func_pointer** @"\01__imp_$s15resilient_class9BaseClassC4waitxyYaFTjTu" to i64), i64 1), i64 1), i64 0),
48+
// CHECK-INDIRECT-SAME: %swift.async_func_pointer* inttoptr (i64 add (i64 ptrtoint (%swift.async_func_pointer** @"\01__imp_$s15resilient_class9BaseClassC4waitxyYaFTjTu" to i64), i64 1) to %swift.async_func_pointer*),
49+
// CHECK-INDIRECT-SAME: %swift.async_func_pointer* [[LOAD]]
4650
// CHECK: ret void
4751
public func callsAwaitable<T>(_ c: BaseClass<T>) async -> T {
4852
return await c.wait()

test/IRGen/async/protocol_resilience.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// RUN: %empty-directory(%t)
22
// RUN: %target-swift-frontend -emit-module -disable-availability-checking -g -enable-library-evolution -emit-module-path=%t/resilient_protocol.swiftmodule -module-name=resilient_protocol %S/Inputs/resilient_protocol.swift
3-
// RUN: %target-swift-frontend -I %t -emit-ir -disable-availability-checking -g -enable-library-evolution %s | %FileCheck --check-prefix=CHECK --check-prefix=CHECK-%target-cpu %s
3+
// RUN: %target-swift-frontend -I %t -emit-ir -disable-availability-checking -g -enable-library-evolution %s | %FileCheck -check-prefix CHECK -check-prefix CHECK-%target-cpu -check-prefix CHECK-%target-import-type %s
44
// REQUIRES: concurrency
55

66
import resilient_protocol
@@ -27,7 +27,11 @@ public protocol MyAwaitable {
2727
// CHECK-SAME: %swift.async_func_pointer* @"$s19protocol_resilience19ConformsToAwaitableVyxG010resilient_A00E0AaeFP4wait6ResultQzyYaFTWTu"
2828

2929
// CHECK-LABEL: define {{(dllexport )?}}{{(protected )?}}swift{{(tail)?}}cc void @"$s19protocol_resilience14callsAwaitabley6ResultQzxYa010resilient_A00D0RzlF"(%swift.opaque* noalias nocapture %0, %swift.context* swiftasync %1, %swift.opaque* noalias nocapture %2, %swift.type* %T, i8** %T.Awaitable)
30-
// CHECK: %swift.async_func_pointer* @"$s18resilient_protocol9AwaitableP4wait6ResultQzyYaFTjTu"
30+
// CHECK-DIRECT: %swift.async_func_pointer* @"$s18resilient_protocol9AwaitableP4wait6ResultQzyYaFTjTu"
31+
// CHECK-INDIRECT: [[LOAD:%[0-9]+]] = load %swift.async_func_pointer*, %swift.async_func_pointer** inttoptr (i64 and (i64 add (i64 ptrtoint (%swift.async_func_pointer** @"\01__imp_$s18resilient_protocol9AwaitableP4wait6ResultQzyYaFTjTu" to i64), i64 1), i64 -2) to %swift.async_func_pointer**), align {{4|8}}
32+
// CHECK-INDIRECT: select i1 icmp eq (i64 and (i64 add (i64 ptrtoint (%swift.async_func_pointer** @"\01__imp_$s18resilient_protocol9AwaitableP4wait6ResultQzyYaFTjTu" to i64), i64 1), i64 1), i64 0),
33+
// CHECK-INDIRECT-SAME: %swift.async_func_pointer* inttoptr (i64 add (i64 ptrtoint (%swift.async_func_pointer** @"\01__imp_$s18resilient_protocol9AwaitableP4wait6ResultQzyYaFTjTu" to i64), i64 1) to %swift.async_func_pointer*),
34+
// CHECK-INDIRECT-SAME: %swift.async_func_pointer* [[LOAD]]
3135
// CHECK: ret void
3236
public func callsAwaitable<T : Awaitable>(_ t: T) async -> T.Result {
3337
return await t.wait()

test/IRGen/async/run-call-nonresilient-classinstance-void-to-void.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
// REQUIRES: concurrency
1212
// REQUIRES: concurrency_runtime
1313
// UNSUPPORTED: back_deployment_runtime
14-
// XFAIL: windows
1514

1615
import _Concurrency
1716
import NonresilientClass

0 commit comments

Comments
 (0)