[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