[CIR] Eliminate SymbolTable::lookupSymbolIn hotspots (#193362)

mlir::SymbolTable::lookupSymbolIn is O(n) per lookup, so cumulative
symbol lookups during CIRGen are O(n^2) in the number of global symbols.
On template-heavy translation units this becomes a significant
compile-time hotspot.

Replace the SymbolTable lookup path with a per-CIRGenModule DenseMap
cache keyed by symbol name, giving O(1) lookups.

On a synthetic template-heavy stress test, end-to-end compile time on
`clang -fclangir -S -emit-llvm -O0` improves by ~11% on a 33K-LOC input
(5.86s -> 5.21s) and ~16% on a 67K-LOC input (16.09s -> 13.52s). The
super-linear growth of the win with input size confirms the O(n^2) ->
O(n) effect.

Similar to previous compile time fix, repro shape (scale records and
template instantiations into the hundreds/thousands to amplify):

  template <typename T, int N> struct Box { T head; Box<T, N-1> tail; };
  template <typename T> struct Box<T, 0> { T head; };
  struct R0; struct R1;
  struct R0 { int v; Box<int, 4> b; R1* next; };
  struct R1 { long v; Box<long, 4> b; R0* next; };
  int f0(R0*); int f1(R1*);
  int f0(R0* r) { return r->v + (r->next ? f1(r->next) : 0); }
  int f1(R1* r) { return (int)r->v + (r->next ? f0(r->next) : 0); }
This commit is contained in:
Bruno Cardoso Lopes
2026-04-27 14:32:51 -07:00
committed by GitHub
parent 10fe2e3d68
commit 20a62a41c2
7 changed files with 48 additions and 12 deletions

View File

@@ -450,6 +450,7 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &d,
cir::GlobalOp gv = builder.createVersionedGlobal(
getModule(), getLoc(d.getLocation()), name, lty, false, linkage);
insertGlobalSymbol(gv);
// TODO(cir): infer visibility from linkage in global op builder.
gv.setVisibility(getMLIRVisibilityFromCIRLinkage(linkage));
gv.setInitialValueAttr(init);
@@ -545,6 +546,7 @@ Address CIRGenModule::createUnnamedGlobalFrom(const VarDecl &d,
cir::GlobalOp gv = builder.createVersionedGlobal(
getModule(), getLoc(d.getLocation()), name, ty, isConstant,
cir::GlobalLinkageKind::PrivateLinkage);
insertGlobalSymbol(gv);
// TODO(cir): infer visibility from linkage in global op builder.
gv.setVisibility(getMLIRVisibilityFromCIRLinkage(
cir::GlobalLinkageKind::PrivateLinkage));

View File

@@ -2077,6 +2077,7 @@ CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
clone = cir::FuncOp::create(builder, calleeFunc.getLoc(), fdInlineName,
calleeFunc.getFunctionType());
cgm.insertGlobalSymbol(clone);
clone.setLinkageAttr(cir::GlobalLinkageKindAttr::get(
&cgm.getMLIRContext(), cir::GlobalLinkageKind::InternalLinkage));
clone.setSymVisibility("private");

View File

@@ -683,6 +683,7 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
builder.setInsertionPoint(fn);
clone = cir::FuncOp::create(builder, fn.getLoc(), fdInlineName,
fn.getFunctionType());
cgm.insertGlobalSymbol(clone);
clone.setLinkage(cir::GlobalLinkageKind::InternalLinkage);
clone.setSymVisibility("private");
clone.setInlineKind(cir::InlineKind::AlwaysInline);
@@ -707,6 +708,7 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
.replaceAllSymbolUses(fn.getSymNameAttr(), cgm.getModule())
.failed())
llvm_unreachable("Failed to replace inline builtin symbol uses");
cgm.eraseGlobalSymbol(inlineFn);
inlineFn.erase();
}
break;

View File

@@ -1198,8 +1198,7 @@ CIRGenItaniumRTTIBuilder::getAddrOfExternalRTTIDescriptor(mlir::Location loc,
CIRGenBuilderTy &builder = cgm.getBuilder();
// Look for an existing global.
cir::GlobalOp gv = dyn_cast_or_null<cir::GlobalOp>(
mlir::SymbolTable::lookupSymbolIn(cgm.getModule(), name));
cir::GlobalOp gv = dyn_cast_or_null<cir::GlobalOp>(cgm.getGlobalValue(name));
if (!gv) {
// Create a new global variable.
@@ -1428,8 +1427,7 @@ mlir::Attribute CIRGenItaniumRTTIBuilder::buildTypeInfo(mlir::Location loc,
llvm::raw_svector_ostream out(name);
cgm.getCXXABI().getMangleContext().mangleCXXRTTI(ty, out);
auto oldGV = dyn_cast_or_null<cir::GlobalOp>(
mlir::SymbolTable::lookupSymbolIn(cgm.getModule(), name));
auto oldGV = dyn_cast_or_null<cir::GlobalOp>(cgm.getGlobalValue(name));
if (oldGV && !oldGV.isDeclaration()) {
assert(!oldGV.hasAvailableExternallyLinkage() &&
@@ -1604,8 +1602,7 @@ mlir::Attribute CIRGenItaniumRTTIBuilder::buildTypeInfo(
cgm.getCXXABI().getMangleContext().mangleCXXRTTI(ty, out);
// Create new global and search for an existing global.
auto oldGV = dyn_cast_or_null<cir::GlobalOp>(
mlir::SymbolTable::lookupSymbolIn(cgm.getModule(), name));
auto oldGV = dyn_cast_or_null<cir::GlobalOp>(cgm.getGlobalValue(name));
cir::GlobalOp gv =
CIRGenModule::createGlobalOp(cgm, loc, name, init.getType(),
@@ -1627,6 +1624,7 @@ mlir::Attribute CIRGenItaniumRTTIBuilder::buildTypeInfo(
cgm.errorNYI("buildTypeInfo: old GV !use_empty");
return {};
}
cgm.eraseGlobalSymbol(oldGV);
oldGV->erase();
}

View File

@@ -380,8 +380,7 @@ void CIRGenModule::emitGlobalDecl(const clang::GlobalDecl &d) {
// TODO: Not sure what to map this to for MLIR
mlir::Operation *globalValueOp = op;
if (auto gv = dyn_cast<cir::GetGlobalOp>(op)) {
globalValueOp =
mlir::SymbolTable::lookupSymbolIn(getModule(), gv.getNameAttr());
globalValueOp = getGlobalValue(gv.getName());
assert(globalValueOp && "expected a valid global op");
}
@@ -663,7 +662,8 @@ void CIRGenModule::handleCXXStaticMemberVarInstantiation(VarDecl *vd) {
}
mlir::Operation *CIRGenModule::getGlobalValue(StringRef name) {
return mlir::SymbolTable::lookupSymbolIn(theModule, name);
auto it = symbolLookupCache.find(name);
return it != symbolLookupCache.end() ? it->second : nullptr;
}
cir::GlobalOp
@@ -699,6 +699,7 @@ CIRGenModule::createGlobalOp(CIRGenModule &cgm, mlir::Location loc,
mlir::SymbolTable::setSymbolVisibility(
g, mlir::SymbolTable::Visibility::Private);
}
cgm.symbolLookupCache[g.getSymNameAttr()] = g;
return g;
}
@@ -944,6 +945,7 @@ void CIRGenModule::replaceGlobal(cir::GlobalOp oldGV, cir::GlobalOp newGV) {
// erased) operation, which would leave them detached from the module.
if (lastGlobalOp == oldGV)
lastGlobalOp = newGV;
eraseGlobalSymbol(oldGV);
oldGV.erase();
}
@@ -1598,6 +1600,7 @@ void CIRGenModule::applyReplacements() {
llvm_unreachable("internal error, cannot RAUW symbol");
if (newF) {
newF->moveBefore(oldF);
eraseGlobalSymbol(oldF);
oldF->erase();
}
}
@@ -1606,8 +1609,7 @@ void CIRGenModule::applyReplacements() {
cir::GlobalOp CIRGenModule::createOrReplaceCXXRuntimeVariable(
mlir::Location loc, StringRef name, mlir::Type ty,
cir::GlobalLinkageKind linkage, clang::CharUnits alignment) {
auto gv = mlir::dyn_cast_or_null<cir::GlobalOp>(
mlir::SymbolTable::lookupSymbolIn(theModule, name));
auto gv = mlir::dyn_cast_or_null<cir::GlobalOp>(getGlobalValue(name));
if (gv) {
// Check if the variable has the right type.
@@ -1928,7 +1930,7 @@ std::string CIRGenModule::getUniqueGlobalName(const std::string &baseName) {
std::string result =
baseName + "." + std::to_string(cgGlobalNames[baseName]++);
// There should not be any symbol with this name in the module.
assert(!mlir::SymbolTable::lookupSymbolIn(theModule, result));
assert(!getGlobalValue(result));
return result;
}
@@ -2954,6 +2956,7 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
replaceUsesOfNonProtoTypeWithRealFunction(entry, funcOp);
// Obliterate no-proto declaration.
eraseGlobalSymbol(entry);
entry->erase();
}
@@ -3036,6 +3039,8 @@ CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
func = cir::FuncOp::create(builder, loc, name, funcType);
symbolLookupCache[func.getSymNameAttr()] = func;
assert(!cir::MissingFeatures::opFuncAstDeclAttr());
if (funcDecl && !funcDecl->hasPrototype())
@@ -3312,6 +3317,7 @@ void CIRGenModule::emitAliasForGlobal(StringRef mangledName,
// function declaration.
assert(cast<cir::FuncOp>(op).getFunctionType() == alias.getFunctionType() &&
"declaration exists with different type");
eraseGlobalSymbol(op);
op->erase();
} else {
// Name already set by createCIRFunction
@@ -3556,6 +3562,7 @@ CIRGenModule::getAddrOfGlobalTemporary(const MaterializeTemporaryExpr *mte,
mlir::Operation *&entry = materializedGlobalTemporaryMap[mte];
if (entry) {
entry->replaceAllUsesWith(cv);
eraseGlobalSymbol(entry);
entry->erase();
}
entry = cv;

View File

@@ -35,6 +35,7 @@
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/TargetParser/Triple.h"
@@ -226,8 +227,31 @@ public:
llvm::DenseMap<const Decl *, cir::GlobalOp> staticLocalDeclMap;
llvm::DenseMap<const VarDecl *, cir::GlobalOp> initializerConstants;
/// Cache for O(1) symbol lookups by name, replacing the O(N) linear scan
/// in SymbolTable::lookupSymbolIn that getGlobalValue used previously.
llvm::StringMap<mlir::Operation *> symbolLookupCache;
mlir::Operation *getGlobalValue(llvm::StringRef ref);
/// O(1) lookup of a FuncOp by name in the symbol cache.
/// Returns nullptr if the name is not found or is not a FuncOp.
cir::FuncOp lookupFuncOp(llvm::StringRef name) {
auto *op = getGlobalValue(name);
return op ? mlir::dyn_cast<cir::FuncOp>(op) : cir::FuncOp{};
}
void insertGlobalSymbol(mlir::Operation *op) {
if (auto sym = mlir::dyn_cast<mlir::SymbolOpInterface>(op))
symbolLookupCache[sym.getName()] = op;
}
void eraseGlobalSymbol(mlir::Operation *op) {
if (auto sym = mlir::dyn_cast<mlir::SymbolOpInterface>(op)) {
auto it = symbolLookupCache.find(sym.getName());
if (it != symbolLookupCache.end() && it->second == op)
symbolLookupCache.erase(it);
}
}
cir::GlobalOp getStaticLocalDeclAddress(const VarDecl *d) {
return staticLocalDeclMap[d];
}

View File

@@ -900,10 +900,12 @@ cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl gd,
assert(oldThunkFn.isDeclaration() && "Shouldn't replace non-declaration");
// Remove the name from the old thunk function and get a new thunk.
cgm.eraseGlobalSymbol(oldThunkFn);
oldThunkFn.setName(StringRef());
thunkFn =
cir::FuncOp::create(cgm.getBuilder(), thunk->getLoc(), name.str(),
thunkFnTy, cir::GlobalLinkageKind::ExternalLinkage);
cgm.insertGlobalSymbol(thunkFn);
cgm.setCIRFunctionAttributes(md, fnInfo, thunkFn, /*isThunk=*/false);
if (!oldThunkFn->use_empty())