From 564df842ec46bc36dd2a0c9d077965aa6d6e0bd5 Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Tue, 3 May 2022 09:19:03 -0700 Subject: [PATCH] [5.7] Pre-specialization fixes cherry-picks This is a cherry-pick of PR#58642 PR#58687 PR#58701 PR#58723 PR#58760 PR#58642 Eager specializer: Fix pre-specialization of imported code We must no pre-specialize imported code (except if this was explicitly called for by the importing module). Therefore, don't pre-specialize `shared` definitions based on their pre-specialization attributes. Rather, only pre-specialize if the pre-specialization is called for using a `target: "theFunctionToSpecialize"` parameter. Run OnonePrespecializations before serialization so that module native functions are not yet marked `shared` and can be identified as native. rdar://92337361 PR#58687 CrossModuleOptimization: Don't serialize pre-specialized public entry points We should continue to use the public pre-specialized entry point from another module. But not block other uses of generic specializations. PR#58701 SILFunctionBuilder: Don't create [serialized] function post serialization This might fix the randomly occuring errors of: ``` SIL verification failed: cannot have a serialized function after the module has been serialized: !F->isSerialized() || !mod.isSerialized() || mod.isParsedAsSerializedSIL() ``` PR#58723 GenericSpecialization: Move once initialization of pre-specializations earlier before the first query for pre-specialziations PR#58760 swiftinterface: print _specialize functions with targetFunction parameter in .swiftinterface If we have an internal function with a `_specialize` attribute that has a `targetFunction:` parameter we want the function to appear in the .swiftinterface file such that the exported specialization can be picked up by the compiler. --- lib/AST/ASTPrinter.cpp | 17 ++++++- lib/SIL/IR/SILFunctionBuilder.cpp | 4 ++ .../IPO/CrossModuleOptimization.cpp | 4 +- lib/SILOptimizer/PassManager/PassPipeline.cpp | 8 ++-- .../Transforms/EagerSpecializer.cpp | 8 ++++ .../Transforms/GenericSpecializer.cpp | 45 +++++++++++++++++++ lib/SILOptimizer/Utils/Generics.cpp | 45 ------------------- test/ModuleInterface/attrs.swift | 9 ++++ .../Inputs/cross-module/cross-module.swift | 1 + .../Inputs/cross-module/cross-submodule.swift | 6 +++ .../Inputs/prespecialize_import_module.swift | 7 +++ test/SILOptimizer/eager_specialize.sil | 18 ++++++++ test/SILOptimizer/prespecialize_import.swift | 11 +++++ test/Serialization/serialize_attr.swift | 4 +- test/sil-passpipeline-dump/basic.test-sh | 6 +-- 15 files changed, 137 insertions(+), 56 deletions(-) create mode 100644 test/SILOptimizer/Inputs/prespecialize_import_module.swift create mode 100644 test/SILOptimizer/prespecialize_import.swift diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 8c5b5b970bc47..ebad640374a14 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -107,6 +107,17 @@ static bool isPublicOrUsableFromInline(Type ty) { }); } +static bool isPrespecilizationDeclWithTarget(const ValueDecl *vd) { + // Add exported prespecialized symbols. + for (auto *attr : vd->getAttrs().getAttributes()) { + if (!attr->isExported()) + continue; + if (auto *targetFun = attr->getTargetFunctionDecl(vd)) + return true; + } + return false; +} + static bool contributesToParentTypeStorage(const AbstractStorageDecl *ASD) { auto *DC = ASD->getDeclContext()->getAsDecl(); if (!DC) return false; @@ -178,9 +189,11 @@ PrintOptions PrintOptions::printSwiftInterfaceFile(ModuleDecl *ModuleToPrint, if (!options.PrintSPIs && D->isSPI()) return false; - // Skip anything that isn't 'public' or '@usableFromInline'. + // Skip anything that isn't 'public' or '@usableFromInline' or has a + // _specialize attribute with a targetFunction parameter. if (auto *VD = dyn_cast(D)) { - if (!isPublicOrUsableFromInline(VD)) { + if (!isPublicOrUsableFromInline(VD) && + !isPrespecilizationDeclWithTarget(VD)) { // We do want to print private stored properties, without their // original names present. if (auto *ASD = dyn_cast(VD)) diff --git a/lib/SIL/IR/SILFunctionBuilder.cpp b/lib/SIL/IR/SILFunctionBuilder.cpp index b53ccec81e574..e9b5743a4453f 100644 --- a/lib/SIL/IR/SILFunctionBuilder.cpp +++ b/lib/SIL/IR/SILFunctionBuilder.cpp @@ -270,7 +270,11 @@ SILFunction *SILFunctionBuilder::getOrCreateFunction( IsTransparent_t IsTrans = constant.isTransparent() ? IsTransparent : IsNotTransparent; + IsSerialized_t IsSer = constant.isSerialized(); + // Don't create a [serialized] function after serialization has happened. + if (IsSer == IsSerialized && mod.isSerialized()) + IsSer = IsNotSerialized; Inline_t inlineStrategy = InlineDefault; if (constant.isNoinline()) diff --git a/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp b/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp index ddd1c3fb6160d..7f464554d1f9f 100644 --- a/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp +++ b/lib/SILOptimizer/IPO/CrossModuleOptimization.cpp @@ -207,7 +207,9 @@ bool CrossModuleOptimization::canSerializeFunction( // Do the same check for the specializations of such functions. if (function->isSpecialization()) { const SILFunction *parent = function->getSpecializationInfo()->getParent(); - if (!parent->getSpecializeAttrs().empty()) + // Don't serialize exported (public) specializations. + if (!parent->getSpecializeAttrs().empty() && + function->getLinkage() == SILLinkage::Public) return false; } diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index 344cb7a29ebdf..2b8dd6698c5d1 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -927,6 +927,11 @@ SILPassPipelinePlan::getOnonePassPipeline(const SILOptions &Options) { // be no need to run another analysis of copies at -Onone. P.addMandatoryARCOpts(); + // Create pre-specializations. + // This needs to run pre-serialization because it needs to identify native + // inlinable functions from imported ones. + P.addOnonePrespecializations(); + // First serialize the SIL if we are asked to. P.startPipeline("Serialization"); P.addSerializeSILPass(); @@ -946,9 +951,6 @@ SILPassPipelinePlan::getOnonePassPipeline(const SILOptions &Options) { P.addAssumeSingleThreaded(); } - // Create pre-specializations. - P.addOnonePrespecializations(); - // Has only an effect if the -sil-based-debuginfo option is specified. P.addSILDebugInfoGenerator(); diff --git a/lib/SILOptimizer/Transforms/EagerSpecializer.cpp b/lib/SILOptimizer/Transforms/EagerSpecializer.cpp index 1b6bb098efb68..9a2f2363841ab 100644 --- a/lib/SILOptimizer/Transforms/EagerSpecializer.cpp +++ b/lib/SILOptimizer/Transforms/EagerSpecializer.cpp @@ -853,6 +853,14 @@ void EagerSpecializerTransform::run() { assert(success); } onlyCreatePrespecializations = true; + } else if (targetFunc->getLinkage() == SILLinkage::Shared) { + // We have `shared` linkage if we deserialize a public serialized + // function. + // That means we are loading it from another module. In this case, we + // don't want to create a pre-specialization. + SpecializedFuncs.push_back(nullptr); + ReInfoVec.emplace_back(ReabstractionInfo()); + continue; } ReInfoVec.emplace_back(FuncBuilder.getModule().getSwiftModule(), FuncBuilder.getModule().isWholeModule(), targetFunc, diff --git a/lib/SILOptimizer/Transforms/GenericSpecializer.cpp b/lib/SILOptimizer/Transforms/GenericSpecializer.cpp index 9da1a9d064900..98e194cf734eb 100644 --- a/lib/SILOptimizer/Transforms/GenericSpecializer.cpp +++ b/lib/SILOptimizer/Transforms/GenericSpecializer.cpp @@ -33,6 +33,44 @@ using namespace swift; namespace { +static void transferSpecializeAttributeTargets(SILModule &M, + SILOptFunctionBuilder &builder, + Decl *d) { + auto *vd = cast(d); + for (auto *A : vd->getAttrs().getAttributes()) { + auto *SA = cast(A); + // Filter _spi. + auto spiGroups = SA->getSPIGroups(); + auto hasSPIGroup = !spiGroups.empty(); + if (hasSPIGroup) { + if (vd->getModuleContext() != M.getSwiftModule() && + !M.getSwiftModule()->isImportedAsSPI(SA, vd)) { + continue; + } + } + if (auto *targetFunctionDecl = SA->getTargetFunctionDecl(vd)) { + auto target = SILDeclRef(targetFunctionDecl); + auto targetSILFunction = builder.getOrCreateFunction( + SILLocation(vd), target, NotForDefinition, + [&builder](SILLocation loc, SILDeclRef constant) -> SILFunction * { + return builder.getOrCreateFunction(loc, constant, NotForDefinition); + }); + auto kind = SA->getSpecializationKind() == + SpecializeAttr::SpecializationKind::Full + ? SILSpecializeAttr::SpecializationKind::Full + : SILSpecializeAttr::SpecializationKind::Partial; + Identifier spiGroupIdent; + if (hasSPIGroup) { + spiGroupIdent = spiGroups[0]; + } + auto availability = AvailabilityInference::annotatedAvailableRangeForAttr( + SA, M.getSwiftModule()->getASTContext()); + targetSILFunction->addSpecializeAttr(SILSpecializeAttr::create( + M, SA->getSpecializedSignature(), SA->isExported(), kind, nullptr, + spiGroupIdent, vd->getModuleContext(), availability)); + } + } +} static bool specializeAppliesInFunction(SILFunction &F, SILTransform *transform, @@ -60,6 +98,13 @@ static bool specializeAppliesInFunction(SILFunction &F, auto *Callee = Apply.getReferencedFunctionOrNull(); if (!Callee) continue; + + FunctionBuilder.getModule().performOnceForPrespecializedImportedExtensions( + [&FunctionBuilder](AbstractFunctionDecl *pre) { + transferSpecializeAttributeTargets(FunctionBuilder.getModule(), FunctionBuilder, + pre); + }); + if (!Callee->isDefinition() && !Callee->hasPrespecialization()) { ORE.emit([&]() { using namespace OptRemark; diff --git a/lib/SILOptimizer/Utils/Generics.cpp b/lib/SILOptimizer/Utils/Generics.cpp index ecd63f31cd909..12a384c5c0647 100644 --- a/lib/SILOptimizer/Utils/Generics.cpp +++ b/lib/SILOptimizer/Utils/Generics.cpp @@ -2552,45 +2552,6 @@ usePrespecialized(SILOptFunctionBuilder &funcBuilder, ApplySite apply, return nullptr; } -static void transferSpecializeAttributeTargets(SILModule &M, - SILOptFunctionBuilder &builder, - Decl *d) { - auto *vd = cast(d); - for (auto *A : vd->getAttrs().getAttributes()) { - auto *SA = cast(A); - // Filter _spi. - auto spiGroups = SA->getSPIGroups(); - auto hasSPIGroup = !spiGroups.empty(); - if (hasSPIGroup) { - if (vd->getModuleContext() != M.getSwiftModule() && - !M.getSwiftModule()->isImportedAsSPI(SA, vd)) { - continue; - } - } - if (auto *targetFunctionDecl = SA->getTargetFunctionDecl(vd)) { - auto target = SILDeclRef(targetFunctionDecl); - auto targetSILFunction = builder.getOrCreateFunction( - SILLocation(vd), target, NotForDefinition, - [&builder](SILLocation loc, SILDeclRef constant) -> SILFunction * { - return builder.getOrCreateFunction(loc, constant, NotForDefinition); - }); - auto kind = SA->getSpecializationKind() == - SpecializeAttr::SpecializationKind::Full - ? SILSpecializeAttr::SpecializationKind::Full - : SILSpecializeAttr::SpecializationKind::Partial; - Identifier spiGroupIdent; - if (hasSPIGroup) { - spiGroupIdent = spiGroups[0]; - } - auto availability = AvailabilityInference::annotatedAvailableRangeForAttr( - SA, M.getSwiftModule()->getASTContext()); - targetSILFunction->addSpecializeAttr(SILSpecializeAttr::create( - M, SA->getSpecializedSignature(), SA->isExported(), kind, nullptr, - spiGroupIdent, vd->getModuleContext(), availability)); - } - } -} - void swift::trySpecializeApplyOfGeneric( SILOptFunctionBuilder &FuncBuilder, ApplySite Apply, DeadInstructionSet &DeadApplies, @@ -2646,12 +2607,6 @@ void swift::trySpecializeApplyOfGeneric( SILFunction *prespecializedF = nullptr; ReabstractionInfo prespecializedReInfo; - FuncBuilder.getModule().performOnceForPrespecializedImportedExtensions( - [&FuncBuilder](AbstractFunctionDecl *pre) { - transferSpecializeAttributeTargets(FuncBuilder.getModule(), FuncBuilder, - pre); - }); - if ((prespecializedF = usePrespecialized(FuncBuilder, Apply, RefF, ReInfo, prespecializedReInfo))) { ReInfo = prespecializedReInfo; diff --git a/test/ModuleInterface/attrs.swift b/test/ModuleInterface/attrs.swift index 09efd228004fa..87845d3120d44 100644 --- a/test/ModuleInterface/attrs.swift +++ b/test/ModuleInterface/attrs.swift @@ -17,3 +17,12 @@ // CHECK-NEXT: public var y: Swift.Int public var y: Int } // CHECK-NEXT: {{^}$}} + +public func someGenericFunction(_ t: T) -> Int { return 0 } + +// CHECK: @_specialize(exported: true, kind: full, target: someGenericFunction(_:), where T == Swift.Int) +// CHECK: internal func __specialize_someGenericFunction(_ t: T) +@_specialize(exported: true, target: someGenericFunction(_:), where T == Int) +internal func __specialize_someGenericFunction(_ t: T) -> Int { + fatalError("don't call") +} diff --git a/test/SILOptimizer/Inputs/cross-module/cross-module.swift b/test/SILOptimizer/Inputs/cross-module/cross-module.swift index 522859fb06fea..69107cc1ae106 100644 --- a/test/SILOptimizer/Inputs/cross-module/cross-module.swift +++ b/test/SILOptimizer/Inputs/cross-module/cross-module.swift @@ -17,6 +17,7 @@ public struct Container { var arr = Array() arr.append(Base()) print(arr) + dontBlockSerialization(arr) return t } diff --git a/test/SILOptimizer/Inputs/cross-module/cross-submodule.swift b/test/SILOptimizer/Inputs/cross-module/cross-submodule.swift index 2df3e5f66057f..03f938d9ac96d 100644 --- a/test/SILOptimizer/Inputs/cross-module/cross-submodule.swift +++ b/test/SILOptimizer/Inputs/cross-module/cross-submodule.swift @@ -10,3 +10,9 @@ public func genericSubmoduleFunc(_ t: T) { printit(t) } +@_specialize(exported: true, where T == Int) +@inlinable +@inline(never) +public func dontBlockSerialization(_ t: T) { + print(t) +} diff --git a/test/SILOptimizer/Inputs/prespecialize_import_module.swift b/test/SILOptimizer/Inputs/prespecialize_import_module.swift new file mode 100644 index 0000000000000..0c97f81c56152 --- /dev/null +++ b/test/SILOptimizer/Inputs/prespecialize_import_module.swift @@ -0,0 +1,7 @@ +public func someFunc(_ t: T) { + print(t) +} + +@_specialize(exported: true, target: someFunc(_:), where T == Int) +@usableFromInline +func __specialize_someFunc(_: T) {} diff --git a/test/SILOptimizer/eager_specialize.sil b/test/SILOptimizer/eager_specialize.sil index fd80533ec561f..a0f387f74565a 100644 --- a/test/SILOptimizer/eager_specialize.sil +++ b/test/SILOptimizer/eager_specialize.sil @@ -848,6 +848,24 @@ bb0(%0 : $*T): return %8 : $Builtin.Int64 } +// Don't specialize `shared` definitions they are imported from another module. +// CHECK-NOT: sil @$s24testDontSpecializeSharedSi_Ts5 +sil shared [_specialize exported: true, kind: full, where T == Int] @testDontSpecializeShared : $@convention(thin) (@in T) -> () { +bb(%0: $*T): + destroy_addr %0 : $*T + %t = tuple() + return %t : $() +} + +// But do specialize `shared` definitions when they are target from another // function. +// CHECK: sil @$s24testDontSpecializeSharedSd_Ts5 +sil shared [_specialize exported: true, kind: full, target: "testDontSpecializeShared" ,where T == Double] @butSpecializeWhenTargetIsPresent : $@convention(thin) (@in T) -> () { +bb(%0: $*T): + destroy_addr %0 : $*T + %t = tuple() + return %t : $() +} + sil_vtable ClassUsingThrowingP { #ClassUsingThrowingP.init!allocator: (ClassUsingThrowingP.Type) -> () -> ClassUsingThrowingP : @$s34eager_specialize_throwing_function19ClassUsingThrowingPCACycfC // ClassUsingThrowingP.__allocating_init() #ClassUsingThrowingP.init!initializer: (ClassUsingThrowingP.Type) -> () -> ClassUsingThrowingP : @$s34eager_specialize_throwing_function19ClassUsingThrowingPCACycfc // ClassUsingThrowingP.init() diff --git a/test/SILOptimizer/prespecialize_import.swift b/test/SILOptimizer/prespecialize_import.swift new file mode 100644 index 0000000000000..3b5e43f05a800 --- /dev/null +++ b/test/SILOptimizer/prespecialize_import.swift @@ -0,0 +1,11 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -module-name A -emit-module-path %t/A.swiftmodule %S/Inputs/prespecialize_import_module.swift +// RUN: %target-swift-frontend -O -emit-sil -module-name B -I %t %s | %FileCheck %s +import A + +// CHECK-LABEL: sil{{.*}} @$s1B4testyyF +public func test() { + // CHECK: s1A8someFuncyyxlFSi_Ts5 + someFunc(5) +} +// CHECK: end sil function '$s1B4testyyF' diff --git a/test/Serialization/serialize_attr.swift b/test/Serialization/serialize_attr.swift index ea7c44e0b4547..5bd1c0fe07b47 100644 --- a/test/Serialization/serialize_attr.swift +++ b/test/Serialization/serialize_attr.swift @@ -79,6 +79,6 @@ public class CC { } } -// CHECK-DAG: sil [serialized] [_specialize exported: false, kind: full, where T == Int, U == Float] [canonical] [ossa] @$s14serialize_attr14specializeThis_1uyx_q_tr0_lF : $@convention(thin) (@in_guaranteed T, @in_guaranteed U) -> () { +// CHECK-DAG: sil [serialized] [canonical] [ossa] @$s14serialize_attr14specializeThis_1uyx_q_tr0_lF : $@convention(thin) (@in_guaranteed T, @in_guaranteed U) -> () { -// CHECK-DAG: sil [serialized] [noinline] [_specialize exported: false, kind: full, where T == RR, U == SS] [canonical] [ossa] @$s14serialize_attr2CCC3foo_1gqd___AA2GGVyxGtqd___AHtAA2QQRd__lF : $@convention(method) (@in_guaranteed U, GG, @guaranteed CC) -> (@out U, GG) { +// CHECK-DAG: sil [serialized] [noinline] [canonical] [ossa] @$s14serialize_attr2CCC3foo_1gqd___AA2GGVyxGtqd___AHtAA2QQRd__lF : $@convention(method) (@in_guaranteed U, GG, @guaranteed CC) -> (@out U, GG) { diff --git a/test/sil-passpipeline-dump/basic.test-sh b/test/sil-passpipeline-dump/basic.test-sh index 0f69b08bc8047..f101be0abd146 100644 --- a/test/sil-passpipeline-dump/basic.test-sh +++ b/test/sil-passpipeline-dump/basic.test-sh @@ -2,13 +2,13 @@ // CHECK: --- // CHECK: name: Non-Diagnostic Mandatory Optimizations -// CHECK: passes: [ "for-each-loop-unroll", "mandatory-combine", -// CHECK: "mandatory-arc-opts" ] +// CHECK: passes: [ "for-each-loop-unroll", "mandatory-combine", "mandatory-arc-opts", +// CHECK: "onone-prespecializer" ] // CHECK: --- // CHECK: name: Serialization // CHECK: passes: [ "serialize-sil", "sil-onone-debuginfo-canonicalizer", // CHECK-NEXT: "ownership-model-eliminator" ] // CHECK: --- // CHECK: name: Rest of Onone -// CHECK: passes: [ "use-prespecialized", "onone-prespecializer", "sil-debuginfo-gen" ] +// CHECK: passes: [ "use-prespecialized", "sil-debuginfo-gen" ] // CHECK: ...