From 221ebd23e415e88ce855d1b072d8a43514fe7cfd Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Fri, 20 Mar 2020 15:13:44 -0700 Subject: [PATCH 1/3] Fix the mid-level pass pipeline. Module passes need to be in a separate pipeline, otherwise the pipeline restart mechanism will be broken. This makes GlobalOpt and serialization run earlier in the pipeline. There's no explicit reason for them to be run later, in the middle of a function pass pipeline. Also, pipeline boundaries, like serialization and module passes should be explicit at the the top level function that creates the pass pipelines. --- lib/SILOptimizer/PassManager/PassPipeline.cpp | 117 ++++++++++++------ 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index f2d717f4f4f72..00cb239e0e980 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -264,8 +264,12 @@ void addHighLevelLoopOptPasses(SILPassPipelinePlan &P) { P.addSwiftArrayPropertyOpt(); } -// Perform classic SSA optimizations. -void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) { +// Primary FunctionPass pipeline. +// +// Inserting a module passes within this pipeline would break the pipeline +// restart functionality. +void addFunctionPasses(SILPassPipelinePlan &P, + OptimizationLevelKind OpLevel) { // Promote box allocations to stack allocations. P.addAllocBoxToStack(); @@ -289,11 +293,25 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) { // Cleanup, which is important if the inliner has restarted the pass pipeline. P.addPerformanceConstantPropagation(); - P.addSimplifyCFG(); - P.addSILCombine(); + addSimplifyCFGSILCombinePasses(P); - // Mainly for Array.append(contentsOf) optimization. - P.addArrayElementPropagation(); + // Perform a round of loop/array optimization in the mid-level pipeline after + // potentially inlining semantic calls, e.g. Array append. The high level + // pipeline only optimizes semantic calls *after* inlining (see + // addHighLevelLoopOptPasses). For example, the high-level pipeline may + // perform ArrayElementPropagation and after inlining a level of semantic + // calls, the mid-level pipeline may handle uniqueness hoisting. Do this as + // late as possible before inlining because it must run between runs of the + // inliner when the pipeline restarts. + if (OpLevel == OptimizationLevelKind::MidLevel) { + P.addHighLevelLICM(); + P.addArrayCountPropagation(); + P.addABCOpt(); + P.addDCE(); + P.addCOWArrayOpts(); + P.addDCE(); + P.addSwiftArrayPropertyOpt(); + } // Run the devirtualizer, specializer, and inliner. If any of these // makes a change we'll end up restarting the function passes on the @@ -310,22 +328,6 @@ void addSSAPasses(SILPassPipelinePlan &P, OptimizationLevelKind OpLevel) { P.addEarlyInliner(); break; case OptimizationLevelKind::MidLevel: - P.addGlobalOpt(); - P.addLetPropertiesOpt(); - // It is important to serialize before any of the @_semantics - // functions are inlined, because otherwise the information about - // uses of such functions inside the module is lost, - // which reduces the ability of the compiler to optimize clients - // importing this module. - P.addSerializeSILPass(); - - // Now strip any transparent functions that still have ownership. - if (P.getOptions().StripOwnershipAfterSerialization) - P.addOwnershipModelEliminator(); - - if (P.getOptions().StopOptimizationAfterSerialization) - return; - // Does inline semantics-functions (except "availability"), but not // global-init functions. P.addPerfInliner(); @@ -432,30 +434,58 @@ static void addPerfEarlyModulePassPipeline(SILPassPipelinePlan &P) { P.addCMOSerializeSILPass(); } -static void addHighLevelEarlyLoopOptPipeline(SILPassPipelinePlan &P) { - P.startPipeline("HighLevel+EarlyLoopOpt"); - // FIXME: update this to be a function pass. +// The "high-level" pipeline serves two purposes: +// +// 1. Optimize the standard library Swift module prior to serialization. This +// reduces the amount of work during compilation of all non-stdlib clients. +// +// 2. Optimize caller functions before inlining semantic calls inside +// callees. This provides more precise escape analysis and side effect analysis +// of callee arguments. +static void addHighLevelFunctionPipeline(SILPassPipelinePlan &P) { + P.startPipeline("HighLevel,Function+EarlyLoopOpt"); + // FIXME: update EagerSpecializer to be a function pass! P.addEagerSpecializer(); - addSSAPasses(P, OptimizationLevelKind::HighLevel); + addFunctionPasses(P, OptimizationLevelKind::HighLevel); + addHighLevelLoopOptPasses(P); } -static void addMidModulePassesStackPromotePassPipeline(SILPassPipelinePlan &P) { - P.startPipeline("MidModulePasses+StackPromote"); +// After "high-level" function passes have processed the entire call tree, run +// one round of module passes. +static void addHighLevelModulePipeline(SILPassPipelinePlan &P) { + P.startPipeline("HighLevel,Module+StackPromote"); P.addDeadFunctionElimination(); P.addPerformanceSILLinker(); P.addDeadObjectElimination(); P.addGlobalPropertyOpt(); - // Do the first stack promotion on high-level SIL. + // Do the first stack promotion on high-level SIL before serialization. + // + // FIXME: why does StackPromotion need to run in the module pipeline? P.addStackPromotion(); + + P.addGlobalOpt(); + P.addLetPropertiesOpt(); } -static bool addMidLevelPassPipeline(SILPassPipelinePlan &P) { +static void addSerializePipeline(SILPassPipelinePlan &P) { + P.startPipeline("Serialize"); + // It is important to serialize before any of the @_semantics + // functions are inlined, because otherwise the information about + // uses of such functions inside the module is lost, + // which reduces the ability of the compiler to optimize clients + // importing this module. + P.addSerializeSILPass(); + + // Strip any transparent functions that still have ownership. + if (P.getOptions().StripOwnershipAfterSerialization) + P.addOwnershipModelEliminator(); +} + +static void addMidLevelFunctionPipeline(SILPassPipelinePlan &P) { P.startPipeline("MidLevel"); - addSSAPasses(P, OptimizationLevelKind::MidLevel); - if (P.getOptions().StopOptimizationAfterSerialization) - return true; + addFunctionPasses(P, OptimizationLevelKind::MidLevel); // Specialize partially applied functions with dead arguments as a preparation // for CapturePropagation. @@ -467,7 +497,6 @@ static bool addMidLevelPassPipeline(SILPassPipelinePlan &P) { // Run loop unrolling after inlining and constant propagation, because loop // trip counts may have became constant. P.addLoopUnroll(); - return false; } static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) { @@ -523,7 +552,7 @@ static void addLowLevelPassPipeline(SILPassPipelinePlan &P) { // Should be after FunctionSignatureOpts and before the last inliner. P.addReleaseDevirtualizer(); - addSSAPasses(P, OptimizationLevelKind::LowLevel); + addFunctionPasses(P, OptimizationLevelKind::LowLevel); P.addDeadObjectElimination(); P.addObjectOutliner(); @@ -651,13 +680,21 @@ SILPassPipelinePlan::getPerformancePassPipeline(const SILOptions &Options) { addPerfEarlyModulePassPipeline(P); // Then run an iteration of the high-level SSA passes. - addHighLevelEarlyLoopOptPipeline(P); - addMidModulePassesStackPromotePassPipeline(P); + // + // FIXME: When *not* emitting a .swiftmodule, skip the high-level function + // pipeline to save compile time. + addHighLevelFunctionPipeline(P); - // Run an iteration of the mid-level SSA passes. - if (addMidLevelPassPipeline(P)) + addHighLevelModulePipeline(P); + + addSerializePipeline(P); + if (Options.StopOptimizationAfterSerialization) return P; + // After serialization run the function pass pipeline to iteratively lower + // high-level constructs like @_semantics calls. + addMidLevelFunctionPipeline(P); + // Perform optimizations that specialize. addClosureSpecializePassPipeline(P); @@ -697,7 +734,7 @@ SILPassPipelinePlan::getOnonePassPipeline(const SILOptions &Options) { P.startPipeline("Serialization"); P.addSerializeSILPass(); - // And then strip ownership... + // Now strip any transparent functions that still have ownership. if (Options.StripOwnershipAfterSerialization) P.addOwnershipModelEliminator(); From bf0814d2ac1d6b21f22b5e6fdcf6c4158ccb10ab Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 30 Mar 2020 14:18:01 -0700 Subject: [PATCH 2/3] SILOptimizer: Add enforcement of function-pass pipelines. Don't allow module passes to be inserted within a function pass pipeline. This silently breaks the function pipeline both interfering with analysis and the normal pipeline restart mechanism. --- .../swift/SILOptimizer/PassManager/PassManager.h | 11 +---------- .../swift/SILOptimizer/PassManager/PassPipeline.h | 9 ++++++--- lib/SILOptimizer/PassManager/PassManager.cpp | 13 +++++++++++++ lib/SILOptimizer/PassManager/PassPipeline.cpp | 4 ++-- 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/include/swift/SILOptimizer/PassManager/PassManager.h b/include/swift/SILOptimizer/PassManager/PassManager.h index 0d4470c8a2f6d..15802da54a6bb 100644 --- a/include/swift/SILOptimizer/PassManager/PassManager.h +++ b/include/swift/SILOptimizer/PassManager/PassManager.h @@ -254,16 +254,7 @@ class SILPassManager { } } - void executePassPipelinePlan(const SILPassPipelinePlan &Plan) { - for (const SILPassPipeline &Pipeline : Plan.getPipelines()) { - setStageName(Pipeline.Name); - resetAndRemoveTransformations(); - for (PassKind Kind : Plan.getPipelinePasses(Pipeline)) { - addPass(Kind); - } - execute(); - } - } + void executePassPipelinePlan(const SILPassPipelinePlan &Plan); private: void execute(); diff --git a/include/swift/SILOptimizer/PassManager/PassPipeline.h b/include/swift/SILOptimizer/PassManager/PassPipeline.h index ee82883fa9053..7df29dc559183 100644 --- a/include/swift/SILOptimizer/PassManager/PassPipeline.h +++ b/include/swift/SILOptimizer/PassManager/PassPipeline.h @@ -88,7 +88,7 @@ class SILPassPipelinePlan final { void print(llvm::raw_ostream &os); - void startPipeline(StringRef Name = ""); + void startPipeline(StringRef Name = "", bool isFunctionPassPipeline = false); using PipelineKindIterator = decltype(Kinds)::const_iterator; using PipelineKindRange = iterator_range; iterator_range @@ -128,6 +128,7 @@ struct SILPassPipeline final { unsigned ID; StringRef Name; unsigned KindOffset; + bool isFunctionPassPipeline; friend bool operator==(const SILPassPipeline &lhs, const SILPassPipeline &rhs) { @@ -145,9 +146,11 @@ struct SILPassPipeline final { } }; -inline void SILPassPipelinePlan::startPipeline(StringRef Name) { +inline void SILPassPipelinePlan:: +startPipeline(StringRef Name, bool isFunctionPassPipeline) { PipelineStages.push_back(SILPassPipeline{ - unsigned(PipelineStages.size()), Name, unsigned(Kinds.size())}); + unsigned(PipelineStages.size()), Name, unsigned(Kinds.size()), + isFunctionPassPipeline}); } inline SILPassPipelinePlan::PipelineKindRange diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index 3190e63503527..e855e5e031f3a 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -603,6 +603,19 @@ void SILPassManager::runModulePass(unsigned TransIdx) { } } +void SILPassManager::executePassPipelinePlan(const SILPassPipelinePlan &Plan) { + for (const SILPassPipeline &Pipeline : Plan.getPipelines()) { + setStageName(Pipeline.Name); + resetAndRemoveTransformations(); + for (PassKind Kind : Plan.getPipelinePasses(Pipeline)) { + addPass(Kind); + assert(!Pipeline.isFunctionPassPipeline + || isa(Transformations.back())); + } + execute(); + } +} + void SILPassManager::execute() { const SILOptions &Options = getOptions(); diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index 00cb239e0e980..f3c4033c99e33 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -484,7 +484,7 @@ static void addSerializePipeline(SILPassPipelinePlan &P) { } static void addMidLevelFunctionPipeline(SILPassPipelinePlan &P) { - P.startPipeline("MidLevel"); + P.startPipeline("MidLevel,Function", true /*isFunctionPassPipeline*/); addFunctionPasses(P, OptimizationLevelKind::MidLevel); // Specialize partially applied functions with dead arguments as a preparation @@ -547,7 +547,7 @@ static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) { } static void addLowLevelPassPipeline(SILPassPipelinePlan &P) { - P.startPipeline("LowLevel"); + P.startPipeline("LowLevel,Function", true /*isFunctionPassPipeline*/); // Should be after FunctionSignatureOpts and before the last inliner. P.addReleaseDevirtualizer(); From ebf6411c2ae33bd35b991151de62cce59504da82 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Fri, 21 Feb 2020 20:57:26 -0800 Subject: [PATCH 3/3] WIP: Fix SimplifyCFG analysis invalidation. Not all simplifications correctly invalidate CFG analyses. --- lib/SILOptimizer/Transforms/SimplifyCFG.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp index b13249ab7361d..154f933fd1f1a 100644 --- a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp +++ b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp @@ -3065,6 +3065,10 @@ bool SimplifyCFG::run() { LLVM_DEBUG(llvm::dbgs() << "### Run SimplifyCFG on " << Fn.getName() << '\n'); + //!!! + if (Fn.hasName("$s32sil_combine_concrete_existential29testWitnessReturnOptionalSelfAA2PP_pSgyF")) { + llvm::dbgs() << "TEST WITNESS SIMPLIFYCFG\n"; + } // Disable some expensive optimizations if the function is huge. isVeryLargeFunction = (Fn.size() > 10000);