[clang] [CIR] Memoize FuncType purity checks in the CIR dialect (PR #232863)
Bruno Cardoso Lopes via cfe-commits
cfe-commits at lists.llvm.org
Fri Oct 16 10:54:21 PDT 2026
https://github.com/bcardosolopes updated https://github.com/llvm/llvm-project/pull/232863
>From 586a80a94542620da7d681323353543f9e093d39 Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Wed, 7 Oct 2026 17:12:42 -0700
Subject: [PATCH] [CIR] Memoize FuncType purity checks in the CIR dialect
FuncType::verify walks every type in the signature to prove it only
contains CIR types, and it runs on every FuncType construction in
asserts-enabled builds. Signatures share the same record types over
and over, so the same graphs were re-walked thousands of times per
translation unit (15% of CXXABILowering).
Memoize purity per type in the CIR dialect object: the dialect gives
the cache exactly the context lifetime, and a mutex guards the map
(computing outside the lock is safe since two threads store the same
value). Records gain members when completed, so a 'pure' answer is
only cached when the walk saw no incomplete record; 'impure' answers
are always stable.
CXXABILowering on clang/lib/Sema/SemaExpr.cpp goes from 6.3s to 3.0s
and -emit-cir from 49.4s to 29.3s (asserts-enabled build); output is
unchanged.
Assisted-by: Muse Code (Muse).
---
.../include/clang/CIR/Dialect/IR/CIRDialect.h | 2 ++
.../clang/CIR/Dialect/IR/CIRDialect.td | 8 ++++++
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 27 +++++++++++++++++++
clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 9 ++-----
clang/test/CIR/IR/invalid-func.cir | 10 +++++++
5 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index d9018e93628b69..f28f2bf34422d0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -34,6 +34,8 @@
#include "clang/CIR/Interfaces/CIROpInterfaces.h"
#include "clang/CIR/MissingFeatures.h"
+#include <mutex>
+
using BuilderCallbackRef =
llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>;
using BuilderOpStateCallbackRef = llvm::function_ref<void(
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 429af17eb194b8..2d6fc2ce8e7d06 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -146,6 +146,14 @@ def CIR_Dialect : Dialect {
mlir::Type parseType(mlir::DialectAsmParser &parser) const override;
void printType(mlir::Type type,
mlir::DialectAsmPrinter &printer) const override;
+
+ /// Returns true if \p ty and every type nested in it are CIR types.
+ /// Memoized, since FuncType verification re-queries the same records.
+ bool isPureCIRType(mlir::Type ty) const;
+
+ private:
+ mutable llvm::DenseMap<mlir::Type, bool> pureCIRTypeCache;
+ mutable std::mutex pureCIRTypeCacheMutex;
}];
}
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index f564eee0537057..4dfbfbb195ca46 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -253,6 +253,33 @@ static LogicalResult verifyOffloadContainer(mlir::Operation *op) {
return success();
}
+bool cir::CIRDialect::isPureCIRType(mlir::Type ty) const {
+ assert(ty && "isPureCIRType requires a non-null type");
+ {
+ std::lock_guard<std::mutex> lock(pureCIRTypeCacheMutex);
+ auto it = pureCIRTypeCache.find(ty);
+ if (it != pureCIRTypeCache.end())
+ return it->second;
+ }
+ // Records gain members when completed, so a "pure" answer is only stable
+ // when the walk saw no incomplete record. "Impure" answers stay stable.
+ bool sawIncompleteRecord = false;
+ bool result = !ty.walk([&](mlir::Type t) {
+ if (!t)
+ return mlir::WalkResult::advance();
+ if (auto rec = mlir::dyn_cast<cir::RecordType>(t))
+ sawIncompleteRecord |= rec.isIncomplete();
+ return mlir::isa<cir::CIRDialect>(t.getDialect())
+ ? mlir::WalkResult::advance()
+ : mlir::WalkResult::interrupt();
+ }).wasInterrupted();
+ if (!result || !sawIncompleteRecord) {
+ std::lock_guard<std::mutex> lock(pureCIRTypeCacheMutex);
+ pureCIRTypeCache[ty] = result;
+ }
+ return result;
+}
+
LogicalResult
cir::CIRDialect::verifyOperationAttribute(mlir::Operation *op,
mlir::NamedAttribute attr) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 1ec5687e17db01..c6296180cfb025 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -64,13 +64,8 @@ cir::FPTypeInterface cir::getFloatingPointType(const llvm::fltSemantics &sem,
static bool isPureCIRType(mlir::Type ty) {
if (!ty)
return true;
- return !ty.walk([](mlir::Type t) {
- if (!t)
- return mlir::WalkResult::advance();
- return mlir::isa<cir::CIRDialect>(t.getDialect())
- ? mlir::WalkResult::advance()
- : mlir::WalkResult::interrupt();
- }).wasInterrupted();
+ return ty.getContext()->getOrLoadDialect<cir::CIRDialect>()->isPureCIRType(
+ ty);
}
//===----------------------------------------------------------------------===//
diff --git a/clang/test/CIR/IR/invalid-func.cir b/clang/test/CIR/IR/invalid-func.cir
index c4b0eb1fd0c3ef..da335f3a96c435 100644
--- a/clang/test/CIR/IR/invalid-func.cir
+++ b/clang/test/CIR/IR/invalid-func.cir
@@ -109,3 +109,13 @@ module {
cir.func comdat preferred_alignment 0 @f() { // expected-error {{expected '('}}
cir.return
}
+
+// -----
+
+// The inner function type is built while S is still incomplete; the purity
+// cache must not keep that answer once S completes with a non-CIR member.
+!rec_S = !cir.struct<"S" {data !cir.ptr<!cir.func<(!cir.ptr<!cir.struct<"S">>)>>, data i32}>
+
+module {
+ cir.func private @l0(!cir.ptr<!rec_S>) // expected-error {{expected all types in the function signature to be CIR types}}
+}
More information about the cfe-commits
mailing list