Skip to content

IRGen: fix async vtable stubs #65940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions lib/IRGen/GenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2103,8 +2103,20 @@ void IRGenModule::emitVTableStubs() {
}

// For each eliminated method symbol create an alias to the stub.
auto *alias = llvm::GlobalAlias::create(llvm::GlobalValue::ExternalLinkage,
F.getName(), stub);
llvm::GlobalValue *alias = nullptr;
if (F.isAsync()) {
// TODO: We cannot directly create a pointer to `swift_deletedAsyncMethodError`
// to workaround a linker crash.
// Instead use the stub, which calls swift_deletedMethodError. This works because
// swift_deletedMethodError takes no parameters and simply aborts the program.
auto asyncLayout = getAsyncContextLayout(*this, const_cast<SILFunction *>(&F));
auto entity = LinkEntity::forSILFunction(const_cast<SILFunction *>(&F));
auto *fnPtr = emitAsyncFunctionPointer(*this, stub, entity, asyncLayout.getSize());
alias = fnPtr;
} else {
alias = llvm::GlobalAlias::create(llvm::GlobalValue::ExternalLinkage,
F.getName(), stub);
}

if (F.getEffectiveSymbolLinkage() == SILLinkage::Hidden)
alias->setVisibility(llvm::GlobalValue::HiddenVisibility);
Expand Down
71 changes: 58 additions & 13 deletions test/IRGen/Inputs/report_dead_method_call/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,24 @@ func testProto(_ c: Container) {
c.p.abc()
}

@inline(never)
func testProtoAsync(_ c: Container) async {
// call the dead witness method abcAsync()
await c.p.abcAsync()
}

@inline(never)
func testClass(_ c: ClassContainer) {
// call the dead vtable method def()
c.p.def()
}

@inline(never)
func testClassAsync(_ c: ClassContainer) async {
// call the dead vtable method defAsync()
await c.p.defAsync()
}

public class PublicDerived : PublicBase {
// The vtable of PublicDerived contains a reference to PublicBase.ghi()
}
Expand All @@ -31,22 +43,55 @@ func testPublicClass(_ c: PublicBase) {
c.ghi()
}

let ReportDeadMethodCallTestSuite = TestSuite("ReportDeadMethodCall")

ReportDeadMethodCallTestSuite.test("Call class") {
expectCrashLater()
callClass()
@inline(never)
func callPublicClassAsync() async {
await testPublicClassAsync(PublicDerived())
}

ReportDeadMethodCallTestSuite.test("Call proto") {
expectCrashLater()
callProto()
@inline(never)
func testPublicClassAsync(_ c: PublicBase) async {
// call the dead private vtable method ghiAsync()
await c.ghiAsync()
}

ReportDeadMethodCallTestSuite.test("Call public class") {
expectCrashLater()
callPublicClass()
}
@main struct Main {
static func main() async {

let tests = TestSuite("ReportDeadMethodCall")

tests.test("Call class") {
expectCrashLater(withMessage: "Fatal error: Call of deleted method")
callClass()
}

tests.test("Call class async") {
// TODO: it should crash with the error message and not with sigsegv
expectCrashLater()
await callClassAsync()
}

tests.test("Call proto") {
expectCrashLater(withMessage: "Fatal error: Call of deleted method")
callProto()
}

tests.test("Call proto async") {
// TODO: it should crash with the error message and not with sigsegv
expectCrashLater(withMessage: "")
await callProtoAsync()
}

tests.test("Call public class") {
expectCrashLater(withMessage: "Fatal error: Call of deleted method")
callPublicClass()
}

tests.test("Call public class async") {
expectCrashLater(withMessage: "Fatal error: Call of deleted method")
await callPublicClassAsync()
}

runAllTests()
await runAllTestsAsync()
}
}

35 changes: 26 additions & 9 deletions test/IRGen/report_dead_method_call.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@

// We compile with -O (optimizations) and -disable-access-control (which
// allows use to "call" methods that are removed by dead code elimination).
// RUN: %target-build-swift %S/Inputs/report_dead_method_call/main.swift %s -O -Xfrontend -disable-access-control -o %t/report_dead_method_call
// RUN: %target-build-swift -parse-as-library %S/Inputs/report_dead_method_call/main.swift %s -O -Xfrontend -disable-access-control -Xfrontend -disable-availability-checking -o %t/report_dead_method_call

// The private, unused methods are optimized away. The test calls these
// methods anyway (since it has overridden the access control), so we
// expect them to produce "Fatal error: Call of deleted method" when run.
// RUN: %target-codesign %t/report_dead_method_call
// RUN: %target-run %t/report_dead_method_call

// REQUIRES: executable_test
// REQUIRES: concurrency
// UNSUPPORTED: freestanding
// REQUIRES: concurrency_runtime
// UNSUPPORTED: back_deployment_runtime

// UNSUPPORTED: swift_test_mode_optimize_none_with_implicit_dynamic
// UNSUPPORTED: swift_test_mode_optimize_with_implicit_dynamic

private protocol PrivateProto {
func abc()
func abcAsync() async
}

struct PrivateStructC : PrivateProto {
func abc() {
}
func abc() {}
func abcAsync() async {}
}

struct Container {
Expand All @@ -33,14 +39,19 @@ func callProto() {
testProto(Container())
}

@inline(never)
func callProtoAsync() async {
await testProtoAsync(Container())
}

private class Base {
func def() {
}
func def() {}
func defAsync() async {}
}

private class Derived : Base {
override func def() {
}
override func def() {}
override func defAsync() async {}
}

struct ClassContainer {
Expand All @@ -53,8 +64,14 @@ func callClass() {
testClass(ClassContainer())
}

@inline(never)
func callClassAsync() async {
await testClassAsync(ClassContainer())
}

public class PublicBase {
private func ghi() {
}
private func ghi() { }

private func ghiAsync() async {}
}

22 changes: 19 additions & 3 deletions test/IRGen/static-vtable-stubs.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
// RUN: %empty-directory(%t)
// RUN: split-file --leading-lines %s %t
// RUN: %swift-target-frontend -parse-as-library -static -O -module-name M -c -primary-file %t/A.swift %t/B.swift -S -emit-ir -o - | %FileCheck %t/A.swift -check-prefix CHECK
// RUN: %swift-target-frontend -parse-as-library -static -O -module-name M -c %t/A.swift -primary-file %t/B.swift -S -emit-ir -o - | %FileCheck %t/B.swift -check-prefix CHECK
// RUN: %swift-target-frontend -disable-availability-checking -parse-as-library -static -O -module-name M -c -primary-file %t/A.swift %t/B.swift -S -emit-ir -o - | %FileCheck %t/A.swift -check-prefix CHECK
// RUN: %swift-target-frontend -disable-availability-checking -parse-as-library -static -O -module-name M -c %t/A.swift -primary-file %t/B.swift -S -emit-ir -o - | %FileCheck %t/B.swift -check-prefix CHECK

// Verify that we can link successfully.
// RUN: %target-build-swift -Xfrontend -disable-availability-checking -O %t/A.swift %t/B.swift -o %t/a.out

// REQUIRES: concurrency

//--- A.swift
open class C {
private var i: [ObjectIdentifier:Any] = [:]
private var i: [ObjectIdentifier:Any] = [:]

private func foo() async {}
}

// CHECK: @"$s1M1CC3foo33_{{.*}}Tu" = hidden global %swift.async_func_pointer <{ {{.*}} @_swift_dead_method_stub

// CHECK: @"$s1M1CC1i33_807E3D81CC6CDD898084F3279464DDF9LLSDySOypGvg" = hidden alias void (), void ()* @_swift_dead_method_stub
// CHECK: @"$s1M1CC1i33_807E3D81CC6CDD898084F3279464DDF9LLSDySOypGvs" = hidden alias void (), void ()* @_swift_dead_method_stub
// CHECK: @"$s1M1CC1i33_807E3D81CC6CDD898084F3279464DDF9LLSDySOypGvM" = hidden alias void (), void ()* @_swift_dead_method_stub
Expand All @@ -19,3 +28,10 @@ final class D: C {
// CHECK: declare swiftcc %swift.bridge* @"$s1M1CC1i33_807E3D81CC6CDD898084F3279464DDF9LLSDySOypGvg"(%T1M1CC* swiftself) #0
// CHECK: declare swiftcc void @"$s1M1CC1i33_807E3D81CC6CDD898084F3279464DDF9LLSDySOypGvs"(%swift.bridge*, %T1M1CC* swiftself) #0
// CHECK: declare swiftcc { i8*, %TSD* } @"$s1M1CC1i33_807E3D81CC6CDD898084F3279464DDF9LLSDySOypGvM"(i8* noalias dereferenceable(32), %T1M1CC* swiftself) #0

@main
struct Main {
static func main() {
}
}