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
108 lines
4.0 KiB
C++
108 lines
4.0 KiB
C++
//===- 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);
|
|
}
|