From 9b28969e2d0171ebaeaf14f66b8c07498e6e7e40 Mon Sep 17 00:00:00 2001 From: Ellie Shin Date: Wed, 8 May 2024 05:00:36 -0700 Subject: [PATCH] To support serializing functions containing loadable types in a resiliently built module when package serialization is enabled, return maximal resilience expansion in SILFunction::getResilienceExpansion(). This allows aggregate types to be generated as loadable SIL types which otherwise are address-only in a serialized function. During type lowering, opaque flag setting is also skipped if package serialization is enabled. Resolves rdar://127400743 --- include/swift/SIL/SILFunction.h | 6 +- lib/SIL/IR/SILFunction.cpp | 17 ++++ lib/SIL/IR/TypeLowering.cpp | 12 ++- lib/SIL/Verifier/SILVerifier.cpp | 15 +--- .../IPO/CrossModuleOptimization.cpp | 22 +----- .../package-cmo-resilient-mode.swift | 77 +++++++++++++++---- 6 files changed, 96 insertions(+), 53 deletions(-) diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index c8016f41e374b..34f8b40f2cc2d 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -779,11 +779,7 @@ class SILFunction return getLoweredFunctionType()->getRepresentation(); } - ResilienceExpansion getResilienceExpansion() const { - return (isSerialized() - ? ResilienceExpansion::Minimal - : ResilienceExpansion::Maximal); - } + ResilienceExpansion getResilienceExpansion() const; // Returns the type expansion context to be used inside this function. TypeExpansionContext getTypeExpansionContext() const { diff --git a/lib/SIL/IR/SILFunction.cpp b/lib/SIL/IR/SILFunction.cpp index ad2bfdca46d48..b64195f794a33 100644 --- a/lib/SIL/IR/SILFunction.cpp +++ b/lib/SIL/IR/SILFunction.cpp @@ -517,6 +517,23 @@ bool SILFunction::isNoReturnFunction(TypeExpansionContext context) const { .isNoReturnFunction(getModule(), context); } +ResilienceExpansion SILFunction::getResilienceExpansion() const { + // If package serialization is enabled, we can safely + // assume that the defining .swiftmodule is built from + // source and is never used outside of its package; + // Even if the module is built resiliently, return + // maximal expansion here so aggregate types can be + // loadable in the same resilient domain (from a client + // module in the same package. + if (getModule().getSwiftModule()->serializePackageEnabled() && + getModule().getSwiftModule()->isResilient()) + return ResilienceExpansion::Maximal; + + return (isSerialized() + ? ResilienceExpansion::Minimal + : ResilienceExpansion::Maximal); +} + const TypeLowering & SILFunction::getTypeLowering(AbstractionPattern orig, Type subst) { return getModule().Types.getTypeLowering(orig, subst, diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 86caf813b2970..b9a1ee3edc685 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2324,17 +2324,25 @@ namespace { if (D->isResilient()) { // If the type is resilient and defined in our module, make a note of // that, since our lowering now depends on the resilience expansion. - bool sameModule = (D->getModuleContext() == &TC.M); + auto declModule = D->getModuleContext(); + bool sameModule = (declModule == &TC.M); if (sameModule) properties.addSubobject(RecursiveProperties::forResilient()); // If the type is in a different module, or if we're using a minimal // expansion, the type is address only and completely opaque to us. + // However, this is not true if the different module is in the same + // package and package serialization is enabled (resilience expansion + // is maximal), e.g. in case of package-cmo. // // Note: if the type is in a different module, the lowering does // not depend on the resilience expansion, so we do not need to set // the isResilient() flag above. - if (!sameModule || Expansion.getResilienceExpansion() == + bool serializedPackage = declModule->inSamePackage(&TC.M) && + declModule->isResilient() && + declModule->serializePackageEnabled(); + if ((!sameModule && !serializedPackage) || + Expansion.getResilienceExpansion() == ResilienceExpansion::Minimal) { properties.addSubobject(RecursiveProperties::forOpaque()); return true; diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index c90a3c28fd50c..93d17abfa974d 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -158,22 +158,9 @@ namespace { template bool checkResilience(DeclType *D, ModuleDecl *M, ResilienceExpansion expansion) { - auto refDeclModule = D->getModuleContext(); // Explicitly bypassed for debugging with `bypass-resilience-checks` - if (refDeclModule->getBypassResilience()) + if (D->getModuleContext()->getBypassResilience()) return false; - - // If package serialization is enabled with `experimental-package-cmo`, - // decls can be serialized in a resiliently built module. In such case, - // a direct access should be allowed. - auto packageSerialized = expansion == ResilienceExpansion::Minimal && - refDeclModule->isResilient() && - refDeclModule->allowNonResilientAccess() && - refDeclModule->serializePackageEnabled() && - refDeclModule->inSamePackage(M); - if (packageSerialized) - return false; - return D->isResilient(M, expansion); } diff --git a/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp b/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp index 99ef125fffd00..5597a99b94031 100644 --- a/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp +++ b/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp @@ -84,7 +84,7 @@ class CrossModuleOptimization { bool canSerializeGlobal(SILGlobalVariable *global); - bool canSerializeType(SILType type, TypeExpansionContext typeExpCtx); + bool canSerializeType(SILType type); bool canUseFromInline(DeclContext *declCtxt); @@ -331,14 +331,12 @@ bool CrossModuleOptimization::canSerializeFunction( bool CrossModuleOptimization::canSerializeInstruction( SILInstruction *inst, FunctionFlags &canSerializeFlags, int maxDepth) { // First check if any result or operand types prevent serialization. - auto typeExpCtx = inst->getFunction()->getTypeExpansionContext(); - for (SILValue result : inst->getResults()) { - if (!canSerializeType(result->getType(), typeExpCtx)) + if (!canSerializeType(result->getType())) return false; } for (Operand &op : inst->getAllOperands()) { - if (!canSerializeType(op.get()->getType(), typeExpCtx)) + if (!canSerializeType(op.get()->getType())) return false; } @@ -437,23 +435,11 @@ bool CrossModuleOptimization::canSerializeGlobal(SILGlobalVariable *global) { return true; } -bool CrossModuleOptimization::canSerializeType(SILType type, - TypeExpansionContext typeExpCtx) { +bool CrossModuleOptimization::canSerializeType(SILType type) { auto iter = typesChecked.find(type); if (iter != typesChecked.end()) return iter->getSecond(); - if (M.getSwiftModule()->isResilient()) { - auto minResilientCtx = TypeExpansionContext(ResilienceExpansion::Minimal, - typeExpCtx.getContext(), - typeExpCtx.isWholeModuleContext()); - auto loadableInMinResilientCtx = M.Types.getTypeLowering(type, minResilientCtx).isLoadable(); - if (!loadableInMinResilientCtx) { - typesChecked[type] = false; - return false; - } - } - bool success = !type.getASTType().findIf( [this](Type rawSubType) { CanType subType = rawSubType->getCanonicalType(); diff --git a/test/SILOptimizer/package-cmo-resilient-mode.swift b/test/SILOptimizer/package-cmo-resilient-mode.swift index 2b7a6837c04be..a8516d0e98292 100644 --- a/test/SILOptimizer/package-cmo-resilient-mode.swift +++ b/test/SILOptimizer/package-cmo-resilient-mode.swift @@ -172,43 +172,73 @@ print(prevPkgData) //--- Lib.swift -// FIXME: handle struct_element_addr %field in resilient mode; requires non-resilience in SIL verify. -// CHECK-RES-NOT: s3Lib9PubStructV6fooVarSivg -// CHECK-RES-NOT: s3Lib9PkgStructV6fooVarSivg - -// FIXME: handle `struct $PubStruct` in resilient mode; PubStruct is by-address, so fails in IsLodableOrOpaque check. -// CHECK-RES-NOT: s3Lib9PubStructV6fooVarSivs -// CHECK-RES-NOT: s3Lib9PkgStructV6fooVarSivs - public struct PubStruct { + // PubStruct.foovar.getter + // CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV6fooVarSivg : $@convention(method) (@in_guaranteed PubStruct) -> Int { // CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivg : $@convention(method) (PubStruct) -> Int - // CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivM : $@yield_once @convention(method) (@inout PubStruct) -> @yields @inout Int { + // CHECK-RES-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PubStruct, #PubStruct.fooVar + // CHECK-RES-DAG: load [[FIELD]] : $*Int + // CHECK-NONRES-DAG = struct_extract %0 : $PubStruct, #PubStruct.fooVar + + // PubStruct.foovar.setter + // CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV6fooVarSivs : $@convention(method) (Int, @inout PubStruct) -> () { // CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivs : $@convention(method) (Int, @inout PubStruct) -> () { + + /// NOTE: `struct $PubStruct` in [serialized] function is legal only if package serialization is enabled. + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PubStruct + // CHECK-RES-DAG: store [[FIELD]] to {{.*}} : $*PubStruct + // CHECK-NONRES-DAG: store [[FIELD]] to [trivial] {{.*}} : $*PubStruct + + // PubStruct.foovar.modify + // CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV6fooVarSivM : $@yield_once @convention(method) (@inout PubStruct) -> @yields @inout Int { + // CHECK-NONRES-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib9PubStructV6fooVarSivM : $@yield_once @convention(method) (@inout PubStruct) -> @yields @inout Int { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PubStruct, #PubStruct.fooVar + // CHECK-COMMON-DAG: yield [[FIELD]] public var fooVar: Int public init(_ arg: Int) { + // CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructVyACSicfC : $@convention(method) (Int, @thin PubStruct.Type) -> @out PubStruct { // CHECK-NONRES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructVyACSicfC : $@convention(method) (Int, @thin PubStruct.Type) -> PubStruct { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PubStruct + // CHECK-RES-DAG: store [[FIELD]] to %0 : $*PubStruct + // CHECK-NONRES-DAG: return [[FIELD]] : $PubStruct fooVar = arg } public func f() { + // CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV1fyyF : $@convention(method) (@in_guaranteed PubStruct) -> () { // CHECK-NONRES-DAG: sil [serialized] [canonical] @$s3Lib9PubStructV1fyyF : $@convention(method) (PubStruct) -> () { print(fooVar) } } public func runPub(_ arg: PubStruct) { + // CHECK-RES-DAG: sil [serialized] [canonical] @$s3Lib6runPubyyAA0C6StructVF : $@convention(thin) (@in_guaranteed PubStruct) -> () { // CHECK-NONRES-DAG: sil [serialized] [canonical] @$s3Lib6runPubyyAA0C6StructVF : $@convention(thin) (PubStruct) -> () { print(arg) } @frozen public struct FrPubStruct { - // CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivM : $@yield_once @convention(method) (@inout FrPubStruct) -> @yields @inout Int { + // FrPubStruct.fooVar.getter // CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivg : $@convention(method) (FrPubStruct) -> Int { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_extract %0 : $FrPubStruct, #FrPubStruct.fooVar + // CHECK-COMMON-DAG: return [[FIELD]] : $Int + + // FrPubStruct.fooVar.setter // CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivs : $@convention(method) (Int, @inout FrPubStruct) -> () { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $FrPubStruct + // CHECK-COMMON-DAG: store [[FIELD]] to [trivial] {{.*}} : $*FrPubStruct + + // FrPubStruct.fooVar.modify + // CHECK-COMMON-DAG: sil [transparent] [serialized] [canonical] [ossa] @$s3Lib11FrPubStructV6fooVarSivM : $@yield_once @convention(method) (@inout FrPubStruct) -> @yields @inout Int { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*FrPubStruct, #FrPubStruct.fooVar + // CHECK-COMMON-DAG: yield [[FIELD]] public var fooVar: Int + public init(_ arg: Int) { // CHECK-COMMON-DAG: sil [serialized] [canonical] @$s3Lib11FrPubStructVyACSicfC : $@convention(method) (Int, @thin FrPubStruct.Type) -> FrPubStruct { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $FrPubStruct + // CHECK-COMMON-DAG: return [[FIELD]] : $FrPubStruct fooVar = arg } public func f() { @@ -222,25 +252,44 @@ public func runFrPub(_ arg: FrPubStruct) { } package struct PkgStruct { - // fooVar.getter + // PkgStruct.fooVar.getter + // CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV6fooVarSivg : $@convention(method) (@in_guaranteed PkgStruct) -> Int { // CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivg : $@convention(method) (PkgStruct) -> Int { - // fooVar.modify - // CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivM : $@yield_once @convention(method) (@inout PkgStruct) -> @yields @inout Int { - // fooVar.setter + // CHECK-RES-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PkgStruct, #PkgStruct.fooVar + // CHECK-RES-DAG: load [[FIELD]] : $*Int + // CHECK-NONRES-DAG = struct_extract %0 : $PkgStruct, #PkgStruct.fooVar + + // PkgStruct.fooVar.setter + // CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV6fooVarSivs : $@convention(method) (Int, @inout PkgStruct) -> () { // CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivs : $@convention(method) (Int, @inout PkgStruct) -> () { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PkgStruct + // CHECK-RES-DAG: store [[FIELD]] to {{.*}} : $*PkgStruct + // CHECK-NONRES-DAG: store [[FIELD]] to [trivial] {{.*}} : $*PkgStruct + + // PkgStruct.fooVar.modify + // CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV6fooVarSivM : $@yield_once @convention(method) (@inout PkgStruct) -> @yields @inout Int { + // CHECK-NONRES-DAG: sil package [transparent] [serialized] [canonical] [ossa] @$s3Lib9PkgStructV6fooVarSivM : $@yield_once @convention(method) (@inout PkgStruct) -> @yields @inout Int { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct_element_addr %0 : $*PkgStruct, #PkgStruct.fooVar + // CHECK-COMMON-DAG: yield [[FIELD]] package var fooVar: Int package init(_ arg: Int) { + // CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructVyACSicfC : $@convention(method) (Int, @thin PkgStruct.Type) -> @out PkgStruct { // CHECK-NONRES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructVyACSicfC : $@convention(method) (Int, @thin PkgStruct.Type) -> PkgStruct { + // CHECK-COMMON-DAG: [[FIELD:%.*]] = struct $PkgStruct + // CHECK-RES-DAG: store [[FIELD]] to %0 : $*PkgStruct + // CHECK-NONRES-DAG: return [[FIELD]] : $PkgStruct fooVar = arg } package func f() { + // CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV1fyyF : $@convention(method) (@in_guaranteed PkgStruct) -> () { // CHECK-NONRES-DAG: sil package [serialized] [canonical] @$s3Lib9PkgStructV1fyyF : $@convention(method) (PkgStruct) -> () { print(fooVar) } } package func runPkg(_ arg: PkgStruct) { + // CHECK-RES-DAG: sil package [serialized] [canonical] @$s3Lib6runPkgyyAA0C6StructVF : $@convention(thin) (@in_guaranteed PkgStruct) -> () { // CHECK-NONRES-DAG: sil package [serialized] [canonical] @$s3Lib6runPkgyyAA0C6StructVF : $@convention(thin) (PkgStruct) -> () { print(arg) }