[flang-commits] [flang] ac78ce3 - [flang][AliasAnalysis] Add opt-in getSource memoization cache (#208319)

via flang-commits flang-commits at lists.llvm.org
Thu Jul 9 18:14:24 PDT 2026


Author: Slava Zakharin
Date: 2026-07-09T18:14:20-07:00
New Revision: ac78ce369272ea46ceb8b00f762c496ecd40cb19

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

LOG: [flang][AliasAnalysis] Add opt-in getSource memoization cache (#208319)

Add an opt-in cache to fir::AliasAnalysis that memoizes getSource()
results keyed on (value, flags). Caching is off by default and turned on
via enableSourceCache(); getSource() becomes a thin wrapper over the
uncached getSourceImpl(). The cache is a frozen snapshot with no
automatic invalidation and lives no longer than the AliasAnalysis
instance, so a client enables it only for a region in which it does not
mutate IR in a way that would change a source.

Flang's LICM enables the cache on its fir::AliasAnalysis before adding
it to the mlir::AliasAnalysis aggregate: LICM only moves operations, so
getSource()'s inputs are unchanged across the hoists, and the pass
manager drops the analysis (and its cache) after the pass.

Add a unit test covering the default-off behavior, cache fill/hit, and
that disableSourceCache() clears and bypasses the cache.

Assisted-by: Cursor

Added: 
    flang/unittests/Optimizer/AliasAnalysisCacheTest.cpp

Modified: 
    flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
    flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
    flang/lib/Optimizer/Transforms/LoopInvariantCodeMotion.cpp
    flang/unittests/Optimizer/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
index 832634a708dba..32d95d3e09677 100644
--- a/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
+++ b/flang/include/flang/Optimizer/Analysis/AliasAnalysis.h
@@ -19,7 +19,9 @@
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/PointerUnion.h"
 #include "llvm/ADT/SmallVector.h"
+#include <cstddef>
 #include <memory>
+#include <utility>
 
 namespace fir {
 
@@ -351,10 +353,39 @@ struct AliasAnalysis {
   /// snapshots are not collected, and getSource performs only the
   /// SourceKind/origin classification without that bookkeeping side
   /// effect.
+  ///
+  /// When source caching is enabled (see enableSourceCache()), the result is
+  /// memoized keyed on (value, flags), and recursive sub-queries share the
+  /// same cache. The cache is a frozen snapshot with no automatic
+  /// invalidation: it is only valid while the IR reachable from the queried
+  /// values is not mutated in a way that would change the source. It lives no
+  /// longer than this AliasAnalysis instance, so a client enables caching only
+  /// for a region in which it does not perform such mutations (e.g. a pass
+  /// that only moves operations).
   fir::AliasAnalysis::Source getSource(mlir::Value,
                                        bool getLastInstantiationPoint = false,
                                        bool collectScopedOrigins = true);
 
+  /// Enable memoization of getSource() results on this analysis instance.
+  /// Caching is opt-in and off by default; a client that does not mutate the
+  /// IR in a way that affects getSource() (see getSource()) may enable it to
+  /// avoid recomputing sources for repeated queries.
+  void enableSourceCache();
+
+  /// Disable getSource() memoization and drop any cached entries.
+  void disableSourceCache();
+
+  /// Testing only: number of entries currently held in the getSource() cache.
+  std::size_t getSourceCacheSizeForTesting() const {
+    return getSourceCache.size();
+  }
+
+  /// Testing only: cumulative getSource() cache hits / misses on this instance.
+  std::size_t getSourceCacheHitsForTesting() const { return sourceCacheHits; }
+  std::size_t getSourceCacheMissesForTesting() const {
+    return sourceCacheMisses;
+  }
+
   /// Return true, if `ty` is a reference type to a boxed
   /// POINTER object or a raw fir::PointerType.
   static bool isPointerReference(mlir::Type ty);
@@ -370,6 +401,16 @@ struct AliasAnalysis {
   bool functionHasMultipleScopes(mlir::Value v);
 
 private:
+  /// Compute the memory source of a value. This is the uncached
+  /// implementation of getSource(); getSource() is a thin wrapper that
+  /// memoizes the result when source caching is enabled.
+  fir::AliasAnalysis::Source getSourceImpl(mlir::Value v,
+                                           bool getLastInstantiationPoint,
+                                           bool collectScopedOrigins);
+
+  /// Clear the getSource() memoization cache.
+  void clearSourceCache() { getSourceCache.clear(); }
+
   /// Build an intermediate Source rooted at the declare captured by the
   /// snapshot. Reuses getSource(declValue) for the SourceKind / origin
   /// classification (with collectScopedOrigins=false), then overrides
@@ -446,6 +487,21 @@ struct AliasAnalysis {
   /// functionHasMultipleScopes(); both true and false are cached so that
   /// repeated queries are O(1) without re-walking the function body.
   llvm::DenseMap<mlir::Operation *, bool> multiScopeCache;
+
+  /// Opt-in memoization of getSource() results, keyed on the queried value
+  /// and the two boolean flags (getLastInstantiationPoint,
+  /// collectScopedOrigins) packed into the unsigned. Only consulted/populated
+  /// while \c sourceCacheEnabled is set. This is a frozen snapshot cache with
+  /// no automatic invalidation (see getSource()).
+  llvm::DenseMap<std::pair<mlir::Value, unsigned>, Source> getSourceCache;
+
+  /// Whether getSource() should consult/populate getSourceCache.
+  bool sourceCacheEnabled = false;
+
+  /// Testing-only counters (see getSourceCacheHitsForTesting()). Incremented
+  /// on each getSource() cache hit / miss.
+  std::size_t sourceCacheHits = 0;
+  std::size_t sourceCacheMisses = 0;
 };
 
 inline bool operator==(const AliasAnalysis::Source::SourceOrigin &lhs,

diff  --git a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
index 435f7aa6b5bf1..0c518fc460375 100644
--- a/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
+++ b/flang/lib/Optimizer/Analysis/AliasAnalysis.cpp
@@ -29,6 +29,7 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #include <optional>
+#include <utility>
 
 using namespace mlir;
 
@@ -1107,9 +1108,40 @@ static mlir::Value walkBlockArgPassThroughs(mlir::Value v) {
   return v;
 }
 
+void AliasAnalysis::enableSourceCache() { sourceCacheEnabled = true; }
+
+void AliasAnalysis::disableSourceCache() {
+  sourceCacheEnabled = false;
+  clearSourceCache();
+}
+
 AliasAnalysis::Source AliasAnalysis::getSource(mlir::Value v,
                                                bool getLastInstantiationPoint,
                                                bool collectScopedOrigins) {
+  if (!sourceCacheEnabled)
+    return getSourceImpl(v, getLastInstantiationPoint, collectScopedOrigins);
+
+  // Key on the queried value and the two boolean flags. Recursive sub-queries
+  // go through this same wrapper, so the whole walk is memoized.
+  std::pair<mlir::Value, unsigned> key{v,
+                                       (getLastInstantiationPoint ? 1u : 0u) |
+                                           (collectScopedOrigins ? 2u : 0u)};
+  auto it = getSourceCache.find(key);
+  if (it != getSourceCache.end()) {
+    ++sourceCacheHits;
+    return it->second;
+  }
+
+  ++sourceCacheMisses;
+  Source source =
+      getSourceImpl(v, getLastInstantiationPoint, collectScopedOrigins);
+  getSourceCache.try_emplace(key, source);
+  return source;
+}
+
+AliasAnalysis::Source
+AliasAnalysis::getSourceImpl(mlir::Value v, bool getLastInstantiationPoint,
+                             bool collectScopedOrigins) {
   // If v is a pass-through block argument (see walkBlockArgPassThroughs),
   // continue from the underlying operand so the tracking loop below has a
   // defining op to chew on. Without this, a recursive query like the one in

diff  --git a/flang/lib/Optimizer/Transforms/LoopInvariantCodeMotion.cpp b/flang/lib/Optimizer/Transforms/LoopInvariantCodeMotion.cpp
index 9a4b88f8f82b3..26388c6758daa 100644
--- a/flang/lib/Optimizer/Transforms/LoopInvariantCodeMotion.cpp
+++ b/flang/lib/Optimizer/Transforms/LoopInvariantCodeMotion.cpp
@@ -24,6 +24,7 @@
 #include "mlir/Transforms/LoopInvariantCodeMotionUtils.h"
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/DebugLog.h"
+#include <utility>
 
 namespace fir {
 #define GEN_PASS_DEF_LOOPINVARIANTCODEMOTION
@@ -265,7 +266,15 @@ void LoopInvariantCodeMotion::runOnOperation() {
   LDBG() << "Enter [HL]FIR LoopInvariantCodeMotion()";
 
   auto &aliasAnalysis = getAnalysis<AliasAnalysis>();
-  aliasAnalysis.addAnalysisImplementation(fir::AliasAnalysis{});
+  // Enable getSource() memoization on the FIR AliasAnalysis for the duration
+  // of this pass. This is a frozen-snapshot cache with no automatic
+  // invalidation, but it is sound here because LICM only moves operations, so
+  // getSource()'s inputs are unchanged across the hoists. The cache lives no
+  // longer than this analysis instance, which the pass manager drops when the
+  // analysis is invalidated after the pass.
+  fir::AliasAnalysis firAliasAnalysis;
+  firAliasAnalysis.enableSourceCache();
+  aliasAnalysis.addAnalysisImplementation(std::move(firAliasAnalysis));
 
   std::function<bool(Operation *, LoopLikeOpInterface, bool)>
       shouldMoveOutOfLoop = [&](Operation *op, LoopLikeOpInterface loopLike,

diff  --git a/flang/unittests/Optimizer/AliasAnalysisCacheTest.cpp b/flang/unittests/Optimizer/AliasAnalysisCacheTest.cpp
new file mode 100644
index 0000000000000..4d1abfff7e258
--- /dev/null
+++ b/flang/unittests/Optimizer/AliasAnalysisCacheTest.cpp
@@ -0,0 +1,107 @@
+//===- AliasAnalysisCacheTest.cpp -----------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Unit tests for the opt-in getSource() memoization cache on
+// fir::AliasAnalysis. The cache is off by default; when enabled it memoizes
+// getSource() results for the lifetime of the analysis instance and is a
+// frozen snapshot (no automatic invalidation).
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+#include "flang/Optimizer/Analysis/AliasAnalysis.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
+#include "flang/Optimizer/Support/InitFIR.h"
+
+struct AliasAnalysisCacheTest : public testing::Test {
+public:
+  void SetUp() override {
+    fir::support::loadDialects(context);
+    builder = std::make_unique<mlir::OpBuilder>(&context);
+    mlir::Location loc = builder->getUnknownLoc();
+
+    // Set up a module with a dummy function; insert into its entry block.
+    moduleOp = mlir::ModuleOp::create(*builder, loc);
+    builder->setInsertionPointToStart(moduleOp->getBody());
+    mlir::func::FuncOp func = mlir::func::FuncOp::create(*builder, loc,
+        "alias_analysis_cache_tests", builder->getFunctionType({}, {}));
+    auto *entryBlock = func.addEntryBlock();
+    builder->setInsertionPointToStart(entryBlock);
+  }
+
+  mlir::Location getLoc() { return builder->getUnknownLoc(); }
+
+  mlir::Value createAlloca() {
+    return fir::AllocaOp::create(
+        *builder, getLoc(), mlir::Float32Type::get(&context));
+  }
+
+  mlir::MLIRContext context;
+  std::unique_ptr<mlir::OpBuilder> builder;
+  mlir::OwningOpRef<mlir::ModuleOp> moduleOp;
+};
+
+// Caching is off by default: getSource() bypasses the cache, so nothing is
+// memoized and the hit/miss counters stay at zero.
+TEST_F(AliasAnalysisCacheTest, DisabledByDefault) {
+  mlir::Value a = createAlloca();
+  fir::AliasAnalysis aa;
+
+  (void)aa.getSource(a);
+  (void)aa.getSource(a);
+
+  EXPECT_EQ(aa.getSourceCacheSizeForTesting(), 0u);
+  EXPECT_EQ(aa.getSourceCacheHitsForTesting(), 0u);
+  EXPECT_EQ(aa.getSourceCacheMissesForTesting(), 0u);
+}
+
+// Once enabled, a query populates the cache (a miss) and a repeated query for
+// the same value is served from it (a hit) without growing the cache.
+TEST_F(AliasAnalysisCacheTest, FillAndHit) {
+  mlir::Value a = createAlloca();
+  mlir::Value b = createAlloca();
+  fir::AliasAnalysis aa;
+  aa.enableSourceCache();
+
+  (void)aa.getSource(a);
+  std::size_t sizeAfterFill = aa.getSourceCacheSizeForTesting();
+  std::size_t missesAfterFill = aa.getSourceCacheMissesForTesting();
+  EXPECT_GE(sizeAfterFill, 1u);
+  EXPECT_GE(missesAfterFill, 1u);
+  EXPECT_EQ(aa.getSourceCacheHitsForTesting(), 0u);
+
+  // Repeating the top-level query is a single cache hit: it returns the
+  // memoized result without recomputing (and thus without new misses or
+  // entries), regardless of any recursive sub-queries the first call made.
+  (void)aa.getSource(a);
+  EXPECT_EQ(aa.getSourceCacheSizeForTesting(), sizeAfterFill);
+  EXPECT_EQ(aa.getSourceCacheMissesForTesting(), missesAfterFill);
+  EXPECT_EQ(aa.getSourceCacheHitsForTesting(), 1u);
+
+  // A distinct value adds at least one new entry.
+  (void)aa.getSource(b);
+  EXPECT_GT(aa.getSourceCacheSizeForTesting(), sizeAfterFill);
+  EXPECT_GT(aa.getSourceCacheMissesForTesting(), missesAfterFill);
+}
+
+// disableSourceCache() clears the entries and turns memoization off, so later
+// queries bypass the cache and leave it empty.
+TEST_F(AliasAnalysisCacheTest, DisableClearsAndBypasses) {
+  mlir::Value a = createAlloca();
+  fir::AliasAnalysis aa;
+  aa.enableSourceCache();
+
+  (void)aa.getSource(a);
+  EXPECT_GE(aa.getSourceCacheSizeForTesting(), 1u);
+
+  aa.disableSourceCache();
+  EXPECT_EQ(aa.getSourceCacheSizeForTesting(), 0u);
+
+  (void)aa.getSource(a);
+  EXPECT_EQ(aa.getSourceCacheSizeForTesting(), 0u);
+}

diff  --git a/flang/unittests/Optimizer/CMakeLists.txt b/flang/unittests/Optimizer/CMakeLists.txt
index 1697b64f12ed9..fc2badc59a083 100644
--- a/flang/unittests/Optimizer/CMakeLists.txt
+++ b/flang/unittests/Optimizer/CMakeLists.txt
@@ -40,6 +40,7 @@ add_flang_unittest(FlangOptimizerTests
   Builder/Runtime/TransformationalTest.cpp
   OpenACC/FIROpenACCPointerLikeTypeInterfaceTest.cpp
   OpenACC/FIROpenACCSupportAnalysisTest.cpp
+  AliasAnalysisCacheTest.cpp
   FIRCallInterfaceTest.cpp
   FIRContextTest.cpp
   FIRTypesTest.cpp


        


More information about the flang-commits mailing list