Skip to content

[PackageCMO] Make optimized binary module work for package-external client. #76729

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
Oct 9, 2024
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
1 change: 0 additions & 1 deletion include/swift/Serialization/SerializedSILLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class SerializedSILLoader {
explicit SerializedSILLoader(
ASTContext &ctx, SILModule *SILMod,
DeserializationNotificationHandlerSet *callbacks);

public:
/// Create a new loader.
///
Expand Down
33 changes: 33 additions & 0 deletions lib/Serialization/DeserializeSIL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,25 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn,

if (funcTyID == 0)
return MF->diagnoseFatal("SILFunction typeID is 0");

// A function with [serialized_for_package] and its body should only be
// deserialized in clients within the same package as its defining module;
// for external clients, it should appear only as a declaration. If the
// function has shared linkage, it must have been internal or private in
// its defining module that's optimized, thus should remain inaccessible
// outside the package boundary. This ensures that instructions, which may
// only be valid in resilient mode when package optimization is enabled,
// aren't inlined at the call site, preventing potential assert fails during
// sil-verify.
if (serializedKind == SerializedKind_t::IsSerializedForPackage) {
if (!SILMod.getSwiftModule()->inSamePackage(getFile()->getParentModule())) {
if (rawLinkage == (unsigned int)(SILLinkage::Shared))
return nullptr;
if (!declarationOnly)
declarationOnly = true;
}
}

auto astType = MF->getTypeChecked(funcTyID);
if (!astType) {
if (!existingFn || errorIfEmptyBody) {
Expand Down Expand Up @@ -3828,6 +3847,13 @@ SILVTable *SILDeserializer::readVTable(DeclID VId) {
return nullptr;
}

// Vtable with [serialized_for_package], i.e. optimized with Package CMO,
// should only be deserialized into a client if its defning module and the
// client are in the package.
if (!SILMod.getSwiftModule()->inSamePackage(getFile()->getParentModule()) &&
SerializedKind_t(Serialized) == SerializedKind_t::IsSerializedForPackage)
return nullptr;

ClassDecl *theClass = cast<ClassDecl>(MF->getDecl(ClassID));

PrettyStackTraceDecl trace("deserializing SIL vtable for", theClass);
Expand Down Expand Up @@ -4201,6 +4227,13 @@ llvm::Expected<SILWitnessTable *>
MF->fatal("invalid linkage code");
}

// Witness with [serialized_for_package], i.e. optimized with Package CMO,
// should only be deserialized into a client if its defning module and the
// client are in the package.
if (!SILMod.getSwiftModule()->inSamePackage(getFile()->getParentModule()) &&
SerializedKind_t(Serialized) == SerializedKind_t::IsSerializedForPackage)
return nullptr;

// Deserialize Conformance.
auto maybeConformance = MF->getConformanceChecked(conformance);
if (!maybeConformance)
Expand Down
129 changes: 129 additions & 0 deletions test/SILOptimizer/package-cmo-deserialize-for-external-client.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// RUN: %empty-directory(%t)
// RUN: split-file %s %t

// RUN: %target-swift-frontend -emit-module %t/Lib.swift -I %t \
// RUN: -module-name Lib -package-name pkg \
// RUN: -enable-library-evolution -swift-version 5 \
// RUN: -O -wmo -experimental-allow-non-resilient-access -experimental-package-cmo \
// RUN: -emit-module -emit-module-path %t/Lib.swiftmodule
// RUN: %target-sil-opt %t/Lib.swiftmodule -o %t/Lib.sil

// RUN: %target-swift-frontend -emit-sil -O %t/Client.swift -I %t -package-name pkg \
// RUN: -Xllvm -sil-disable-pass=DeadFunctionAndGlobalElimination -o %t/InPkgClient.sil

// RUN: %target-swift-frontend -emit-sil -O %t/Client.swift -I %t \
// RUN: -Xllvm -sil-disable-pass=DeadFunctionAndGlobalElimination -o %t/ExtClient.sil

/// TEST how functions (and bodies) and tables optimized with [serialized_for_package] in Lib are
/// deserialized into Client depending on package boundary.
///
/// Test 1: They should be deserialized into Client as Lib and Client are in the same package.
// RUN: %FileCheck -check-prefix=CHECK-INPKG %s < %t/InPkgClient.sil

// Pub.init(_:) is removed after getting lined below.
// CHECK-INPKG-NOT: @$s3Lib3PubCyACSicfc

// Pub.__allocating_init(_:)
// CHECK-INPKG: sil public_external @$s3Lib3PubCyACSicfC : $@convention(method) (Int, @thick Pub.Type) -> @owned Pub {
// Pub.init(_:) is inlined in this block, as its body was deserialized.
// CHECK-INPKG: ref_element_addr {{.*}} : $Pub, #Pub.pubVar

// CHECK-INPKG: sil_vtable Pub {
// CHECK-INPKG: #Pub.pubVar!getter: (Pub) -> () -> Int : @$s3Lib3PubC6pubVarSivg // Pub.pubVar.getter
// CHECK-INPKG: #Pub.pubVar!setter: (Pub) -> (Int) -> () : @$s3Lib3PubC6pubVarSivs // Pub.pubVar.setter
// CHECK-INPKG: #Pub.pubVar!modify: (Pub) -> () -> () : @$s3Lib3PubC6pubVarSivM // Pub.pubVar.modify
// CHECK-INPKG: #Pub.pkgVar!getter: (Pub) -> () -> Int : @$s3Lib3PubC6pkgVarSivg // Pub.pkgVar.getter
// CHECK-INPKG: #Pub.pkgVar!setter: (Pub) -> (Int) -> () : @$s3Lib3PubC6pkgVarSivs // Pub.pkgVar.setter
// CHECK-INPKG: #Pub.pkgVar!modify: (Pub) -> () -> () : @$s3Lib3PubC6pkgVarSivM // Pub.pkgVar.modify
// CHECK-INPKG: #Pub.init!allocator: (Pub.Type) -> (Int) -> Pub : @$s3Lib3PubCyACSicfC // Pub.__allocating_init(_:)
// CHECK-INPKG: #Pub.deinit!deallocator: @$s3Lib3PubCfD // Pub.__deallocating_deinit

// CHECK-INPKG: sil_witness_table public_external Pub: PubProto module Lib {
// CHECK-INPKG: method #PubProto.pubVar!getter: <Self where Self : PubProto> (Self) -> () -> Int : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivgTW // protocol witness for PubProto.pubVar.getter in conformance Pub
// CHECK-INPKG: method #PubProto.pubVar!setter: <Self where Self : PubProto> (inout Self) -> (Int) -> () : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivsTW // protocol witness for PubProto.pubVar.setter in conformance Pub
// CHECK-INPKG: method #PubProto.pubVar!modify: <Self where Self : PubProto> (inout Self) -> () -> () : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivMTW // protocol witness for PubProto.pubVar.modify in conformance Pub


/// Test 2: They should NOT be deserialized into Client as Lib and Client are NOT in the same package;
/// only declaration should be deserialized, not the body.
// RUN: %FileCheck -check-prefix=CHECK-EXT %s < %t/ExtClient.sil

// CHECK-EXT-NOT: sil_vtable Pub {
// CHECK-EXT-NOT: #Pub.pubVar!getter: (Pub) -> () -> Int : @$s3Lib3PubC6pubVarSivg // Pub.pubVar.getter
// CHECK-EXT-NOT: #Pub.pubVar!setter: (Pub) -> (Int) -> () : @$s3Lib3PubC6pubVarSivs // Pub.pubVar.setter
// CHECK-EXT-NOT: #Pub.pubVar!modify: (Pub) -> () -> () : @$s3Lib3PubC6pubVarSivM // Pub.pubVar.modify
// CHECK-EXT-NOT: #Pub.pkgVar!getter: (Pub) -> () -> Int : @$s3Lib3PubC6pkgVarSivg // Pub.pkgVar.getter
// CHECK-EXT-NOT: #Pub.pkgVar!setter: (Pub) -> (Int) -> () : @$s3Lib3PubC6pkgVarSivs // Pub.pkgVar.setter
// CHECK-EXT-NOT: #Pub.pkgVar!modify: (Pub) -> () -> () : @$s3Lib3PubC6pkgVarSivM // Pub.pkgVar.modify
// CHECK-EXT-NOT: #Pub.init!allocator: (Pub.Type) -> (Int) -> Pub : @$s3Lib3PubCyACSicfC // Pub.__allocating_init(_:)
// CHECK-EXT-NOT: #Pub.deinit!deallocator: @$s3Lib3PubCfD // Pub.__deallocating_deinit

// CHECK-EXT-NOT: sil_witness_table public_external Pub: PubProto module Lib {
// CHECK-EXT-NOT: method #PubProto.pubVar!getter: <Self where Self : PubProto> (Self) -> () -> Int : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivgTW // protocol witness for PubProto.pubVar.getter in conformance Pub
// CHECK-EXT-NOT: method #PubProto.pubVar!setter: <Self where Self : PubProto> (inout Self) -> (Int) -> () : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivsTW // protocol witness for PubProto.pubVar.setter in conformance Pub
// CHECK-EXT-NOT: method #PubProto.pubVar!modify: <Self where Self : PubProto> (inout Self) -> () -> () : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivMTW // protocol witness for PubProto.pubVar.modify in conformance Pub

// Pub.init(_:) is just a decl; does not contain the body.
// CHECK-EXT: sil @$s3Lib3PubCyACSicfc : $@convention(method) (Int, @owned Pub) -> @owned Pub


/// Verify functions and tables are optimized with Package CMO in Lib.
// RUN: %FileCheck -check-prefix=CHECK-LIB %s < %t/Lib.sil

// CHECK-LIB: sil [serialized] [exact_self_class] [canonical] @$s3Lib3PubCyACSicfC : $@convention(method) (Int, @thick Pub.Type) -> @owned Pub {
// CHECK-LIB: function_ref @$s3Lib3PubCyACSicfc : $@convention(method) (Int, @owned Pub) -> @owned Pub
// Pub.init(_:)

// CHECK-LIB: sil [serialized_for_package] [canonical] @$s3Lib3PubCyACSicfc : $@convention(method) (Int, @owned Pub) -> @owned Pub {
// CHECK-LIB: ref_element_addr {{.*}} : $Pub, #Pub.pubVar
// Pub.__allocating_init(_:)

// Pub.pkgVar.getter
// CHECK-LIB: sil package [serialized_for_package] [canonical] @$s3Lib3PubC6pkgVarSivg : $@convention(method) (@guaranteed Pub) -> Int {

// CHECK-LIB: sil_vtable [serialized_for_package] Pub {
// CHECK-LIB: #Pub.pubVar!getter: (Pub) -> () -> Int : @$s3Lib3PubC6pubVarSivg // Pub.pubVar.getter
// CHECK-LIB: #Pub.pubVar!setter: (Pub) -> (Int) -> () : @$s3Lib3PubC6pubVarSivs // Pub.pubVar.setter
// CHECK-LIB: #Pub.pubVar!modify: (Pub) -> () -> () : @$s3Lib3PubC6pubVarSivM // Pub.pubVar.modify
// CHECK-LIB: #Pub.pkgVar!getter: (Pub) -> () -> Int : @$s3Lib3PubC6pkgVarSivg // Pub.pkgVar.getter
// CHECK-LIB: #Pub.pkgVar!setter: (Pub) -> (Int) -> () : @$s3Lib3PubC6pkgVarSivs // Pub.pkgVar.setter
// CHECK-LIB: #Pub.pkgVar!modify: (Pub) -> () -> () : @$s3Lib3PubC6pkgVarSivM // Pub.pkgVar.modify
// CHECK-LIB: #Pub.init!allocator: (Pub.Type) -> (Int) -> Pub : @$s3Lib3PubCyACSicfC // Pub.__allocating_init(_:)
// CHECK-LIB: #Pub.deinit!deallocator: @$s3Lib3PubCfD // Pub.__deallocating_deinit

// CHECK-LIB: sil_witness_table [serialized_for_package] Pub: PubProto module Lib {
// CHECK-LIB: method #PubProto.pubVar!getter: <Self where Self : PubProto> (Self) -> () -> Int : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivgTW // protocol witness for PubProto.pubVar.getter in conformance Pub
// CHECK-LIB: method #PubProto.pubVar!setter: <Self where Self : PubProto> (inout Self) -> (Int) -> () : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivsTW // protocol witness for PubProto.pubVar.setter in conformance Pub
// CHECK-LIB: method #PubProto.pubVar!modify: <Self where Self : PubProto> (inout Self) -> () -> () : @$s3Lib3PubCAA0B5ProtoA2aDP6pubVarSivMTW // protocol witness for PubProto.pubVar.modify in conformance Pub


//--- Lib.swift

public protocol PubProto {
var pubVar: Int { get set }
}

public class Pub: PubProto {
public var pubVar: Int = 1
package var pkgVar: Int = 2
public init(_ arg: Int) {
pubVar = arg
pkgVar = arg
}
}

//--- Client.swift
import Lib

public func test(_ arg: Int) -> Int {
let x = Pub(arg)
x.pubVar = 3
return x.run()
}

public extension PubProto {
func run() -> Int {
return pubVar
}
}