[llvm] 9779b66 - [docs][NewPM] Add section on analyses

Arthur Eubanks via llvm-commits llvm-commits at lists.llvm.org
Mon May 3 10:15:27 PDT 2021


Author: Arthur Eubanks
Date: 2021-05-03T10:15:02-07:00
New Revision: 9779b664b6a8d8a047d52061b23a4a3737446e45

URL: https://github.com/llvm/llvm-project/commit/9779b664b6a8d8a047d52061b23a4a3737446e45
DIFF: https://github.com/llvm/llvm-project/commit/9779b664b6a8d8a047d52061b23a4a3737446e45.diff

LOG: [docs][NewPM] Add section on analyses

Reviewed By: asbirlea, ychen

Differential Revision: https://reviews.llvm.org/D100912

Added: 
    

Modified: 
    llvm/docs/NewPassManager.rst

Removed: 
    


################################################################################
diff  --git a/llvm/docs/NewPassManager.rst b/llvm/docs/NewPassManager.rst
index e9a5267a5560..fb085f142fbf 100644
--- a/llvm/docs/NewPassManager.rst
+++ b/llvm/docs/NewPassManager.rst
@@ -140,6 +140,216 @@ sanitizer) passes to various parts of the pipeline.
 ``AMDGPUTargetMachine::registerPassBuilderCallbacks()`` is an example of a
 backend adding passes to various parts of the pipeline.
 
+Using Analyses
+==============
+
+LLVM provides many analyses that passes can use, such as a dominator tree.
+Calculating these can be expensive, so the new pass manager has
+infrastructure to cache analyses and reuse them when possible.
+
+When a pass runs on some IR, it also receives an analysis manager which it
+can query for analyses. Querying for an analysis will cause the manager to
+check if it has already computed the result for the requested IR. If it does
+and the result is still valid, it will return that. Otherwise it will
+construct a new result by calling the analysis's ``run()`` method, cache it,
+and return it. You can also ask the analysis manager to only return an
+analysis if it's already cached.
+
+The analysis manager only provides analysis results for the same IR type as
+what the pass runs on. For example, a function pass receives an analysis
+manager that only provides function-level analyses. This works for many
+passes which work on a fixed scope. However, some passes want to peek up or
+down the IR hierarchy. For example, an SCC pass may want to look at function
+analyses for the functions inside the SCC. Or it may want to look at some
+immutable global analysis. In these cases, the analysis manager can provide a
+proxy to an outer or inner level analysis manager. For example, to get a
+``FunctionAnalysisManager`` from a ``CGSCCAnalysisManager``, you can call
+
+.. code-block:: c++
+
+  FunctionAnalysisManager &FAM =
+      AM.getResult<FunctionAnalysisManagerCGSCCProxy>(InitialC, CG)
+          .getManager();
+
+and use ``FAM`` as a typical ``FunctionAnalysisManager`` that a function pass
+would have access to. To get access to an outer level IR analysis, you can
+call
+
+.. code-block:: c++
+
+  const auto &MAMProxy =
+      AM.getResult<ModuleAnalysisManagerCGSCCProxy>(InitialC, CG);
+  FooAnalysisResult *AR = MAMProxy.getCachedResult<FooAnalysis>(M);
+
+Getting direct access to an outer level IR analysis manager is not allowed.
+This is to keep in mind potential future pass concurrency, for example
+parallelizing function passes over 
diff erent functions in a CGSCC or module.
+Since passes can ask for a cached analysis result, allowing passes to trigger
+outer level analysis computation could result in non-determinism if
+concurrency was supported. Therefore a pass running on inner level IR cannot
+change the state of outer level IR analyses. Another limitation is that outer
+level IR analyses that are used must be immutable, or else they could be
+invalidated by changes to inner level IR. Outer analyses unused by inner
+passes can and often will be invalidated by changes to inner level IR. These
+invalidations happen after the inner pass manager finishes, so accessing
+mutable analyses would give invalid results.
+
+The exception to the above is accessing function analyses in loop passes.
+Loop passes inherently require modifying the function the loop is in, and
+that includes some function analyses the loop analyses depend on. This
+discounts future concurrency over separate loops in a function, but that's a
+tradeoff due to how tightly a loop and its function are coupled. To make sure
+the function analyses loop passes use are valid, they are manually updated in
+the loop passes to ensure that invalidation is not necessary. There is a set
+of common function analyses that loop passes and analyses have access to
+which is passed into loop passes as a ``LoopStandardAnalysisResults``
+parameter. Other function analyses are not accessible from loop passes.
+
+As with any caching mechanism, we need some way to tell analysis managers
+when results are no longer valid. Much of the analysis manager complexity
+comes from trying to invalidate as few analysis results as possible to keep
+compile times as low as possible.
+
+There are two ways to deal with potentially invalid analysis results. One is
+to simply force clear the results. This should generally only be used when
+the IR that the result is keyed on becomes invalid. For example, a function
+is deleted, or a CGSCC has become invalid due to call graph changes.
+
+The typical way to invalidate analysis results is for a pass to declare what
+types of analyses it preserves and what types it does not. When transforming
+IR, a pass either has the option to update analyses alongside the IR
+transformation, or tell the analysis manager that analyses are no longer
+valid and should be invalidated. If a pass wants to keep some specific
+analysis up to date, such as when updating it would be faster than
+invalidating and recalculating it, the analysis itself may have methods to
+update it for specific transformations, or there may be helper updaters like
+``DomTreeUpdater`` for a ``DominatorTree``. Otherwise to mark some analysis
+as no longer valid, the pass can return a ``PreservedAnalyses`` with the
+proper analyses invalidated.
+
+.. code-block:: c++
+
+  // We've made no transformations that can affect any analyses.
+  return PreservedAnalyses::all();
+
+  // We've made transformations and don't want to bother to update any analyses.
+  return PreservedAnalyses::none();
+
+  // We've specifically updated the dominator tree alongside any transformations, but other analysis results may be invalid.
+  PreservedAnalyses PA;
+  PA.preserve<DominatorAnalysis>();
+  return PA;
+
+  // We haven't made any control flow changes, any analyses that only care about the control flow are still valid.
+  PreservedAnalyses PA;
+  PA.preserveSet<CFGAnalyses>();
+  return PA;
+  
+The pass manager will call the analysis manager's ``invalidate()`` method
+with the pass's returned ``PreservedAnalyses``. This can be also done
+manually within the pass:
+
+.. code-block:: c++
+
+  FooModulePass::run(Module& M, ModuleAnalysisManager& AM) {
+    auto &FAM = AM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
+
+    // Invalidate all analysis results for function F
+    FAM.invalidate(F, PreservedAnalyses::none());
+
+    // Invalidate all analysis results
+    AM.invalidate(M, PreservedAnalyses::none());
+
+    ...
+  }
+
+This is especially important when a pass removes then adds a function. The
+analysis manager may store a pointer to a function that has been deleted, and
+if the pass creates a new function before invalidating analysis results, the
+new function may be at the same address as the old one, causing invalid
+cached results. This is also useful for being more precise about
+invalidation. Selectively invalidating analysis results only for functions
+modified in an SCC pass can allow more analysis results to remain. But except
+for complex fine-grain invalidation with inner proxies, passes should
+typically just return a proper ``PreservedAnalyses`` and let the pass manager
+deal with proper invalidation.
+
+Implementing Analysis Invalidation
+==================================
+
+By default, an analysis is invalidated if ``PreservedAnalyses`` says that
+analyses on the IR unit it runs on are not preserved (see
+``AnalysisResultModel::invalidate()``). An analysis can implement
+``invalidate()`` to be more conservative when it comes to invalidation. For
+example,
+
+.. code-block:: c++
+
+  bool FooAnalysisResult::invalidate(Function &F, const PreservedAnalyses &PA,
+                                     FunctionAnalysisManager::Invalidator &) {
+    auto PAC = PA.getChecker<FooAnalysis>();
+    // the default would be:
+    // return !(PAC.preserved() || PAC.preservedSet<AllAnalysesOn<Function>>());
+    return !(PAC.preserved() || PAC.preservedSet<AllAnalysesOn<Function>>()
+        || PAC.preservedSet<CFGAnalyses>());
+  }
+
+says that if the ``PreservedAnalyses`` specifically preserves
+``FooAnalysis``, or if ``PreservedAnalyses`` preserves all analyses (implicit
+in ``PAC.preserved()``), or if ``PreservedAnalyses`` preserves all function
+analyses, or ``PreservedAnalyses`` preserves all analyses that only care
+about the CFG, the ``FooAnalysisResult`` should not be invalidated.
+
+If an analysis is stateless and generally shouldn't be invalidated, use the
+following:
+
+.. code-block:: c++
+
+  bool FooAnalysisResult::invalidate(Function &F, const PreservedAnalyses &PA,
+                                     FunctionAnalysisManager::Invalidator &) {
+    // Check whether the analysis has been explicitly invalidated. Otherwise, it's
+    // stateless and remains preserved.
+    auto PAC = PA.getChecker<FooAnalysis>();
+    return !PAC.preservedWhenStateless();
+  }
+
+If an analysis depends on other analyses, those analyses also need to be
+checked if they are invalidated:
+
+.. code-block:: c++
+
+  bool FooAnalysisResult::invalidate(Function &F, const PreservedAnalyses &PA,
+                                     FunctionAnalysisManager::Invalidator &) {
+    auto PAC = PA.getChecker<FooAnalysis>();
+    if (!PAC.preserved() && !PAC.preservedSet<AllAnalysesOn<Function>>())
+      return true;
+
+    // Check transitive dependencies.
+    return Inv.invalidate<BarAnalysis>(F, PA) ||
+          Inv.invalidate<BazAnalysis>(F, PA);
+  }
+
+Combining invalidation and analysis manager proxies results in some
+complexity. For example, when we invalidate all analyses in a module pass,
+we have to make sure that we also invalidate function analyses accessible via
+any existing inner proxies. The inner proxy's ``invalidate()`` first checks
+if the proxy itself should be invalidated. If so, that means the proxy may
+contain pointers to IR that is no longer valid, meaning that the inner proxy
+needs to completely clear all relevant analysis results. Otherwise the proxy
+simply forwards the invalidation to the inner analysis manager.
+
+Generally for outer proxies, analysis results from the outer analysis manager
+should be immutable, so invalidation shouldn't be a concern. However, it is
+possible for some inner analysis to depend on some outer analysis, and when
+the outer analysis is invalidated, we need to make sure that dependent inner
+analyses are also invalidated. This actually happens with alias analysis
+results. Alias analysis is a function-level analysis, but there are
+module-level implementations of specific types of alias analysis. Currently
+``GlobalsAA`` is the only module-level alias analysis and it generally is not
+invalidated so this is not so much of a concern. See
+``OuterAnalysisManagerProxy::Result::registerOuterAnalysisInvalidation()``
+for more details.
+
 Status of the New and Legacy Pass Managers
 ==========================================
 


        


More information about the llvm-commits mailing list