Skip to content

Commit 0768d1e

Browse files
committed
IRGen: Fix resilient witness tables and vtables to correctly reference async methods
Resilient witness tables and resilient class vtables are built from descriptors. Make sure we reference the AsyncFunctionPointer of a method implementation, and not the implementation itself, if the method is async. Part of rdar://problem/73625623.
1 parent 844ce09 commit 0768d1e

File tree

9 files changed

+165
-5
lines changed

9 files changed

+165
-5
lines changed

lib/IRGen/GenMeta.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,14 @@ static void buildMethodDescriptorFields(IRGenModule &IGM,
289289

290290
if (auto entry = VTable->getEntry(IGM.getSILModule(), fn)) {
291291
assert(entry->getKind() == SILVTable::Entry::Kind::Normal);
292-
auto *implFn = IGM.getAddrOfSILFunction(entry->getImplementation(),
293-
NotForDefinition);
292+
293+
auto *impl = entry->getImplementation();
294+
llvm::Constant *implFn;
295+
if (impl->isAsync())
296+
implFn = IGM.getAddrOfAsyncFunctionPointer(impl);
297+
else
298+
implFn = IGM.getAddrOfSILFunction(impl, NotForDefinition);
299+
294300
descriptor.addRelativeAddress(implFn);
295301
} else {
296302
// The method is removed by dead method elimination.
@@ -1757,8 +1763,14 @@ namespace {
17571763
// The implementation of the override.
17581764
if (auto entry = VTable->getEntry(IGM.getSILModule(), baseRef)) {
17591765
assert(entry->getKind() == SILVTable::Entry::Kind::Override);
1760-
auto *implFn = IGM.getAddrOfSILFunction(entry->getImplementation(),
1761-
NotForDefinition);
1766+
1767+
auto *impl = entry->getImplementation();
1768+
llvm::Constant *implFn;
1769+
if (impl->isAsync())
1770+
implFn = IGM.getAddrOfAsyncFunctionPointer(impl);
1771+
else
1772+
implFn = IGM.getAddrOfSILFunction(impl, NotForDefinition);
1773+
17621774
descriptor.addRelativeAddress(implFn);
17631775
} else {
17641776
// The method is removed by dead method elimination.

lib/IRGen/GenProto.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1678,7 +1678,10 @@ void ResilientWitnessTableBuilder::collectResilientWitnesses(
16781678
SILFunction *Func = entry.getMethodWitness().Witness;
16791679
llvm::Constant *witness;
16801680
if (Func) {
1681-
witness = IGM.getAddrOfSILFunction(Func, NotForDefinition);
1681+
if (Func->isAsync())
1682+
witness = IGM.getAddrOfAsyncFunctionPointer(Func);
1683+
else
1684+
witness = IGM.getAddrOfSILFunction(Func, NotForDefinition);
16821685
} else {
16831686
// The method is removed by dead method elimination.
16841687
// It should be never called. We add a null pointer.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
open class BaseClass<T> {
2+
let value: T
3+
4+
public init(value: T) {
5+
self.value = value
6+
}
7+
8+
open func waitForNothing() async {}
9+
open func wait() async -> T {
10+
return value
11+
}
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public protocol Awaitable {
2+
associatedtype Result
3+
func waitForNothing() async
4+
func wait() async -> Result
5+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-build-swift-dylib(%t/%target-library-name(resilient_class)) -Xfrontend -enable-experimental-concurrency -enable-library-evolution %S/Inputs/resilient_class.swift -emit-module -emit-module-path %t/resilient_class.swiftmodule -module-name resilient_class
4+
// RUN: %target-codesign %t/%target-library-name(resilient_class)
5+
6+
// RUN: %target-build-swift -Xfrontend -enable-experimental-concurrency %s -lresilient_class -I %t -L %t -o %t/main %target-rpath(%t)
7+
// RUN: %target-codesign %t/main
8+
9+
// RUN: %target-run %t/main %t/%target-library-name(resilient_class)
10+
11+
// REQUIRES: executable_test
12+
// REQUIRES: concurrency
13+
14+
import StdlibUnittest
15+
import resilient_class
16+
17+
class MyDerived : BaseClass<Int> {
18+
override func waitForNothing() async {
19+
await super.waitForNothing()
20+
}
21+
22+
override func wait() async -> Int {
23+
return await super.wait() * 2
24+
}
25+
}
26+
27+
func virtualWaitForNothing<T>(_ c: BaseClass<T>) async {
28+
await c.waitForNothing()
29+
}
30+
31+
func virtualWait<T>(_ t: BaseClass<T>) async -> T {
32+
return await t.wait()
33+
}
34+
35+
var AsyncVTableMethodSuite = TestSuite("ResilientClass")
36+
37+
AsyncVTableMethodSuite.test("AsyncVTableMethod") {
38+
runAsyncAndBlock {
39+
let x = MyDerived(value: 321)
40+
41+
await virtualWaitForNothing(x)
42+
43+
expectEqual(642, await virtualWait(x))
44+
}
45+
}
46+
47+
runAllTests()
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-build-swift-dylib(%t/%target-library-name(resilient_protocol)) -Xfrontend -enable-experimental-concurrency -enable-library-evolution %S/Inputs/resilient_protocol.swift -emit-module -emit-module-path %t/resilient_protocol.swiftmodule -module-name resilient_protocol
4+
// RUN: %target-codesign %t/%target-library-name(resilient_protocol)
5+
6+
// RUN: %target-build-swift -Xfrontend -enable-experimental-concurrency %s -lresilient_protocol -I %t -L %t -o %t/main %target-rpath(%t)
7+
// RUN: %target-codesign %t/main
8+
9+
// RUN: %target-run %t/main %t/%target-library-name(resilient_protocol)
10+
11+
// REQUIRES: executable_test
12+
// REQUIRES: concurrency
13+
14+
import StdlibUnittest
15+
import resilient_protocol
16+
17+
struct IntAwaitable : Awaitable {
18+
func waitForNothing() async {}
19+
20+
func wait() async -> Int {
21+
return 123
22+
}
23+
}
24+
25+
func genericWaitForNothing<T : Awaitable>(_ t: T) async {
26+
await t.waitForNothing()
27+
}
28+
29+
func genericWait<T : Awaitable>(_ t: T) async -> T.Result {
30+
return await t.wait()
31+
}
32+
33+
var AsyncProtocolRequirementSuite = TestSuite("ResilientProtocol")
34+
35+
AsyncProtocolRequirementSuite.test("AsyncProtocolRequirement") {
36+
runAsyncAndBlock {
37+
let x = IntAwaitable()
38+
39+
await genericWaitForNothing(x)
40+
41+
expectEqual(123, await genericWait(x))
42+
}
43+
}
44+
45+
runAllTests()

test/IRGen/async/class_resilience.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ open class MyBaseClass<T> {
2727

2828
// CHECK-LABEL: @"$s16class_resilience11MyBaseClassC4waitxyYFTjTu" = {{(dllexport )?}}{{(protected )?}}global %swift.async_func_pointer
2929

30+
// CHECK-LABEL: @"$s16class_resilience11MyBaseClassCMn" = {{(dllexport )?}}{{(protected )?}}constant
31+
// CHECK-SAME: %swift.async_func_pointer* @"$s16class_resilience11MyBaseClassC4waitxyYFTu"
32+
33+
// CHECK-LABEL: @"$s16class_resilience9MyDerivedCMn" = hidden constant
34+
// CHECK-SAME: %swift.async_func_pointer* @"$s16class_resilience9MyDerivedC4waitSiyYF010resilient_A09BaseClassCADxyYFTVTu"
35+
3036
// CHECK-LABEL: define {{(dllexport )?}}{{(protected )?}}swiftcc void @"$s16class_resilience14callsAwaitableyx010resilient_A09BaseClassCyxGYlF"(%swift.task* %0, %swift.executor* %1, %swift.context* swiftasync %2)
3137
// CHECK: %swift.async_func_pointer* @"$s15resilient_class9BaseClassC4waitxyYFTjTu"
3238
// CHECK: ret void
@@ -35,3 +41,9 @@ public func callsAwaitable<T>(_ c: BaseClass<T>) async -> T {
3541
}
3642

3743
// CHECK-LABEL: define {{(dllexport )?}}{{(protected )?}}swiftcc void @"$s16class_resilience11MyBaseClassC4waitxyYFTj"(%swift.task* %0, %swift.executor* %1, %swift.context* swiftasync %2) #0 {
44+
45+
class MyDerived : BaseClass<Int> {
46+
override func wait() async -> Int {
47+
return await super.wait()
48+
}
49+
}

test/IRGen/async/protocol_resilience.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public protocol MyAwaitable {
1818

1919
// CHECK-LABEL: @"$s19protocol_resilience11MyAwaitableP4wait6ResultQzyYFTjTu" = {{(dllexport )?}}{{(protected )?}}global %swift.async_func_pointer
2020

21+
// CHECK-LABEL: @"$s19protocol_resilience19ConformsToAwaitableVyxG010resilient_A00E0AAMc" = hidden constant
22+
// CHECK-SAME: %swift.async_func_pointer* @"$s19protocol_resilience19ConformsToAwaitableVyxG010resilient_A00E0AaeFP4wait6ResultQzyYFTWTu"
23+
2124
// CHECK-LABEL: define {{(dllexport )?}}{{(protected )?}}swiftcc void @"$s19protocol_resilience14callsAwaitabley6ResultQzxY010resilient_A00D0RzlF"(%swift.task* %0, %swift.executor* %1, %swift.context* swiftasync %2)
2225
// CHECK: %swift.async_func_pointer* @"$s18resilient_protocol9AwaitableP4wait6ResultQzyYFTjTu"
2326
// CHECK: ret void
@@ -26,3 +29,11 @@ public func callsAwaitable<T : Awaitable>(_ t: T) async -> T.Result {
2629
}
2730

2831
// CHECK-LABEL: define {{(dllexport )?}}{{(protected )?}}swiftcc void @"$s19protocol_resilience11MyAwaitableP4wait6ResultQzyYFTj"(%swift.task* %0, %swift.executor* %1, %swift.context* swiftasync %2)
32+
33+
struct ConformsToAwaitable<T> : Awaitable {
34+
var value: T
35+
36+
func wait() async -> T {
37+
return value
38+
}
39+
}

test/SILGen/async_vtable_thunk.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// RUN: %target-swift-frontend -emit-silgen %s -enable-experimental-concurrency | %FileCheck %s
2+
// REQUIRES: concurrency
3+
4+
class BaseClass<T> {
5+
func wait() async -> T {}
6+
}
7+
8+
class Derived : BaseClass<Int> {
9+
override func wait() async -> Int {}
10+
}
11+
12+
// CHECK-LABEL: sil private [thunk] [ossa] @$s18async_vtable_thunk7DerivedC4waitSiyYFAA9BaseClassCADxyYFTV : $@convention(method) @async (@guaranteed Derived) -> @out Int {
13+

0 commit comments

Comments
 (0)