https://github.com/SharmaRithik updated 
https://github.com/llvm/llvm-project/pull/207261

>From 2a327307f1d32b905525dd906951c0052b6611a3 Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Wed, 1 Jul 2026 23:56:31 +0000
Subject: [PATCH 1/9] [CIR] Add support for AST call expressions on cir.call

Before this change the AST node of a call was thrown away right after
CIRGen. Passes that want to know what the source code called had nothing
to look at. The ClangIR incubator fixes this by keeping a link to the
call expression as an attribute on the call op.

This patch adds that attribute. CIRGen now keeps the call expression that
each cir.call came from, for calls that appear in the source code. Calls
that the compiler makes on its own have no call expression, so they are
skipped. These include constructors, thunks, and the CUDA device stub
launcher. The attribute works the same way as the existing ASTVarDeclAttr.
It is optional and stays hidden in the normal printed IR, so the output
does not change from before. It shows up only in the generic op form,
which is what the tests check.

The ASTCallExprInterface has no methods yet. They will come later with the
pass that uses them, the IdiomRecognizer pass.
---
 clang/include/clang/CIR/Dialect/IR/CIRAttrs.td    |  4 ++++
 clang/include/clang/CIR/Dialect/IR/CIRDialect.td  |  1 +
 clang/include/clang/CIR/Dialect/IR/CIROps.td      |  3 ++-
 .../clang/CIR/Interfaces/ASTAttrInterfaces.h      |  1 +
 .../clang/CIR/Interfaces/ASTAttrInterfaces.td     |  4 ++++
 clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp            |  2 ++
 clang/lib/CIR/CodeGen/CIRGenCall.cpp              |  9 ++++++++-
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp              |  2 +-
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp           |  4 ++--
 clang/lib/CIR/CodeGen/CIRGenFunction.h            |  3 ++-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp           |  3 ++-
 clang/test/CIR/CodeGen/call-ast-attr.c            | 13 +++++++++++++
 clang/test/CIR/CodeGen/call-ast-attr.cpp          | 15 +++++++++++++++
 13 files changed, 57 insertions(+), 7 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/call-ast-attr.c
 create mode 100644 clang/test/CIR/CodeGen/call-ast-attr.cpp

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index ec99899355b17..cf00f9022c952 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1684,6 +1684,10 @@ def CIR_ASTVarDeclAttr : CIR_AST<"VarDecl", "var.decl", [
   ASTVarDeclInterface
 ]>;
 
+def CIR_ASTCallExprAttr : CIR_AST<"CallExpr", "call.expr", [
+  ASTCallExprInterface
+]>;
+
 
//===----------------------------------------------------------------------===//
 // AnnotationAttr
 
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index c20af04f97a1a..7700ba4aec693 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -37,6 +37,7 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getSourceLanguageAttrName() { return "cir.lang"; }
     static llvm::StringRef getTripleAttrName() { return "cir.triple"; }
     static llvm::StringRef getOptInfoAttrName() { return "cir.opt_info"; }
+    static llvm::StringRef getAstAttrName() { return "ast"; }
     static llvm::StringRef getCalleeAttrName() { return "callee"; }
     static llvm::StringRef getNoThrowAttrName() { return "nothrow"; }
     static llvm::StringRef getNoReturnAttrName() { return "noreturn"; }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index e83fb81291007..24e1d0e9dbf3f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4312,7 +4312,8 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
       UnitAttr:$musttail,
       DefaultValuedAttr<CIR_SideEffect, "SideEffect::All">:$side_effect,
       OptionalAttr<DictArrayAttr>:$arg_attrs,
-      OptionalAttr<DictArrayAttr>:$res_attrs
+      OptionalAttr<DictArrayAttr>:$res_attrs,
+      OptionalAttr<ASTCallExprInterface>:$ast
       );
 }
 
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h 
b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
index 2fb633ee9221a..e886808f8e21a 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
@@ -14,6 +14,7 @@
 #include "clang/AST/Attr.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
 
 /// Include the generated interface declarations.
 #include "clang/CIR/Interfaces/ASTAttrInterfaces.h.inc"
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td 
b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
index 53909063abbab..5bf778afeb0f8 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
@@ -34,6 +34,10 @@ let cppNamespace = "::cir" in {
       }]>
     ];
   }
+
+  // Query methods are added together with their consumers in the
+  // IdiomRecognizer pass.
+  def ASTCallExprInterface : AttrInterface<"ASTCallExprInterface"> {}
 } // namespace cir
 
 #endif // MLIR_CIR_INTERFACES_ASTATTRINTERFACES_TD
diff --git a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
index 65a3c2a7468e9..403deeb849578 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
@@ -292,6 +292,8 @@ void 
CIRGenNVCUDARuntime::emitDeviceStubBodyNew(CIRGenFunction &cgf,
       cast<cir::FuncType>(launchTy), launchKernelName);
   const CIRGenFunctionInfo &callInfo =
       cgm.getTypes().arrangeFunctionDeclaration(cudaLaunchKernelFD);
+  // The emitted call targets the runtime launcher rather than the function
+  // named in the source call, so no AST call expression is attached.
   cgf.emitCall(callInfo, CIRGenCallee::forDirect(cudaKernelLauncherFn),
                ReturnValueSlot(), launchArgs);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index c3065c8917924..ece07b79ca158 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1173,7 +1173,8 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
                                 ReturnValueSlot returnValue,
                                 const CallArgList &args,
                                 cir::CIRCallOpInterface *callOp,
-                                mlir::Location loc) {
+                                mlir::Location loc,
+                                const clang::CallExpr *astCallExpr) {
   QualType retTy = funcInfo.getReturnType();
   cir::FuncType cirFuncTy = getTypes().getFunctionType(funcInfo);
 
@@ -1347,6 +1348,12 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
       emitCallLikeOp(*this, loc, indirectFuncTy, indirectFuncVal, directFuncOp,
                      cirCallArgs, isInvoke, attrs, argAttrs, retAttrs);
 
+  // Attach the AST call expression so later passes can reason about the
+  // original source call.
+  if (astCallExpr)
+    theCall->setAttr(cir::CIRDialect::getAstAttrName(),
+                     cir::ASTCallExprAttr::get(&getMLIRContext(), 
astCallExpr));
+
   if (callOp)
     *callOp = theCall;
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 9b9811f7c4cb1..8ab6e1dbce938 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2423,7 +2423,7 @@ RValue CIRGenFunction::emitCall(clang::QualType calleeTy,
 
   cir::CIRCallOpInterface callOp;
   RValue callResult = emitCall(funcInfo, callee, returnValue, args, &callOp,
-                               getLoc(e->getExprLoc()));
+                               getLoc(e->getExprLoc()), e);
 
   assert(!cir::MissingFeatures::generateDebugInfo());
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index df60005d77259..17cbb1d483a6e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -121,7 +121,7 @@ CIRGenFunction::emitCXXMemberPointerCallExpr(const 
CXXMemberCallExpr *ce,
   assert(!cir::MissingFeatures::opCallMustTail());
   return emitCall(cgm.getTypes().arrangeCXXMethodCall(argsList, fpt, required,
                                                       /*PrefixSize=*/0),
-                  callee, returnValue, argsList, nullptr, loc);
+                  callee, returnValue, argsList, nullptr, loc, ce);
 }
 
 RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
@@ -323,7 +323,7 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
   assert((ce || currSrcLoc) && "expected source location");
   mlir::Location loc = ce ? getLoc(ce->getExprLoc()) : *currSrcLoc;
   assert(!cir::MissingFeatures::opCallMustTail());
-  return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
+  return emitCall(fnInfo, callee, returnValue, args, nullptr, loc, ce);
 }
 
 static void emitNullBaseClassInitialization(CIRGenFunction &cgf,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 322355fde3957..a23763990c162 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1737,7 +1737,8 @@ class CIRGenFunction : public CIRGenTypeCache {
   RValue emitCall(const CIRGenFunctionInfo &funcInfo,
                   const CIRGenCallee &callee, ReturnValueSlot returnValue,
                   const CallArgList &args, cir::CIRCallOpInterface *callOp,
-                  mlir::Location loc);
+                  mlir::Location loc,
+                  const clang::CallExpr *astCallExpr = nullptr);
   RValue emitCall(const CIRGenFunctionInfo &funcInfo,
                   const CIRGenCallee &callee, ReturnValueSlot returnValue,
                   const CallArgList &args,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 67cc5e09f26d0..71dda584e9fcc 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1228,7 +1228,8 @@ printCallCommon(mlir::Operation *op, 
mlir::FlatSymbolRefAttr calleeSym,
       CIRDialect::getSideEffectAttrName(),
       CIRDialect::getOperandSegmentSizesAttrName(),
       llvm::StringRef("res_attrs"),
-      llvm::StringRef("arg_attrs")};
+      llvm::StringRef("arg_attrs"),
+      CIRDialect::getAstAttrName()};
   printer.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
   printer << " : ";
   if (calleeSym || !argAttrs) {
diff --git a/clang/test/CIR/CodeGen/call-ast-attr.c 
b/clang/test/CIR/CodeGen/call-ast-attr.c
new file mode 100644
index 0000000000000..4037b86f0b566
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-ast-attr.c
@@ -0,0 +1,13 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir 
-mlir-print-op-generic %s -o - | FileCheck %s --check-prefix=GENERIC
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
- | FileCheck %s --check-prefix=DEFAULT
+
+void callee(void);
+
+void caller(void) { callee(); }
+
+// Calls carry the AST call expression, visible in the generic form.
+// GENERIC: "cir.call"(){{.*}}ast = #cir.call.expr.ast
+
+// The attribute is elided from the pretty form.
+// DEFAULT: cir.call @callee()
+// DEFAULT-NOT: #cir.call.expr.ast
diff --git a/clang/test/CIR/CodeGen/call-ast-attr.cpp 
b/clang/test/CIR/CodeGen/call-ast-attr.cpp
new file mode 100644
index 0000000000000..402c7c1fff689
--- /dev/null
+++ b/clang/test/CIR/CodeGen/call-ast-attr.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-cir -mmlir -mlir-print-op-generic %s -o - | FileCheck %s
+
+struct S {
+  void method();
+};
+
+// Member calls carry the AST call expression as well.
+void member_call(S &s) { s.method(); }
+// CHECK: "cir.call"
+// CHECK-SAME: ast = #cir.call.expr.ast
+
+// Calls through pointers to member functions carry it too.
+void member_ptr_call(S &s, void (S::*fn)()) { (s.*fn)(); }
+// CHECK: "cir.call"
+// CHECK-SAME: ast = #cir.call.expr.ast

>From 52017132c9e5446285f83d1c105d37c86e0fbf7b Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Fri, 3 Jul 2026 00:10:55 +0000
Subject: [PATCH 2/9] [CIR] Record the source level identity of functions on
 cir.func

CIRGen throws the AST away once it finishes, so passes cannot tell what
the source code calls, and the mangled callee symbol would need a
demangler to read.

The IdiomRecognizer pass from the ClangIR incubator needs three facts
about a call. The plain callee name, whether the callee lives in the
std namespace, and whether it is an instance method. Those recognize
calls like std find as well as the begin and end calls used for
iterator raising. Everything else, like argument counts and types,
comes from the operations, and C library functions like strlen are
matched through their symbol, since C names have no mangling.

This patch provides those three facts as a cir.func_info attribute on
cir.func, set when the function is created, following the
CXXSpecialMemberAttr pattern. A pass answers its questions about a call
by looking up the callee function, the same way LoweringPrepare reads
the special member attribute today. Only functions in the std namespace
carry the attribute for now. It is plain data with a textual form, so
it survives a round trip through a CIR file, and existing test output
does not change. The IdiomRecognizer and LibOpt passes arrive in follow
up PRs.
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 46 +++++++++++++++++--
 .../clang/CIR/Dialect/IR/CIRDialect.td        |  1 -
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  4 +-
 .../clang/CIR/Interfaces/ASTAttrInterfaces.h  |  1 -
 .../clang/CIR/Interfaces/ASTAttrInterfaces.td |  4 --
 clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp        |  2 -
 clang/lib/CIR/CodeGen/CIRGenCall.cpp          |  9 +---
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp          |  2 +-
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp       |  4 +-
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  3 +-
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        | 32 +++++++++++++
 clang/lib/CIR/CodeGen/CIRGenModule.h          |  4 ++
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 26 ++++++++++-
 clang/test/CIR/CodeGen/call-ast-attr.c        | 13 ------
 clang/test/CIR/CodeGen/call-ast-attr.cpp      | 15 ------
 clang/test/CIR/CodeGen/func-info-attr.c       | 10 ++++
 clang/test/CIR/CodeGen/func-info-attr.cpp     | 45 ++++++++++++++++++
 clang/test/CIR/IR/func-info-attr.cir          | 11 +++++
 18 files changed, 175 insertions(+), 57 deletions(-)
 delete mode 100644 clang/test/CIR/CodeGen/call-ast-attr.c
 delete mode 100644 clang/test/CIR/CodeGen/call-ast-attr.cpp
 create mode 100644 clang/test/CIR/CodeGen/func-info-attr.c
 create mode 100644 clang/test/CIR/CodeGen/func-info-attr.cpp
 create mode 100644 clang/test/CIR/IR/func-info-attr.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index cf00f9022c952..2a67053ca3a8c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1298,6 +1298,48 @@ def CIR_CXXSpecialMemberAttr : AnyAttrOf<[
   CIR_CXXAssignAttr
 ]>;
 
+//===----------------------------------------------------------------------===//
+// FuncInfoAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_FuncInfoAttr : CIR_Attr<"FuncInfo", "func_info"> {
+  let summary = "Holds the source level identity of a function";
+  let description = [{
+    Carries facts about the source declaration a function was emitted from,
+    precomputed during CIRGen while the AST is available. Passes that want
+    to reason about what the source calls read this attribute instead of
+    the AST.
+
+    The `name` parameter holds the plain function name as written in the
+    source, without mangling, qualification, or template arguments.
+    Functions without a plain identifier, for example overloaded operators
+    and constructors, carry no attribute, and neither do static member
+    functions, so a free function and a static member with the same name
+    stay apart. The `in_std_namespace` parameter is true when the function
+    is declared directly in the `std` namespace, looking through inline
+    namespaces. For a member function the enclosing record decides. The
+    `instance_method` parameter is true for a member function that is not
+    static.
+
+    Example:
+    ```
+    #cir.func_info<name = "find", in_std_namespace = true>
+    ```
+  }];
+
+  let parameters = (ins
+    "mlir::StringAttr":$name,
+    DefaultValuedParameter<"bool", "false">:$in_std_namespace,
+    DefaultValuedParameter<"bool", "false">:$instance_method
+  );
+
+  let assemblyFormat = [{
+    `<` struct($name, $in_std_namespace, $instance_method) `>`
+  }];
+
+  let canHaveIllegalCXXABIType = 0;
+}
+
 
//===----------------------------------------------------------------------===//
 // BitfieldInfoAttr
 
//===----------------------------------------------------------------------===//
@@ -1684,10 +1726,6 @@ def CIR_ASTVarDeclAttr : CIR_AST<"VarDecl", "var.decl", [
   ASTVarDeclInterface
 ]>;
 
-def CIR_ASTCallExprAttr : CIR_AST<"CallExpr", "call.expr", [
-  ASTCallExprInterface
-]>;
-
 
//===----------------------------------------------------------------------===//
 // AnnotationAttr
 
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 7700ba4aec693..c20af04f97a1a 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -37,7 +37,6 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getSourceLanguageAttrName() { return "cir.lang"; }
     static llvm::StringRef getTripleAttrName() { return "cir.triple"; }
     static llvm::StringRef getOptInfoAttrName() { return "cir.opt_info"; }
-    static llvm::StringRef getAstAttrName() { return "ast"; }
     static llvm::StringRef getCalleeAttrName() { return "callee"; }
     static llvm::StringRef getNoThrowAttrName() { return "nothrow"; }
     static llvm::StringRef getNoReturnAttrName() { return "noreturn"; }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 24e1d0e9dbf3f..6d6b46b45b8be 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4025,6 +4025,7 @@ def CIR_FuncOp : CIR_Op<"func", [
     CIR_OptionalPriorityAttr:$global_ctor_priority,
     CIR_OptionalPriorityAttr:$global_dtor_priority,
     OptionalAttr<CIR_CXXSpecialMemberAttr>:$cxx_special_member,
+    OptionalAttr<CIR_FuncInfoAttr>:$func_info,
     OptionalAttr<CIR_AnnotationArrayAttr>:$annotations
   );
 
@@ -4312,8 +4313,7 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
       UnitAttr:$musttail,
       DefaultValuedAttr<CIR_SideEffect, "SideEffect::All">:$side_effect,
       OptionalAttr<DictArrayAttr>:$arg_attrs,
-      OptionalAttr<DictArrayAttr>:$res_attrs,
-      OptionalAttr<ASTCallExprInterface>:$ast
+      OptionalAttr<DictArrayAttr>:$res_attrs
       );
 }
 
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h 
b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
index e886808f8e21a..2fb633ee9221a 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h
@@ -14,7 +14,6 @@
 #include "clang/AST/Attr.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclTemplate.h"
-#include "clang/AST/Expr.h"
 
 /// Include the generated interface declarations.
 #include "clang/CIR/Interfaces/ASTAttrInterfaces.h.inc"
diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td 
b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
index 5bf778afeb0f8..53909063abbab 100644
--- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td
@@ -34,10 +34,6 @@ let cppNamespace = "::cir" in {
       }]>
     ];
   }
-
-  // Query methods are added together with their consumers in the
-  // IdiomRecognizer pass.
-  def ASTCallExprInterface : AttrInterface<"ASTCallExprInterface"> {}
 } // namespace cir
 
 #endif // MLIR_CIR_INTERFACES_ASTATTRINTERFACES_TD
diff --git a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
index 403deeb849578..65a3c2a7468e9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
@@ -292,8 +292,6 @@ void 
CIRGenNVCUDARuntime::emitDeviceStubBodyNew(CIRGenFunction &cgf,
       cast<cir::FuncType>(launchTy), launchKernelName);
   const CIRGenFunctionInfo &callInfo =
       cgm.getTypes().arrangeFunctionDeclaration(cudaLaunchKernelFD);
-  // The emitted call targets the runtime launcher rather than the function
-  // named in the source call, so no AST call expression is attached.
   cgf.emitCall(callInfo, CIRGenCallee::forDirect(cudaKernelLauncherFn),
                ReturnValueSlot(), launchArgs);
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index ece07b79ca158..c3065c8917924 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1173,8 +1173,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
                                 ReturnValueSlot returnValue,
                                 const CallArgList &args,
                                 cir::CIRCallOpInterface *callOp,
-                                mlir::Location loc,
-                                const clang::CallExpr *astCallExpr) {
+                                mlir::Location loc) {
   QualType retTy = funcInfo.getReturnType();
   cir::FuncType cirFuncTy = getTypes().getFunctionType(funcInfo);
 
@@ -1348,12 +1347,6 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
       emitCallLikeOp(*this, loc, indirectFuncTy, indirectFuncVal, directFuncOp,
                      cirCallArgs, isInvoke, attrs, argAttrs, retAttrs);
 
-  // Attach the AST call expression so later passes can reason about the
-  // original source call.
-  if (astCallExpr)
-    theCall->setAttr(cir::CIRDialect::getAstAttrName(),
-                     cir::ASTCallExprAttr::get(&getMLIRContext(), 
astCallExpr));
-
   if (callOp)
     *callOp = theCall;
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 8ab6e1dbce938..9b9811f7c4cb1 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2423,7 +2423,7 @@ RValue CIRGenFunction::emitCall(clang::QualType calleeTy,
 
   cir::CIRCallOpInterface callOp;
   RValue callResult = emitCall(funcInfo, callee, returnValue, args, &callOp,
-                               getLoc(e->getExprLoc()), e);
+                               getLoc(e->getExprLoc()));
 
   assert(!cir::MissingFeatures::generateDebugInfo());
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 17cbb1d483a6e..df60005d77259 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -121,7 +121,7 @@ CIRGenFunction::emitCXXMemberPointerCallExpr(const 
CXXMemberCallExpr *ce,
   assert(!cir::MissingFeatures::opCallMustTail());
   return emitCall(cgm.getTypes().arrangeCXXMethodCall(argsList, fpt, required,
                                                       /*PrefixSize=*/0),
-                  callee, returnValue, argsList, nullptr, loc, ce);
+                  callee, returnValue, argsList, nullptr, loc);
 }
 
 RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
@@ -323,7 +323,7 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
   assert((ce || currSrcLoc) && "expected source location");
   mlir::Location loc = ce ? getLoc(ce->getExprLoc()) : *currSrcLoc;
   assert(!cir::MissingFeatures::opCallMustTail());
-  return emitCall(fnInfo, callee, returnValue, args, nullptr, loc, ce);
+  return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
 }
 
 static void emitNullBaseClassInitialization(CIRGenFunction &cgf,
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h 
b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index a23763990c162..322355fde3957 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1737,8 +1737,7 @@ class CIRGenFunction : public CIRGenTypeCache {
   RValue emitCall(const CIRGenFunctionInfo &funcInfo,
                   const CIRGenCallee &callee, ReturnValueSlot returnValue,
                   const CallArgList &args, cir::CIRCallOpInterface *callOp,
-                  mlir::Location loc,
-                  const clang::CallExpr *astCallExpr = nullptr);
+                  mlir::Location loc);
   RValue emitCall(const CIRGenFunctionInfo &funcInfo,
                   const CIRGenCallee &callee, ReturnValueSlot returnValue,
                   const CallArgList &args,
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 7a05aed4c33e0..5e0ad33173468 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3446,6 +3446,9 @@ CIRGenModule::createCIRFunction(mlir::Location loc, 
StringRef name,
     // Mark C++ special member functions (Constructor, Destructor etc.)
     setCXXSpecialMemberAttr(func, funcDecl);
 
+    // Record the source level identity of the function.
+    setFuncInfoAttr(func, funcDecl);
+
     if (!cgf)
       theModule.push_back(func);
 
@@ -3522,6 +3525,35 @@ void CIRGenModule::setCXXSpecialMemberAttr(
   }
 }
 
+void CIRGenModule::setFuncInfoAttr(cir::FuncOp funcOp,
+                                   const clang::FunctionDecl *funcDecl) {
+  // Functions without a plain identifier, for example overloaded operators
+  // and constructors, carry no information, since no pass can match a
+  // function it cannot name.
+  if (!funcDecl || !funcDecl->getIdentifier())
+    return;
+
+  // A member function lives inside its record, so the record decides
+  // whether the function belongs to the std namespace. Static member
+  // functions carry no attribute, so that a static member like
+  // char_traits::find can never be mistaken for the free std::find.
+  const auto *method = dyn_cast<CXXMethodDecl>(funcDecl);
+  if (method && method->isStatic())
+    return;
+  bool inStdNamespace = method ? method->getParent()->isInStdNamespace()
+                               : funcDecl->isInStdNamespace();
+
+  // Only functions in the std namespace carry the attribute for now. Those
+  // are the ones upcoming passes look for, and plain C library functions
+  // can be matched through their symbol, since C names have no mangling.
+  if (!inStdNamespace)
+    return;
+
+  funcOp.setFuncInfoAttr(cir::FuncInfoAttr::get(
+      &getMLIRContext(), builder.getStringAttr(funcDecl->getName()),
+      inStdNamespace, method && method->isInstance()));
+}
+
 static void setWindowsItaniumDLLImport(CIRGenModule &cgm, bool isLocal,
                                        cir::FuncOp funcOp, StringRef name) {
   // In Windows Itanium environments, try to mark runtime functions
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h 
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 144f8c7b9f3e7..8d5f955cbb261 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -802,6 +802,10 @@ class CIRGenModule : public CIRGenTypeCache {
   void setCXXSpecialMemberAttr(cir::FuncOp funcOp,
                                const clang::FunctionDecl *funcDecl);
 
+  /// Record the source level identity of the function (plain name, std
+  /// namespace membership) so passes can reason about it without the AST.
+  void setFuncInfoAttr(cir::FuncOp funcOp, const clang::FunctionDecl 
*funcDecl);
+
   cir::FuncOp createRuntimeFunction(cir::FuncType ty, llvm::StringRef name,
                                     mlir::NamedAttrList extraAttrs = {},
                                     bool isLocal = false,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 71dda584e9fcc..4ee19634f5822 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1228,8 +1228,7 @@ printCallCommon(mlir::Operation *op, 
mlir::FlatSymbolRefAttr calleeSym,
       CIRDialect::getSideEffectAttrName(),
       CIRDialect::getOperandSegmentSizesAttrName(),
       llvm::StringRef("res_attrs"),
-      llvm::StringRef("arg_attrs"),
-      CIRDialect::getAstAttrName()};
+      llvm::StringRef("arg_attrs")};
   printer.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
   printer << " : ";
   if (calleeSym || !argAttrs) {
@@ -2560,6 +2559,23 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
       return failure();
   }
 
+  // Parse FuncInfo attribute
+  if (parser.parseOptionalKeyword("func_info").succeeded()) {
+    if (parser.parseLess().failed())
+      return failure();
+
+    mlir::Attribute attr;
+    if (parser.parseAttribute(attr).failed())
+      return failure();
+    if (!mlir::isa<cir::FuncInfoAttr>(attr))
+      return parser.emitError(parser.getCurrentLocation(),
+                              "expected a function information attribute");
+    state.addAttribute(getFuncInfoAttrName(state.name), attr);
+
+    if (parser.parseGreater().failed())
+      return failure();
+  }
+
   if (parseGlobalDtorCtor("global_ctor", [&](std::optional<int> priority) {
         mlir::IntegerAttr globalCtorPriorityAttr =
             builder.getI32IntegerAttr(priority.value_or(65535));
@@ -2757,6 +2773,12 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
     p << '>';
   }
 
+  if (cir::FuncInfoAttr funcInfo = getFuncInfoAttr()) {
+    p << " func_info<";
+    p.printAttribute(funcInfo);
+    p << '>';
+  }
+
   if (auto globalCtorPriority = getGlobalCtorPriority()) {
     p << " global_ctor";
     if (globalCtorPriority.value() != 65535)
diff --git a/clang/test/CIR/CodeGen/call-ast-attr.c 
b/clang/test/CIR/CodeGen/call-ast-attr.c
deleted file mode 100644
index 4037b86f0b566..0000000000000
--- a/clang/test/CIR/CodeGen/call-ast-attr.c
+++ /dev/null
@@ -1,13 +0,0 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir 
-mlir-print-op-generic %s -o - | FileCheck %s --check-prefix=GENERIC
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
- | FileCheck %s --check-prefix=DEFAULT
-
-void callee(void);
-
-void caller(void) { callee(); }
-
-// Calls carry the AST call expression, visible in the generic form.
-// GENERIC: "cir.call"(){{.*}}ast = #cir.call.expr.ast
-
-// The attribute is elided from the pretty form.
-// DEFAULT: cir.call @callee()
-// DEFAULT-NOT: #cir.call.expr.ast
diff --git a/clang/test/CIR/CodeGen/call-ast-attr.cpp 
b/clang/test/CIR/CodeGen/call-ast-attr.cpp
deleted file mode 100644
index 402c7c1fff689..0000000000000
--- a/clang/test/CIR/CodeGen/call-ast-attr.cpp
+++ /dev/null
@@ -1,15 +0,0 @@
-// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-cir -mmlir -mlir-print-op-generic %s -o - | FileCheck %s
-
-struct S {
-  void method();
-};
-
-// Member calls carry the AST call expression as well.
-void member_call(S &s) { s.method(); }
-// CHECK: "cir.call"
-// CHECK-SAME: ast = #cir.call.expr.ast
-
-// Calls through pointers to member functions carry it too.
-void member_ptr_call(S &s, void (S::*fn)()) { (s.*fn)(); }
-// CHECK: "cir.call"
-// CHECK-SAME: ast = #cir.call.expr.ast
diff --git a/clang/test/CIR/CodeGen/func-info-attr.c 
b/clang/test/CIR/CodeGen/func-info-attr.c
new file mode 100644
index 0000000000000..41b7e0f846086
--- /dev/null
+++ b/clang/test/CIR/CodeGen/func-info-attr.c
@@ -0,0 +1,10 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
- | FileCheck %s
+
+void callee(void);
+
+void caller(void) { callee(); }
+
+// Plain C functions carry no identity attribute. Their symbol already is
+// the plain name, since C names have no mangling.
+// CHECK: cir.func{{.*}} @caller
+// CHECK-NOT: func_info
diff --git a/clang/test/CIR/CodeGen/func-info-attr.cpp 
b/clang/test/CIR/CodeGen/func-info-attr.cpp
new file mode 100644
index 0000000000000..736f980789b0a
--- /dev/null
+++ b/clang/test/CIR/CodeGen/func-info-attr.cpp
@@ -0,0 +1,45 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck %s --input-file=%t.cir
+// RUN: FileCheck %s --input-file=%t.cir --check-prefix=NEG
+
+namespace std {
+int *find(int *first, int *last, int value);
+inline namespace __1 {
+void sort(int *first, int *last);
+}
+struct container {
+  int *begin();
+};
+struct traits {
+  static int *locate(int *first);
+};
+}
+
+int *std_call(int *first, int *last) { return std::find(first, last, 42); }
+// Functions declared in the std namespace carry their source identity.
+// CHECK-DAG: cir.func{{.*}} @_ZSt4find{{.*}} func_info<#cir.func_info<name = 
"find", in_std_namespace = true>>
+
+void inline_ns_call(int *first, int *last) { std::sort(first, last); }
+// Inline namespaces, like the one in libc++, are looked through.
+// CHECK-DAG: cir.func{{.*}} @_ZNSt3__14sort{{.*}} 
func_info<#cir.func_info<name = "sort", in_std_namespace = true>>
+
+int *member_call(std::container &c) { return c.begin(); }
+// Member functions of std records are marked as instance methods.
+// CHECK-DAG: cir.func{{.*}} @_ZNSt9container5begin{{.*}} 
func_info<#cir.func_info<name = "begin", in_std_namespace = true, 
instance_method = true>>
+
+struct S {
+  void method();
+  void operator()();
+};
+
+void plain_calls(S &s) {
+  s.method();
+  s();
+}
+int *static_member_call(int *first) { return std::traits::locate(first); }
+// Functions outside the std namespace carry nothing, and neither do
+// functions without a plain name, such as operators, nor static member
+// functions, even inside std.
+// NEG-NOT: name = "method"
+// NEG-NOT: name = "operator
+// NEG-NOT: name = "locate"
diff --git a/clang/test/CIR/IR/func-info-attr.cir 
b/clang/test/CIR/IR/func-info-attr.cir
new file mode 100644
index 0000000000000..902616014bd9b
--- /dev/null
+++ b/clang/test/CIR/IR/func-info-attr.cir
@@ -0,0 +1,11 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+module {
+
+cir.func private @_ZSt4findv() func_info<#cir.func_info<name = "find", 
in_std_namespace = true>>
+// CHECK: cir.func private @_ZSt4findv() func_info<#cir.func_info<name = 
"find", in_std_namespace = true>>
+
+cir.func private @_ZNSt9container5beginEv() func_info<#cir.func_info<name = 
"begin", in_std_namespace = true, instance_method = true>>
+// CHECK: cir.func private @_ZNSt9container5beginEv() 
func_info<#cir.func_info<name = "begin", in_std_namespace = true, 
instance_method = true>>
+
+}

>From fd3b0e089bcd2305b751f4f4fb7ac860cfa914cd Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Mon, 6 Jul 2026 15:42:49 +0000
Subject: [PATCH 3/9] [CIR] Rephrase the func_info documentation

The description now explains the purpose of the attribute in general
terms. It also spells out how std membership behaves with the inline
versioning namespace of libc++ and with using declarations and
namespace aliases, since membership follows the declaration rather
than the spelling at a use site.
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 37 +++++++++++--------
 1 file changed, 22 insertions(+), 15 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 2a67053ca3a8c..6056ace48dfd3 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1305,21 +1305,28 @@ def CIR_CXXSpecialMemberAttr : AnyAttrOf<[
 def CIR_FuncInfoAttr : CIR_Attr<"FuncInfo", "func_info"> {
   let summary = "Holds the source level identity of a function";
   let description = [{
-    Carries facts about the source declaration a function was emitted from,
-    precomputed during CIRGen while the AST is available. Passes that want
-    to reason about what the source calls read this attribute instead of
-    the AST.
-
-    The `name` parameter holds the plain function name as written in the
-    source, without mangling, qualification, or template arguments.
-    Functions without a plain identifier, for example overloaded operators
-    and constructors, carry no attribute, and neither do static member
-    functions, so a free function and a static member with the same name
-    stay apart. The `in_std_namespace` parameter is true when the function
-    is declared directly in the `std` namespace, looking through inline
-    namespaces. For a member function the enclosing record decides. The
-    `instance_method` parameter is true for a member function that is not
-    static.
+    Describes the source level entity a function represents, so that
+    transformations can recognize calls to well known library functions
+    without decoding mangled symbol names.
+
+    The `name` parameter holds the function name as written in the source,
+    without qualification, template arguments, or mangling.
+
+    The `in_std_namespace` parameter is true when the function belongs to
+    the `std` namespace. Membership follows the declaration rather than
+    the spelling at any use site. Inline namespaces, such as the
+    versioning namespace of libc++, are looked through, so a function
+    declared in `std::__1` counts as a member of `std`. Using declarations
+    and namespace aliases do not move a declaration, so they do not affect
+    the value either. For a member function the enclosing record decides.
+
+    The `instance_method` parameter is true for a member function that is
+    not static.
+
+    Functions without a plain identifier, such as overloaded operators and
+    constructors, carry no attribute, and neither do static member
+    functions, which keeps a static member apart from a free function of
+    the same name.
 
     Example:
     ```

>From 57895cf353c33e6413a486c90bc42190d5928c58 Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Mon, 6 Jul 2026 17:39:28 +0000
Subject: [PATCH 4/9] [CIR] Clarify who carries the attribute in
 setFuncInfoAttr

The comment above the static member guard spoke mostly about member
functions, which made the code read as if it only handled methods. It
now states that free functions and instance methods carry the
attribute and that only static member functions are skipped.
---
 clang/lib/CIR/CodeGen/CIRGenModule.cpp | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 5e0ad33173468..26f8ff75aefb4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3533,13 +3533,15 @@ void CIRGenModule::setFuncInfoAttr(cir::FuncOp funcOp,
   if (!funcDecl || !funcDecl->getIdentifier())
     return;
 
-  // A member function lives inside its record, so the record decides
-  // whether the function belongs to the std namespace. Static member
-  // functions carry no attribute, so that a static member like
+  // Free functions and instance methods carry the attribute. Only static
+  // member functions are skipped, so that a static member like
   // char_traits::find can never be mistaken for the free std::find.
   const auto *method = dyn_cast<CXXMethodDecl>(funcDecl);
   if (method && method->isStatic())
     return;
+
+  // A member function lives inside its record, so the record decides
+  // whether the function belongs to the std namespace.
   bool inStdNamespace = method ? method->getParent()->isInStdNamespace()
                                : funcDecl->isInStdNamespace();
 

>From 4a91cab1022a99e6da17644b33fc54060bb55c58 Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Mon, 6 Jul 2026 21:48:29 +0000
Subject: [PATCH 5/9] [CIR] Generalize the special member attribute into
 func_info and add a callee resolver

The cxx_special_member slot on cir.func becomes a general slot named
func_info, so facts about a function that are not special member facts
can join later without redesign.

Nothing about the special member forms changes. The attribute stays the
same union of the constructor, destructor, and assignment forms. Each
form keeps its shape and its embedded record type, and the CXX ABI
lowering still replaces that type with the converted one. The FuncOp
helpers keep their names, so passes that read the special member facts
do not change.

The printed keyword on cir.func changes from special_member to
func_info, so the old spelling no longer parses. The existing tests
update their check lines with no other change.

Calls gain a resolver named resolveCallee that returns the function a
direct call targets, so a pass standing at a call can read the facts
recorded on its callee. It returns null when the call is indirect, when
the callee symbol does not resolve, and when the target is not a
cir.func. A second form takes a symbol table collection so a pass can
reuse its cached lookup. Both cir.call and cir.try_call get the resolver
through one shared implementation, and a unit test drives all of these
cases.
---
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  |  59 ++--------
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  14 ++-
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |  40 +------
 clang/lib/CIR/CodeGen/CIRGenModule.h          |   4 -
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 110 ++++++++++--------
 .../CIR/CodeGen/array-init-loop-exprs.cpp     |   4 +-
 clang/test/CIR/CodeGen/ctor-try-body.cpp      |   2 +-
 .../CIR/CodeGen/cxx-special-member-attr.cpp   |  16 +--
 clang/test/CIR/CodeGen/func-info-attr.c       |  10 --
 clang/test/CIR/CodeGen/func-info-attr.cpp     |  45 -------
 clang/test/CIR/CodeGen/inherited-ctors.cpp    |  10 +-
 clang/test/CIR/IR/func-info-attr.cir          |  11 --
 clang/test/CIR/IR/func.cir                    |  16 +--
 clang/test/CIR/IR/invalid-func.cir            |  11 +-
 .../CIR/Transforms/cxx-abi-lowering-attrs.cir |  24 ++--
 clang/unittests/CIR/CMakeLists.txt            |   1 +
 clang/unittests/CIR/CallOpTest.cpp            | 100 ++++++++++++++++
 17 files changed, 232 insertions(+), 245 deletions(-)
 delete mode 100644 clang/test/CIR/CodeGen/func-info-attr.c
 delete mode 100644 clang/test/CIR/CodeGen/func-info-attr.cpp
 delete mode 100644 clang/test/CIR/IR/func-info-attr.cir
 create mode 100644 clang/unittests/CIR/CallOpTest.cpp

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 6056ace48dfd3..e1344f25f6c88 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1292,60 +1292,19 @@ def CIR_CXXAssignAttr : CIR_Attr<"CXXAssign", 
"cxx_assign"> {
   }];
 }
 
-def CIR_CXXSpecialMemberAttr : AnyAttrOf<[
-  CIR_CXXCtorAttr,
-  CIR_CXXDtorAttr,
-  CIR_CXXAssignAttr
-]>;
-
 
//===----------------------------------------------------------------------===//
 // FuncInfoAttr
 
//===----------------------------------------------------------------------===//
 
-def CIR_FuncInfoAttr : CIR_Attr<"FuncInfo", "func_info"> {
-  let summary = "Holds the source level identity of a function";
-  let description = [{
-    Describes the source level entity a function represents, so that
-    transformations can recognize calls to well known library functions
-    without decoding mangled symbol names.
-
-    The `name` parameter holds the function name as written in the source,
-    without qualification, template arguments, or mangling.
-
-    The `in_std_namespace` parameter is true when the function belongs to
-    the `std` namespace. Membership follows the declaration rather than
-    the spelling at any use site. Inline namespaces, such as the
-    versioning namespace of libc++, are looked through, so a function
-    declared in `std::__1` counts as a member of `std`. Using declarations
-    and namespace aliases do not move a declaration, so they do not affect
-    the value either. For a member function the enclosing record decides.
-
-    The `instance_method` parameter is true for a member function that is
-    not static.
-
-    Functions without a plain identifier, such as overloaded operators and
-    constructors, carry no attribute, and neither do static member
-    functions, which keeps a static member apart from a free function of
-    the same name.
-
-    Example:
-    ```
-    #cir.func_info<name = "find", in_std_namespace = true>
-    ```
-  }];
-
-  let parameters = (ins
-    "mlir::StringAttr":$name,
-    DefaultValuedParameter<"bool", "false">:$in_std_namespace,
-    DefaultValuedParameter<"bool", "false">:$instance_method
-  );
-
-  let assemblyFormat = [{
-    `<` struct($name, $in_std_namespace, $instance_method) `>`
-  }];
-
-  let canHaveIllegalCXXABIType = 0;
-}
+// The attributes accepted in the optional func_info slot on cir.func. Each
+// form records one kind of precomputed fact about the function. The special
+// member forms embed the record type they operate on, and the CXX ABI
+// lowering replaces them with forms holding the converted type.
+def CIR_FuncInfoAttr : AnyAttrOf<[
+  CIR_CXXCtorAttr,
+  CIR_CXXDtorAttr,
+  CIR_CXXAssignAttr
+], "function information attribute">;
 
 
//===----------------------------------------------------------------------===//
 // BitfieldInfoAttr
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 6d6b46b45b8be..e6704ddb8110b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4024,7 +4024,6 @@ def CIR_FuncOp : CIR_Op<"func", [
     OptionalAttr<FlatSymbolRefAttr>:$personality,
     CIR_OptionalPriorityAttr:$global_ctor_priority,
     CIR_OptionalPriorityAttr:$global_dtor_priority,
-    OptionalAttr<CIR_CXXSpecialMemberAttr>:$cxx_special_member,
     OptionalAttr<CIR_FuncInfoAttr>:$func_info,
     OptionalAttr<CIR_AnnotationArrayAttr>:$annotations
   );
@@ -4288,6 +4287,19 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
     bool isIndirect() { return !getCallee(); }
     mlir::Value getIndirectCall();
 
+    /// Returns the function this call resolves to, so a pass standing at
+    /// a call can read the facts recorded on its callee, such as the
+    /// func_info attribute. Returns null when the call is indirect, when
+    /// the callee symbol does not resolve, or when it resolves to
+    /// something other than a cir.func. Walks the enclosing symbol tables
+    /// on every use, so passes that resolve many calls should use
+    /// resolveCalleeInTable instead.
+    cir::FuncOp resolveCallee();
+
+    /// The same resolution through a symbol table collection, so the
+    /// lookup uses its cache.
+    cir::FuncOp resolveCalleeInTable(mlir::SymbolTableCollection *symbolTable);
+
     void setArg(unsigned index, mlir::Value value) {
       if (!isIndirect()) {
         setOperand(index, value);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 26f8ff75aefb4..d4d519690cac5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -3446,9 +3446,6 @@ CIRGenModule::createCIRFunction(mlir::Location loc, 
StringRef name,
     // Mark C++ special member functions (Constructor, Destructor etc.)
     setCXXSpecialMemberAttr(func, funcDecl);
 
-    // Record the source level identity of the function.
-    setFuncInfoAttr(func, funcDecl);
-
     if (!cgf)
       theModule.push_back(func);
 
@@ -3500,7 +3497,7 @@ void CIRGenModule::setCXXSpecialMemberAttr(
     auto cxxDtor = cir::CXXDtorAttr::get(
         convertType(getASTContext().getCanonicalTagType(dtor->getParent())),
         dtor->isTrivial());
-    funcOp.setCxxSpecialMemberAttr(cxxDtor);
+    funcOp.setFuncInfoAttr(cxxDtor);
     return;
   }
 
@@ -3509,7 +3506,7 @@ void CIRGenModule::setCXXSpecialMemberAttr(
     auto cxxCtor = cir::CXXCtorAttr::get(
         convertType(getASTContext().getCanonicalTagType(ctor->getParent())),
         kind, ctor->isTrivial());
-    funcOp.setCxxSpecialMemberAttr(cxxCtor);
+    funcOp.setFuncInfoAttr(cxxCtor);
     return;
   }
 
@@ -3520,42 +3517,11 @@ void CIRGenModule::setCXXSpecialMemberAttr(
     auto cxxAssign = cir::CXXAssignAttr::get(
         convertType(getASTContext().getCanonicalTagType(method->getParent())),
         assignKind, method->isTrivial());
-    funcOp.setCxxSpecialMemberAttr(cxxAssign);
+    funcOp.setFuncInfoAttr(cxxAssign);
     return;
   }
 }
 
-void CIRGenModule::setFuncInfoAttr(cir::FuncOp funcOp,
-                                   const clang::FunctionDecl *funcDecl) {
-  // Functions without a plain identifier, for example overloaded operators
-  // and constructors, carry no information, since no pass can match a
-  // function it cannot name.
-  if (!funcDecl || !funcDecl->getIdentifier())
-    return;
-
-  // Free functions and instance methods carry the attribute. Only static
-  // member functions are skipped, so that a static member like
-  // char_traits::find can never be mistaken for the free std::find.
-  const auto *method = dyn_cast<CXXMethodDecl>(funcDecl);
-  if (method && method->isStatic())
-    return;
-
-  // A member function lives inside its record, so the record decides
-  // whether the function belongs to the std namespace.
-  bool inStdNamespace = method ? method->getParent()->isInStdNamespace()
-                               : funcDecl->isInStdNamespace();
-
-  // Only functions in the std namespace carry the attribute for now. Those
-  // are the ones upcoming passes look for, and plain C library functions
-  // can be matched through their symbol, since C names have no mangling.
-  if (!inStdNamespace)
-    return;
-
-  funcOp.setFuncInfoAttr(cir::FuncInfoAttr::get(
-      &getMLIRContext(), builder.getStringAttr(funcDecl->getName()),
-      inStdNamespace, method && method->isInstance()));
-}
-
 static void setWindowsItaniumDLLImport(CIRGenModule &cgm, bool isLocal,
                                        cir::FuncOp funcOp, StringRef name) {
   // In Windows Itanium environments, try to mark runtime functions
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h 
b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 8d5f955cbb261..144f8c7b9f3e7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -802,10 +802,6 @@ class CIRGenModule : public CIRGenTypeCache {
   void setCXXSpecialMemberAttr(cir::FuncOp funcOp,
                                const clang::FunctionDecl *funcDecl);
 
-  /// Record the source level identity of the function (plain name, std
-  /// namespace membership) so passes can reason about it without the AST.
-  void setFuncInfoAttr(cir::FuncOp funcOp, const clang::FunctionDecl 
*funcDecl);
-
   cir::FuncOp createRuntimeFunction(cir::FuncType ty, llvm::StringRef name,
                                     mlir::NamedAttrList extraAttrs = {},
                                     bool isLocal = false,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 4ee19634f5822..0ffb48c3543cd 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1051,6 +1051,27 @@ unsigned cir::CallOp::getNumArgOperands() {
   return this->getOperation()->getNumOperands();
 }
 
+static cir::FuncOp resolveCalleeImpl(mlir::Operation *op,
+                                     mlir::SymbolTableCollection *symbolTable) 
{
+  auto callee = op->getAttrOfType<mlir::FlatSymbolRefAttr>(
+      CIRDialect::getCalleeAttrName());
+  if (!callee)
+    return {};
+
+  if (symbolTable)
+    return symbolTable->lookupNearestSymbolFrom<cir::FuncOp>(op, callee);
+  return mlir::SymbolTable::lookupNearestSymbolFrom<cir::FuncOp>(op, callee);
+}
+
+cir::FuncOp cir::CallOp::resolveCallee() {
+  return resolveCalleeImpl(*this, /*symbolTable=*/nullptr);
+}
+
+cir::FuncOp
+cir::CallOp::resolveCalleeInTable(mlir::SymbolTableCollection *symbolTable) {
+  return resolveCalleeImpl(*this, symbolTable);
+}
+
 static mlir::ParseResult
 parseTryCallDestinations(mlir::OpAsmParser &parser,
                          mlir::OperationState &result) {
@@ -1362,6 +1383,15 @@ unsigned cir::TryCallOp::getNumArgOperands() {
   return this->getOperation()->getNumOperands();
 }
 
+cir::FuncOp cir::TryCallOp::resolveCallee() {
+  return resolveCalleeImpl(*this, /*symbolTable=*/nullptr);
+}
+
+cir::FuncOp
+cir::TryCallOp::resolveCalleeInTable(mlir::SymbolTableCollection *symbolTable) 
{
+  return resolveCalleeImpl(*this, symbolTable);
+}
+
 LogicalResult
 cir::TryCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   return verifyCallCommInSymbolUses(*this, symbolTable);
@@ -2386,7 +2416,7 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
   mlir::StringAttr comdatNameAttr = getComdatAttrName(state.name);
   mlir::StringAttr visNameAttr = getSymVisibilityAttrName(state.name);
   mlir::StringAttr dsoLocalNameAttr = getDsoLocalAttrName(state.name);
-  mlir::StringAttr specialMemberAttr = getCxxSpecialMemberAttrName(state.name);
+  mlir::StringAttr funcInfoNameAttr = getFuncInfoAttrName(state.name);
 
   if (::mlir::succeeded(parser.parseOptionalKeyword(builtinNameAttr.strref())))
     state.addAttribute(builtinNameAttr, parser.getBuilder().getUnitAttr());
@@ -2520,6 +2550,24 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
   state.addAttribute(callConvNameAttr,
                      cir::CallingConvAttr::get(parser.getContext(), callConv));
 
+  if (parser.parseOptionalKeyword("func_info").succeeded()) {
+    if (parser.parseLess().failed())
+      return failure();
+
+    llvm::SMLoc attrLoc = parser.getCurrentLocation();
+    mlir::Attribute attr;
+    if (parser.parseAttribute(attr).failed())
+      return failure();
+    if (!mlir::isa<cir::CXXCtorAttr, cir::CXXDtorAttr, cir::CXXAssignAttr>(
+            attr))
+      return parser.emitError(attrLoc, "expected a function info attribute, 
got ")
+             << attr;
+    state.addAttribute(funcInfoNameAttr, attr);
+
+    if (parser.parseGreater().failed())
+      return failure();
+  }
+
   auto parseGlobalDtorCtor =
       [&](StringRef keyword,
           llvm::function_ref<void(std::optional<int> prio)> createAttr)
@@ -2541,41 +2589,6 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
     return success();
   };
 
-  // Parse CXXSpecialMember attribute
-  if (parser.parseOptionalKeyword("special_member").succeeded()) {
-    if (parser.parseLess().failed())
-      return failure();
-
-    mlir::Attribute attr;
-    if (parser.parseAttribute(attr).failed())
-      return failure();
-    if (!mlir::isa<cir::CXXCtorAttr, cir::CXXDtorAttr, cir::CXXAssignAttr>(
-            attr))
-      return parser.emitError(parser.getCurrentLocation(),
-                              "expected a C++ special member attribute");
-    state.addAttribute(specialMemberAttr, attr);
-
-    if (parser.parseGreater().failed())
-      return failure();
-  }
-
-  // Parse FuncInfo attribute
-  if (parser.parseOptionalKeyword("func_info").succeeded()) {
-    if (parser.parseLess().failed())
-      return failure();
-
-    mlir::Attribute attr;
-    if (parser.parseAttribute(attr).failed())
-      return failure();
-    if (!mlir::isa<cir::FuncInfoAttr>(attr))
-      return parser.emitError(parser.getCurrentLocation(),
-                              "expected a function information attribute");
-    state.addAttribute(getFuncInfoAttrName(state.name), attr);
-
-    if (parser.parseGreater().failed())
-      return failure();
-  }
-
   if (parseGlobalDtorCtor("global_ctor", [&](std::optional<int> priority) {
         mlir::IntegerAttr globalCtorPriorityAttr =
             builder.getI32IntegerAttr(priority.value_or(65535));
@@ -2657,26 +2670,29 @@ bool cir::FuncOp::isDeclaration() {
 }
 
 bool cir::FuncOp::isCXXSpecialMemberFunction() {
-  return getCxxSpecialMemberAttr() != nullptr;
+  // The func_info union can grow forms that are not special members, so the
+  // check names the concrete forms rather than testing for presence.
+  mlir::Attribute attr = getFuncInfoAttr();
+  return attr && mlir::isa<CXXCtorAttr, CXXDtorAttr, CXXAssignAttr>(attr);
 }
 
 bool cir::FuncOp::isCxxConstructor() {
-  auto attr = getCxxSpecialMemberAttr();
+  auto attr = getFuncInfoAttr();
   return attr && dyn_cast<CXXCtorAttr>(attr);
 }
 
 bool cir::FuncOp::isCxxDestructor() {
-  auto attr = getCxxSpecialMemberAttr();
+  auto attr = getFuncInfoAttr();
   return attr && dyn_cast<CXXDtorAttr>(attr);
 }
 
 bool cir::FuncOp::isCxxSpecialAssignment() {
-  auto attr = getCxxSpecialMemberAttr();
+  auto attr = getFuncInfoAttr();
   return attr && dyn_cast<CXXAssignAttr>(attr);
 }
 
 std::optional<CtorKind> cir::FuncOp::getCxxConstructorKind() {
-  mlir::Attribute attr = getCxxSpecialMemberAttr();
+  mlir::Attribute attr = getFuncInfoAttr();
   if (attr) {
     if (auto ctor = dyn_cast<CXXCtorAttr>(attr))
       return ctor.getCtorKind();
@@ -2685,7 +2701,7 @@ std::optional<CtorKind> 
cir::FuncOp::getCxxConstructorKind() {
 }
 
 std::optional<AssignKind> cir::FuncOp::getCxxSpecialAssignKind() {
-  mlir::Attribute attr = getCxxSpecialMemberAttr();
+  mlir::Attribute attr = getFuncInfoAttr();
   if (attr) {
     if (auto assign = dyn_cast<CXXAssignAttr>(attr))
       return assign.getAssignKind();
@@ -2694,7 +2710,7 @@ std::optional<AssignKind> 
cir::FuncOp::getCxxSpecialAssignKind() {
 }
 
 bool cir::FuncOp::isCxxTrivialMemberFunction() {
-  mlir::Attribute attr = getCxxSpecialMemberAttr();
+  mlir::Attribute attr = getFuncInfoAttr();
   if (attr) {
     if (auto ctor = dyn_cast<CXXCtorAttr>(attr))
       return ctor.getIsTrivial();
@@ -2767,13 +2783,7 @@ void cir::FuncOp::print(OpAsmPrinter &p) {
     p << ")";
   }
 
-  if (auto specialMemberAttr = getCxxSpecialMember()) {
-    p << " special_member<";
-    p.printAttribute(*specialMemberAttr);
-    p << '>';
-  }
-
-  if (cir::FuncInfoAttr funcInfo = getFuncInfoAttr()) {
+  if (mlir::Attribute funcInfo = getFuncInfoAttr()) {
     p << " func_info<";
     p.printAttribute(funcInfo);
     p << '>';
diff --git a/clang/test/CIR/CodeGen/array-init-loop-exprs.cpp 
b/clang/test/CIR/CodeGen/array-init-loop-exprs.cpp
index b8fc4b17ea38d..25ffaca4a6807 100644
--- a/clang/test/CIR/CodeGen/array-init-loop-exprs.cpp
+++ b/clang/test/CIR/CodeGen/array-init-loop-exprs.cpp
@@ -17,7 +17,7 @@ struct HasNonTrivialArray {
     NonTrivial arr[3];
 };
 
-// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN18HasNonTrivialArrayC2ERKS_({{.*}}) 
special_member<#cir.cxx_ctor<!rec_HasNonTrivialArray, copy>> 
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN18HasNonTrivialArrayC2ERKS_({{.*}}) 
func_info<#cir.cxx_ctor<!rec_HasNonTrivialArray, copy>> 
 // CIR: %[[THIS_ALLOCA:.*]] = cir.alloca "this" {{.*}} init : 
!cir.ptr<!cir.ptr<!rec_HasNonTrivialArray>>
 // CIR: %[[RHS_ALLOCA:.*]] = cir.alloca "" {{.*}} init const : 
!cir.ptr<!cir.ptr<!rec_HasNonTrivialArray>>
 // CIR: %[[ITR_ALLOCA:.*]] = cir.alloca "arrayinit.temp" {{.*}} : 
!cir.ptr<!cir.ptr<!rec_NonTrivial>>
@@ -137,7 +137,7 @@ struct HasMultiDimArray {
     NonTrivial arr[2][3][4];
 };
 
-// CIR-LABEL: cir.func {{.*}}@_ZN16HasMultiDimArrayC2ERKS_({{.*}}) 
special_member<#cir.cxx_ctor<!rec_HasMultiDimArray, copy>> 
+// CIR-LABEL: cir.func {{.*}}@_ZN16HasMultiDimArrayC2ERKS_({{.*}}) 
func_info<#cir.cxx_ctor<!rec_HasMultiDimArray, copy>> 
 // CIR: %[[THIS_ALLOCA:.*]] = cir.alloca "this" {{.*}} init : 
!cir.ptr<!cir.ptr<!rec_HasMultiDimArray>>
 // CIR: %[[RHS_ALLOCA:.*]] = cir.alloca "" {{.*}} init const : 
!cir.ptr<!cir.ptr<!rec_HasMultiDimArray>>
 // CIR: %[[ITR1_ALLOCA:.*]] = cir.alloca "arrayinit.temp" {{.*}} : 
!cir.ptr<!cir.ptr<!cir.array<!cir.array<!rec_NonTrivial x 4> x 3>>>
diff --git a/clang/test/CIR/CodeGen/ctor-try-body.cpp 
b/clang/test/CIR/CodeGen/ctor-try-body.cpp
index 503be3f273c70..21967ceebfc10 100644
--- a/clang/test/CIR/CodeGen/ctor-try-body.cpp
+++ b/clang/test/CIR/CodeGen/ctor-try-body.cpp
@@ -30,7 +30,7 @@ struct HasThings : Base {
     side_effect2();
   }
 
-// CIR: cir.func {{.*}}@_ZN9HasThingsC2ERK4Ctor(%[[THIS_ARG:.*]]: 
!cir.ptr<!rec_HasThings> {{.*}}, %[[C_ARG:.*]]: !cir.ptr<!rec_Ctor> {{.*}}) 
{{.*}}special_member<#cir.cxx_ctor<!rec_HasThings, custom>>{{.*}} {
+// CIR: cir.func {{.*}}@_ZN9HasThingsC2ERK4Ctor(%[[THIS_ARG:.*]]: 
!cir.ptr<!rec_HasThings> {{.*}}, %[[C_ARG:.*]]: !cir.ptr<!rec_Ctor> {{.*}}) 
{{.*}}func_info<#cir.cxx_ctor<!rec_HasThings, custom>>{{.*}} {
 // CIR-NEXT:  %[[THIS_ALLOC:.*]] = cir.alloca "this" {{.*}} init : 
!cir.ptr<!cir.ptr<!rec_HasThings>>
 // CIR-NEXT:  %[[C_ALLOC:.*]] = cir.alloca "c" {{.*}} init const : 
!cir.ptr<!cir.ptr<!rec_Ctor>>
 // CIR-NEXT:  cir.store %[[THIS_ARG]], %[[THIS_ALLOC]] : 
!cir.ptr<!rec_HasThings>, !cir.ptr<!cir.ptr<!rec_HasThings>>
diff --git a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp 
b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
index bfca59db44fdf..6a43ceeb97b48 100644
--- a/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
+++ b/clang/test/CIR/CodeGen/cxx-special-member-attr.cpp
@@ -31,8 +31,8 @@ struct Foo {
 };
 
 // Trivial copy/move assignment operator definitions appear at module level.
-// CIR: @_ZN4FlubaSERKS_(%arg0: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Flub>{{.*}}) special_member<#cir.cxx_assign<!rec_Flub, copy, 
trivial true>>
-// CIR: @_ZN4FlubaSEOS_(%arg0: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Flub>{{.*}}) special_member<#cir.cxx_assign<!rec_Flub, move, 
trivial true>>
+// CIR: @_ZN4FlubaSERKS_(%arg0: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Flub>{{.*}}) func_info<#cir.cxx_assign<!rec_Flub, copy, trivial 
true>>
+// CIR: @_ZN4FlubaSEOS_(%arg0: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Flub> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Flub>{{.*}}) func_info<#cir.cxx_assign<!rec_Flub, move, trivial 
true>>
 
 void trivial_func() {
   Flub f1{};
@@ -50,18 +50,18 @@ void trivial_func() {
 
 void non_trivial_func() {
   Foo f1{};
-  // CIR: @_ZN3FooC2Ev(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) 
special_member<#cir.cxx_ctor<!rec_Foo, default>>
+  // CIR: @_ZN3FooC2Ev(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) 
func_info<#cir.cxx_ctor<!rec_Foo, default>>
 
   Foo f2 = f1;
-  // CIR: @_ZN3FooC2ERKS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) 
special_member<#cir.cxx_ctor<!rec_Foo, copy>>
+  // CIR: @_ZN3FooC2ERKS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) 
func_info<#cir.cxx_ctor<!rec_Foo, copy>>
 
   Foo f3 = static_cast<Foo&&>(f1);
-  // CIR: @_ZN3FooC2EOS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) 
special_member<#cir.cxx_ctor<!rec_Foo, move>>
+  // CIR: @_ZN3FooC2EOS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) 
func_info<#cir.cxx_ctor<!rec_Foo, move>>
 
   f2 = f1;
-  // CIR: @_ZN3FooaSERKS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Foo>{{.*}}) special_member<#cir.cxx_assign<!rec_Foo, copy>>
+  // CIR: @_ZN3FooaSERKS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Foo>{{.*}}) func_info<#cir.cxx_assign<!rec_Foo, copy>>
 
   f1 = static_cast<Foo&&>(f3);
-  // CIR: @_ZN3FooaSEOS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Foo>{{.*}}) special_member<#cir.cxx_assign<!rec_Foo, move>>
-  // CIR: @_ZN3FooD1Ev(!cir.ptr<!rec_Foo> {{.*}}) 
special_member<#cir.cxx_dtor<!rec_Foo>>
+  // CIR: @_ZN3FooaSEOS_(%arg0: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} 
loc({{.*}}), %arg1: !cir.ptr<!rec_Foo> {{[{][^}]*[}]}} loc({{.*}})) -> 
(!cir.ptr<!rec_Foo>{{.*}}) func_info<#cir.cxx_assign<!rec_Foo, move>>
+  // CIR: @_ZN3FooD1Ev(!cir.ptr<!rec_Foo> {{.*}}) 
func_info<#cir.cxx_dtor<!rec_Foo>>
 }
diff --git a/clang/test/CIR/CodeGen/func-info-attr.c 
b/clang/test/CIR/CodeGen/func-info-attr.c
deleted file mode 100644
index 41b7e0f846086..0000000000000
--- a/clang/test/CIR/CodeGen/func-info-attr.c
+++ /dev/null
@@ -1,10 +0,0 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
- | FileCheck %s
-
-void callee(void);
-
-void caller(void) { callee(); }
-
-// Plain C functions carry no identity attribute. Their symbol already is
-// the plain name, since C names have no mangling.
-// CHECK: cir.func{{.*}} @caller
-// CHECK-NOT: func_info
diff --git a/clang/test/CIR/CodeGen/func-info-attr.cpp 
b/clang/test/CIR/CodeGen/func-info-attr.cpp
deleted file mode 100644
index 736f980789b0a..0000000000000
--- a/clang/test/CIR/CodeGen/func-info-attr.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-emit-cir %s -o %t.cir
-// RUN: FileCheck %s --input-file=%t.cir
-// RUN: FileCheck %s --input-file=%t.cir --check-prefix=NEG
-
-namespace std {
-int *find(int *first, int *last, int value);
-inline namespace __1 {
-void sort(int *first, int *last);
-}
-struct container {
-  int *begin();
-};
-struct traits {
-  static int *locate(int *first);
-};
-}
-
-int *std_call(int *first, int *last) { return std::find(first, last, 42); }
-// Functions declared in the std namespace carry their source identity.
-// CHECK-DAG: cir.func{{.*}} @_ZSt4find{{.*}} func_info<#cir.func_info<name = 
"find", in_std_namespace = true>>
-
-void inline_ns_call(int *first, int *last) { std::sort(first, last); }
-// Inline namespaces, like the one in libc++, are looked through.
-// CHECK-DAG: cir.func{{.*}} @_ZNSt3__14sort{{.*}} 
func_info<#cir.func_info<name = "sort", in_std_namespace = true>>
-
-int *member_call(std::container &c) { return c.begin(); }
-// Member functions of std records are marked as instance methods.
-// CHECK-DAG: cir.func{{.*}} @_ZNSt9container5begin{{.*}} 
func_info<#cir.func_info<name = "begin", in_std_namespace = true, 
instance_method = true>>
-
-struct S {
-  void method();
-  void operator()();
-};
-
-void plain_calls(S &s) {
-  s.method();
-  s();
-}
-int *static_member_call(int *first) { return std::traits::locate(first); }
-// Functions outside the std namespace carry nothing, and neither do
-// functions without a plain name, such as operators, nor static member
-// functions, even inside std.
-// NEG-NOT: name = "method"
-// NEG-NOT: name = "operator
-// NEG-NOT: name = "locate"
diff --git a/clang/test/CIR/CodeGen/inherited-ctors.cpp 
b/clang/test/CIR/CodeGen/inherited-ctors.cpp
index 1a5a3eb6f8479..ce6df242df742 100644
--- a/clang/test/CIR/CodeGen/inherited-ctors.cpp
+++ b/clang/test/CIR/CodeGen/inherited-ctors.cpp
@@ -38,11 +38,11 @@ void fallsthrough() {
 
 
 // LLVM and OGCG check labels are identical other than the 1 difference called 
out (and ordering).
-// CIR-LABEL: cir.func private @_ZN4BaseC2Ei(!cir.ptr<!rec_Base>{{.*}}, 
!s32i{{.*}}) special_member<#cir.cxx_ctor<!rec_Base, custom>>
+// CIR-LABEL: cir.func private @_ZN4BaseC2Ei(!cir.ptr<!rec_Base>{{.*}}, 
!s32i{{.*}}) func_info<#cir.cxx_ctor<!rec_Base, custom>>
 // LLVM-LABEL: declare void @_ZN4BaseC2Ei(ptr {{.*}}, i32 {{.*}})
 //
 //
-// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN7DerivedCI24BaseEi(%{{.*}}: !cir.ptr<!rec_Derived>{{.*}}, %{{.*}}: 
!s32i{{.*}}) special_member<#cir.cxx_ctor<!rec_Derived, custom>>
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN7DerivedCI24BaseEi(%{{.*}}: !cir.ptr<!rec_Derived>{{.*}}, %{{.*}}: 
!s32i{{.*}}) func_info<#cir.cxx_ctor<!rec_Derived, custom>>
 // CIR: %[[THIS_ALLOCA:.*]] = cir.alloca "this" {{.*}} init : 
!cir.ptr<!cir.ptr<!rec_Derived>>
 // CIR: %[[INT_ALLOCA:.*]] = cir.alloca "" {{.*}} init : !cir.ptr<!s32i>
 // CIR: %[[THIS_LOAD:.*]] = cir.load %[[THIS_ALLOCA]] : 
!cir.ptr<!cir.ptr<!rec_Derived>>, !cir.ptr<!rec_Derived>
@@ -63,7 +63,7 @@ void fallsthrough() {
 // LLVM-LABEL: define dso_local void @_Z20emitDelegateCallArgsv()
 // LLVM: call void @_ZN7DerivedCI14BaseEi(ptr {{.*}}, i32 {{.*}}1)
 //
-// CIR-LABEL: cir.func private @_ZN4BaseC2Efz(!cir.ptr<!rec_Base>{{.*}}, 
!cir.float{{.*}}, ...) special_member<#cir.cxx_ctor<!rec_Base, custom>>
+// CIR-LABEL: cir.func private @_ZN4BaseC2Efz(!cir.ptr<!rec_Base>{{.*}}, 
!cir.float{{.*}}, ...) func_info<#cir.cxx_ctor<!rec_Base, custom>>
 // LLVM-LABEL: declare void @_ZN4BaseC2Efz(ptr {{.*}}, float {{.*}}, ...)
 //
 // CIR-LABEL: cir.func no_inline dso_local @_Z26cannotEmitDelegateCallArgsv()
@@ -80,7 +80,7 @@ void fallsthrough() {
 // LLVM: %[[TMP_LOAD:.*]] = load ptr, ptr %[[TMP_ALLOCA]]
 // LLVM: call void (ptr, float, ...) @_ZN4BaseC2Efz(ptr {{.*}}%[[TMP_LOAD]], 
float {{.*}}1.100000e+00, i32 {{.*}}2, double {{.*}}3.000000e+00)
 //
-// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN11VirtDerivedCI24BaseEi(%{{.*}}: !cir.ptr<!rec_VirtDerived> {{.*}}, 
%{{.*}}: !cir.ptr<!cir.ptr<!void>>{{.*}}) 
special_member<#cir.cxx_ctor<!rec_VirtDerived, custom>>
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN11VirtDerivedCI24BaseEi(%{{.*}}: !cir.ptr<!rec_VirtDerived> {{.*}}, 
%{{.*}}: !cir.ptr<!cir.ptr<!void>>{{.*}}) 
func_info<#cir.cxx_ctor<!rec_VirtDerived, custom>>
 // CIR: %[[THIS_ALLOCA:.*]] = cir.alloca "this" align(8) init : 
!cir.ptr<!cir.ptr<!rec_VirtDerived>>
 // CIR: %[[VTT_ALLOCA:.]] = cir.alloca "vtt" align(8) init : 
!cir.ptr<!cir.ptr<!cir.ptr<!void>>>
 // CIR: %[[THIS:.*]] = cir.load %[[THIS_ALLOCA]] : 
!cir.ptr<!cir.ptr<!rec_VirtDerived>>, !cir.ptr<!rec_VirtDerived>
@@ -100,7 +100,7 @@ void fallsthrough() {
 // LLVM: store ptr %[[VTT_ADDR_LOAD]], ptr %[[THIS]]
 //
 //
-// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN21VirtualDelegatingCtorC1Ei(%{{.*}}: !cir.ptr<!rec_VirtualDelegatingCtor> 
{{.*}}, %{{.*}}: !s32i {{.*}}) 
special_member<#cir.cxx_ctor<!rec_VirtualDelegatingCtor, custom>>
+// CIR-LABEL: cir.func no_inline comdat linkonce_odr 
@_ZN21VirtualDelegatingCtorC1Ei(%{{.*}}: !cir.ptr<!rec_VirtualDelegatingCtor> 
{{.*}}, %{{.*}}: !s32i {{.*}}) 
func_info<#cir.cxx_ctor<!rec_VirtualDelegatingCtor, custom>>
 // CIR: %[[THIS_ALLOCA:.*]] = cir.alloca "this" align(8) init : 
!cir.ptr<!cir.ptr<!rec_VirtualDelegatingCtor>>
 // CIR: %[[X_ALLOCA:.*]] = cir.alloca "x" align(4) init : !cir.ptr<!s32i>
 // CIR: %[[THIS_LOAD:.*]] = cir.load %[[THIS_ALLOCA]] : 
!cir.ptr<!cir.ptr<!rec_VirtualDelegatingCtor>>, 
!cir.ptr<!rec_VirtualDelegatingCtor>
diff --git a/clang/test/CIR/IR/func-info-attr.cir 
b/clang/test/CIR/IR/func-info-attr.cir
deleted file mode 100644
index 902616014bd9b..0000000000000
--- a/clang/test/CIR/IR/func-info-attr.cir
+++ /dev/null
@@ -1,11 +0,0 @@
-// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
-
-module {
-
-cir.func private @_ZSt4findv() func_info<#cir.func_info<name = "find", 
in_std_namespace = true>>
-// CHECK: cir.func private @_ZSt4findv() func_info<#cir.func_info<name = 
"find", in_std_namespace = true>>
-
-cir.func private @_ZNSt9container5beginEv() func_info<#cir.func_info<name = 
"begin", in_std_namespace = true, instance_method = true>>
-// CHECK: cir.func private @_ZNSt9container5beginEv() 
func_info<#cir.func_info<name = "begin", in_std_namespace = true, 
instance_method = true>>
-
-}
diff --git a/clang/test/CIR/IR/func.cir b/clang/test/CIR/IR/func.cir
index 6d327d60b3266..4c6bd33b386ab 100644
--- a/clang/test/CIR/IR/func.cir
+++ b/clang/test/CIR/IR/func.cir
@@ -166,35 +166,35 @@ cir.func @global_dtor_with_priority() global_dtor(201) {
 
 !rec_Foo = !cir.struct<"Foo" {!s32i}>
 
-cir.func @Foo_default() special_member<#cir.cxx_ctor<!rec_Foo, default>> {
+cir.func @Foo_default() func_info<#cir.cxx_ctor<!rec_Foo, default>> {
   cir.return
 }
 
-// CHECK: cir.func @Foo_default() special_member<#cir.cxx_ctor<!rec_Foo, 
default>> {
+// CHECK: cir.func @Foo_default() func_info<#cir.cxx_ctor<!rec_Foo, default>> {
 // CHECK:   cir.return
 // CHECK: }
 
-cir.func @Foo_trivial_copy() special_member<#cir.cxx_ctor<!rec_Foo, copy, 
trivial true>> {
+cir.func @Foo_trivial_copy() func_info<#cir.cxx_ctor<!rec_Foo, copy, trivial 
true>> {
   cir.return
 }
 
-// CHECK: cir.func @Foo_trivial_copy() special_member<#cir.cxx_ctor<!rec_Foo, 
copy, trivial true>> {
+// CHECK: cir.func @Foo_trivial_copy() func_info<#cir.cxx_ctor<!rec_Foo, copy, 
trivial true>> {
 // CHECK:   cir.return
 // CHECK: }
 
-cir.func @Foo_destructor() special_member<#cir.cxx_dtor<!rec_Foo>> {
+cir.func @Foo_destructor() func_info<#cir.cxx_dtor<!rec_Foo>> {
   cir.return
 }
 
-// CHECK: cir.func @Foo_destructor() special_member<#cir.cxx_dtor<!rec_Foo>> {
+// CHECK: cir.func @Foo_destructor() func_info<#cir.cxx_dtor<!rec_Foo>> {
 // CHECK:   cir.return
 // CHECK: }
 
-cir.func @Foo_move_assign() special_member<#cir.cxx_assign<!rec_Foo, move>> {
+cir.func @Foo_move_assign() func_info<#cir.cxx_assign<!rec_Foo, move>> {
   cir.return
 }
 
-// CHECK: cir.func @Foo_move_assign() special_member<#cir.cxx_assign<!rec_Foo, 
move>> {
+// CHECK: cir.func @Foo_move_assign() func_info<#cir.cxx_assign<!rec_Foo, 
move>> {
 // CHECK:   cir.return
 // CHECK: }
 
diff --git a/clang/test/CIR/IR/invalid-func.cir 
b/clang/test/CIR/IR/invalid-func.cir
index 5fc1c2a53db09..66f73aadc8cc9 100644
--- a/clang/test/CIR/IR/invalid-func.cir
+++ b/clang/test/CIR/IR/invalid-func.cir
@@ -13,7 +13,16 @@ module {
 // -----
 
 module {
-  cir.func @l0() special_member<> { // expected-error {{expected attribute 
value}}
+  cir.func @l0() func_info<> { // expected-error {{expected attribute value}}
+    cir.return
+  }
+}
+
+// -----
+
+module {
+  // expected-error@below {{expected a function info attribute, got 32 : i32}}
+  cir.func @l0() func_info<32 : i32> {
     cir.return
   }
 }
diff --git a/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir 
b/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
index 75bc8471dfe9d..2faf2acf421d4 100644
--- a/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
+++ b/clang/test/CIR/Transforms/cxx-abi-lowering-attrs.cir
@@ -54,35 +54,35 @@ cir.func @TypeAttrs(%arg0 : !rec_Struct, %arg1 : !dmp, 
%arg2 : !dmp_ptr) -> (!re
 }
 
   // cir::CXXCtorAttr
-cir.func @ctor() special_member<#cir.cxx_ctor<!rec_Struct, default>> {
-// CHECK: cir.func @ctor() special_member<#cir.cxx_ctor<!rec_Struct, default>> 
{
+cir.func @ctor() func_info<#cir.cxx_ctor<!rec_Struct, default>> {
+// CHECK: cir.func @ctor() func_info<#cir.cxx_ctor<!rec_Struct, default>> {
   cir.trap
 }
 
-cir.func @ctor2() special_member<#cir.cxx_ctor<!dmp, default>> {
-// CHECK: cir.func @ctor2() special_member<#cir.cxx_ctor<!s64i, default>> {
+cir.func @ctor2() func_info<#cir.cxx_ctor<!dmp, default>> {
+// CHECK: cir.func @ctor2() func_info<#cir.cxx_ctor<!s64i, default>> {
   cir.trap
 }
 
   // cir::CXXDtorAttr
-cir.func @dtor() special_member<#cir.cxx_dtor<!rec_Struct>> {
-// CHECK: cir.func @dtor() special_member<#cir.cxx_dtor<!rec_Struct>> {
+cir.func @dtor() func_info<#cir.cxx_dtor<!rec_Struct>> {
+// CHECK: cir.func @dtor() func_info<#cir.cxx_dtor<!rec_Struct>> {
   cir.trap
 }
 
-cir.func @dtor2() special_member<#cir.cxx_dtor<!dmp>> {
-// CHECK: cir.func @dtor2() special_member<#cir.cxx_dtor<!s64i>> {
+cir.func @dtor2() func_info<#cir.cxx_dtor<!dmp>> {
+// CHECK: cir.func @dtor2() func_info<#cir.cxx_dtor<!s64i>> {
   cir.trap
 }
 
   // cir::CXXAssignAttr
-cir.func @assign() special_member<#cir.cxx_assign<!rec_Struct, move>> {
-// CHECK: cir.func @assign() special_member<#cir.cxx_assign<!rec_Struct, 
move>> {
+cir.func @assign() func_info<#cir.cxx_assign<!rec_Struct, move>> {
+// CHECK: cir.func @assign() func_info<#cir.cxx_assign<!rec_Struct, move>> {
   cir.trap
 }
 
-cir.func @assign2() special_member<#cir.cxx_assign<!dmp, move>> {
-// CHECK: cir.func @assign2() special_member<#cir.cxx_assign<!s64i, move>> {
+cir.func @assign2() func_info<#cir.cxx_assign<!dmp, move>> {
+// CHECK: cir.func @assign2() func_info<#cir.cxx_assign<!s64i, move>> {
   cir.trap
 }
 }
diff --git a/clang/unittests/CIR/CMakeLists.txt 
b/clang/unittests/CIR/CMakeLists.txt
index 81ef87878d33e..a431356044cfc 100644
--- a/clang/unittests/CIR/CMakeLists.txt
+++ b/clang/unittests/CIR/CMakeLists.txt
@@ -4,6 +4,7 @@ include_directories(SYSTEM ${MLIR_INCLUDE_DIR})
 include_directories(${MLIR_TABLEGEN_OUTPUT_DIR})
 
 add_distinct_clang_unittest(CIRUnitTests
+  CallOpTest.cpp
   ControlFlowTest.cpp
   PointerLikeTest.cpp
   RecordTypeMetadataTest.cpp
diff --git a/clang/unittests/CIR/CallOpTest.cpp 
b/clang/unittests/CIR/CallOpTest.cpp
new file mode 100644
index 0000000000000..6e7edf1a2176a
--- /dev/null
+++ b/clang/unittests/CIR/CallOpTest.cpp
@@ -0,0 +1,100 @@
+//===- CallOpTest.cpp - Unit tests for cir.call helpers 
-------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/IR/BuiltinOps.h"
+#include "mlir/IR/MLIRContext.h"
+#include "mlir/IR/OwningOpRef.h"
+#include "mlir/IR/SymbolTable.h"
+#include "mlir/Parser/Parser.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+
+#include <gtest/gtest.h>
+
+using namespace mlir;
+
+namespace {
+
+class CIRCallOpTest : public ::testing::Test {
+protected:
+  CIRCallOpTest() { context.loadDialect<cir::CIRDialect>(); }
+
+  OwningOpRef<ModuleOp> parse(StringRef ir) {
+    auto module = parseSourceString<ModuleOp>(ir, &context);
+    EXPECT_TRUE(module) << "failed to parse IR";
+    return module;
+  }
+
+  MLIRContext context;
+};
+
+// A callee that carries func_info, a callee that carries none, an indirect
+// call with no callee symbol at all, and a cir.try_call to the marked callee.
+constexpr const char *moduleText = R"CIR(
+!s32i = !cir.int<s, 32>
+!rec_S = !cir.struct<"S" {!s32i}>
+
+module {
+  cir.func @marked() func_info<#cir.cxx_ctor<!rec_S, default>> {
+    cir.return
+  }
+  cir.func @plain() {
+    cir.return
+  }
+  cir.func @caller(%arg0: !cir.ptr<!cir.func<()>>) {
+    cir.call @marked() : () -> ()
+    cir.call @plain() : () -> ()
+    cir.call %arg0() : (!cir.ptr<!cir.func<()>>) -> ()
+    cir.return
+  }
+  cir.func @try_caller() {
+    cir.try_call @marked() ^bb1, ^bb2 : () -> ()
+  ^bb1:
+    cir.return
+  ^bb2:
+    cir.return
+  }
+}
+)CIR";
+
+TEST_F(CIRCallOpTest, ResolveCallee) {
+  OwningOpRef<ModuleOp> module = parse(moduleText);
+  ASSERT_TRUE(module);
+
+  SmallVector<cir::CallOp> calls;
+  module->walk([&](cir::CallOp op) { calls.push_back(op); });
+  ASSERT_EQ(calls.size(), 3u);
+
+  // A direct call resolves to its callee, and the caller can then read the
+  // facts recorded on it, such as the func_info attribute.
+  cir::FuncOp marked = calls[0].resolveCallee();
+  ASSERT_TRUE(marked);
+  auto ctor = dyn_cast_or_null<cir::CXXCtorAttr>(marked.getFuncInfoAttr());
+  ASSERT_TRUE(ctor);
+  EXPECT_EQ(ctor.getCtorKind(), cir::CtorKind::Default);
+
+  // The resolution through a symbol table collection finds the same function.
+  SymbolTableCollection symbolTable;
+  EXPECT_EQ(calls[0].resolveCalleeInTable(&symbolTable), marked);
+
+  // A callee without func_info still resolves, and the attribute reads null.
+  cir::FuncOp plain = calls[1].resolveCallee();
+  ASSERT_TRUE(plain);
+  EXPECT_FALSE(plain.getFuncInfoAttr());
+
+  // An indirect call has no callee symbol to resolve.
+  EXPECT_FALSE(calls[2].resolveCallee());
+
+  // cir.try_call resolves through the same implementation.
+  SmallVector<cir::TryCallOp> tryCalls;
+  module->walk([&](cir::TryCallOp op) { tryCalls.push_back(op); });
+  ASSERT_EQ(tryCalls.size(), 1u);
+  EXPECT_EQ(tryCalls[0].resolveCallee(), marked);
+  EXPECT_EQ(tryCalls[0].resolveCalleeInTable(&symbolTable), marked);
+}
+
+} // namespace

>From e89b898b77f83f6ca6f69d59ebdc4ea93611fe8f Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Tue, 7 Jul 2026 00:51:15 +0000
Subject: [PATCH 6/9] [CIR] Fix formatting in the func_info parse block

This fixes minor formatting in the parse block and wraps the diagnostic
line the way clang format asked for.
---
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 38 +++++++++++++------------
 1 file changed, 20 insertions(+), 18 deletions(-)

diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 0ffb48c3543cd..6fe38090b57e5 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2550,24 +2550,6 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
   state.addAttribute(callConvNameAttr,
                      cir::CallingConvAttr::get(parser.getContext(), callConv));
 
-  if (parser.parseOptionalKeyword("func_info").succeeded()) {
-    if (parser.parseLess().failed())
-      return failure();
-
-    llvm::SMLoc attrLoc = parser.getCurrentLocation();
-    mlir::Attribute attr;
-    if (parser.parseAttribute(attr).failed())
-      return failure();
-    if (!mlir::isa<cir::CXXCtorAttr, cir::CXXDtorAttr, cir::CXXAssignAttr>(
-            attr))
-      return parser.emitError(attrLoc, "expected a function info attribute, 
got ")
-             << attr;
-    state.addAttribute(funcInfoNameAttr, attr);
-
-    if (parser.parseGreater().failed())
-      return failure();
-  }
-
   auto parseGlobalDtorCtor =
       [&](StringRef keyword,
           llvm::function_ref<void(std::optional<int> prio)> createAttr)
@@ -2589,6 +2571,26 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, 
OperationState &state) {
     return success();
   };
 
+  // Parse the func_info attribute
+  if (parser.parseOptionalKeyword("func_info").succeeded()) {
+    if (parser.parseLess().failed())
+      return failure();
+
+    llvm::SMLoc attrLoc = parser.getCurrentLocation();
+    mlir::Attribute attr;
+    if (parser.parseAttribute(attr).failed())
+      return failure();
+    if (!mlir::isa<cir::CXXCtorAttr, cir::CXXDtorAttr, cir::CXXAssignAttr>(
+            attr))
+      return parser.emitError(attrLoc,
+                              "expected a function info attribute, got ")
+             << attr;
+    state.addAttribute(funcInfoNameAttr, attr);
+
+    if (parser.parseGreater().failed())
+      return failure();
+  }
+
   if (parseGlobalDtorCtor("global_ctor", [&](std::optional<int> priority) {
         mlir::IntegerAttr globalCtorPriorityAttr =
             builder.getI32IntegerAttr(priority.value_or(65535));

>From a2be935e5144e4e79738d48b4cf468a0c277df9e Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Tue, 7 Jul 2026 23:28:53 +0000
Subject: [PATCH 7/9] [CIR] Require a symbol table collection in the callee
 resolver

The call ops now pass their own getCalleeAttr into the shared
resolution, and the form without a symbol table collection is gone,
since it walked the module on every use. resolveCalleeInTable takes a
reference, so every caller goes through the cache.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 19 ++++++--------
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 26 ++++++--------------
 clang/unittests/CIR/CallOpTest.cpp           | 15 +++++------
 3 files changed, 20 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index e6704ddb8110b..bb8d3e849822f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4287,18 +4287,13 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
     bool isIndirect() { return !getCallee(); }
     mlir::Value getIndirectCall();
 
-    /// Returns the function this call resolves to, so a pass standing at
-    /// a call can read the facts recorded on its callee, such as the
-    /// func_info attribute. Returns null when the call is indirect, when
-    /// the callee symbol does not resolve, or when it resolves to
-    /// something other than a cir.func. Walks the enclosing symbol tables
-    /// on every use, so passes that resolve many calls should use
-    /// resolveCalleeInTable instead.
-    cir::FuncOp resolveCallee();
-
-    /// The same resolution through a symbol table collection, so the
-    /// lookup uses its cache.
-    cir::FuncOp resolveCalleeInTable(mlir::SymbolTableCollection *symbolTable);
+    /// Returns the function this call resolves to through the given
+    /// symbol table collection, so repeated lookups use its cache. A pass
+    /// standing at a call can then read the facts recorded on the callee,
+    /// such as the func_info attribute. Returns null when the call is
+    /// indirect, when the callee symbol does not resolve, or when it
+    /// resolves to something other than a cir.func.
+    cir::FuncOp resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable);
 
     void setArg(unsigned index, mlir::Value value) {
       if (!isIndirect()) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 6fe38090b57e5..8d3cc4bc93eb1 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1052,24 +1052,16 @@ unsigned cir::CallOp::getNumArgOperands() {
 }
 
 static cir::FuncOp resolveCalleeImpl(mlir::Operation *op,
-                                     mlir::SymbolTableCollection *symbolTable) 
{
-  auto callee = op->getAttrOfType<mlir::FlatSymbolRefAttr>(
-      CIRDialect::getCalleeAttrName());
+                                     mlir::FlatSymbolRefAttr callee,
+                                     mlir::SymbolTableCollection &symbolTable) 
{
   if (!callee)
     return {};
-
-  if (symbolTable)
-    return symbolTable->lookupNearestSymbolFrom<cir::FuncOp>(op, callee);
-  return mlir::SymbolTable::lookupNearestSymbolFrom<cir::FuncOp>(op, callee);
-}
-
-cir::FuncOp cir::CallOp::resolveCallee() {
-  return resolveCalleeImpl(*this, /*symbolTable=*/nullptr);
+  return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(op, callee);
 }
 
 cir::FuncOp
-cir::CallOp::resolveCalleeInTable(mlir::SymbolTableCollection *symbolTable) {
-  return resolveCalleeImpl(*this, symbolTable);
+cir::CallOp::resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable) {
+  return resolveCalleeImpl(*this, getCalleeAttr(), symbolTable);
 }
 
 static mlir::ParseResult
@@ -1383,13 +1375,9 @@ unsigned cir::TryCallOp::getNumArgOperands() {
   return this->getOperation()->getNumOperands();
 }
 
-cir::FuncOp cir::TryCallOp::resolveCallee() {
-  return resolveCalleeImpl(*this, /*symbolTable=*/nullptr);
-}
-
 cir::FuncOp
-cir::TryCallOp::resolveCalleeInTable(mlir::SymbolTableCollection *symbolTable) 
{
-  return resolveCalleeImpl(*this, symbolTable);
+cir::TryCallOp::resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable) 
{
+  return resolveCalleeImpl(*this, getCalleeAttr(), symbolTable);
 }
 
 LogicalResult
diff --git a/clang/unittests/CIR/CallOpTest.cpp 
b/clang/unittests/CIR/CallOpTest.cpp
index 6e7edf1a2176a..5b2e4780b1496 100644
--- a/clang/unittests/CIR/CallOpTest.cpp
+++ b/clang/unittests/CIR/CallOpTest.cpp
@@ -69,32 +69,29 @@ TEST_F(CIRCallOpTest, ResolveCallee) {
   module->walk([&](cir::CallOp op) { calls.push_back(op); });
   ASSERT_EQ(calls.size(), 3u);
 
+  SymbolTableCollection symbolTable;
+
   // A direct call resolves to its callee, and the caller can then read the
   // facts recorded on it, such as the func_info attribute.
-  cir::FuncOp marked = calls[0].resolveCallee();
+  cir::FuncOp marked = calls[0].resolveCalleeInTable(symbolTable);
   ASSERT_TRUE(marked);
   auto ctor = dyn_cast_or_null<cir::CXXCtorAttr>(marked.getFuncInfoAttr());
   ASSERT_TRUE(ctor);
   EXPECT_EQ(ctor.getCtorKind(), cir::CtorKind::Default);
 
-  // The resolution through a symbol table collection finds the same function.
-  SymbolTableCollection symbolTable;
-  EXPECT_EQ(calls[0].resolveCalleeInTable(&symbolTable), marked);
-
   // A callee without func_info still resolves, and the attribute reads null.
-  cir::FuncOp plain = calls[1].resolveCallee();
+  cir::FuncOp plain = calls[1].resolveCalleeInTable(symbolTable);
   ASSERT_TRUE(plain);
   EXPECT_FALSE(plain.getFuncInfoAttr());
 
   // An indirect call has no callee symbol to resolve.
-  EXPECT_FALSE(calls[2].resolveCallee());
+  EXPECT_FALSE(calls[2].resolveCalleeInTable(symbolTable));
 
   // cir.try_call resolves through the same implementation.
   SmallVector<cir::TryCallOp> tryCalls;
   module->walk([&](cir::TryCallOp op) { tryCalls.push_back(op); });
   ASSERT_EQ(tryCalls.size(), 1u);
-  EXPECT_EQ(tryCalls[0].resolveCallee(), marked);
-  EXPECT_EQ(tryCalls[0].resolveCalleeInTable(&symbolTable), marked);
+  EXPECT_EQ(tryCalls[0].resolveCalleeInTable(symbolTable), marked);
 }
 
 } // namespace

>From 0cd357cf7cdeda974ed2da98779dcc36157229cd Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Thu, 9 Jul 2026 21:46:06 +0000
Subject: [PATCH 8/9] [CIR] Inline the callee resolver body and trim the
 comments

The resolver body moves into resolveCalleeInTable on cir.call and
cir.try_call, so the shared impl function is gone. The comment on
resolveCalleeInTable now only states the returned value, and the
func_info union comment now only names the slot it describes.
---
 clang/include/clang/CIR/Dialect/IR/CIRAttrs.td |  5 +----
 clang/include/clang/CIR/Dialect/IR/CIROps.td   |  8 ++------
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp        | 18 ++++++++----------
 3 files changed, 11 insertions(+), 20 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index e1344f25f6c88..88e8f91f7e75c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1296,10 +1296,7 @@ def CIR_CXXAssignAttr : CIR_Attr<"CXXAssign", 
"cxx_assign"> {
 // FuncInfoAttr
 
//===----------------------------------------------------------------------===//
 
-// The attributes accepted in the optional func_info slot on cir.func. Each
-// form records one kind of precomputed fact about the function. The special
-// member forms embed the record type they operate on, and the CXX ABI
-// lowering replaces them with forms holding the converted type.
+// The attributes accepted in the optional func_info slot on cir.func.
 def CIR_FuncInfoAttr : AnyAttrOf<[
   CIR_CXXCtorAttr,
   CIR_CXXDtorAttr,
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index bb8d3e849822f..6be329f6f48e8 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4287,12 +4287,8 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
     bool isIndirect() { return !getCallee(); }
     mlir::Value getIndirectCall();
 
-    /// Returns the function this call resolves to through the given
-    /// symbol table collection, so repeated lookups use its cache. A pass
-    /// standing at a call can then read the facts recorded on the callee,
-    /// such as the func_info attribute. Returns null when the call is
-    /// indirect, when the callee symbol does not resolve, or when it
-    /// resolves to something other than a cir.func.
+    /// Returns the `cir.func` being called or null if the call is indirect or
+    /// cannot be resolved.
     cir::FuncOp resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable);
 
     void setArg(unsigned index, mlir::Value value) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 8d3cc4bc93eb1..bf8f48fe86415 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1051,17 +1051,12 @@ unsigned cir::CallOp::getNumArgOperands() {
   return this->getOperation()->getNumOperands();
 }
 
-static cir::FuncOp resolveCalleeImpl(mlir::Operation *op,
-                                     mlir::FlatSymbolRefAttr callee,
-                                     mlir::SymbolTableCollection &symbolTable) 
{
-  if (!callee)
-    return {};
-  return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(op, callee);
-}
-
 cir::FuncOp
 cir::CallOp::resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable) {
-  return resolveCalleeImpl(*this, getCalleeAttr(), symbolTable);
+  mlir::FlatSymbolRefAttr callee = getCalleeAttr();
+  if (!callee)
+    return {};
+  return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(*this, callee);
 }
 
 static mlir::ParseResult
@@ -1377,7 +1372,10 @@ unsigned cir::TryCallOp::getNumArgOperands() {
 
 cir::FuncOp
 cir::TryCallOp::resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable) 
{
-  return resolveCalleeImpl(*this, getCalleeAttr(), symbolTable);
+  mlir::FlatSymbolRefAttr callee = getCalleeAttr();
+  if (!callee)
+    return {};
+  return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(*this, callee);
 }
 
 LogicalResult

>From 6439048c60c3efd3e17015fd07a76ac794ed22b3 Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Fri, 10 Jul 2026 00:13:52 +0000
Subject: [PATCH 9/9] [CIR] Define the callee resolver in the tablegen file

The resolveCalleeInTable body moves next to its declaration on the
call op base class through extraClassDefinition, so one definition
serves cir.call and cir.try_call and CIRDialect.cpp holds no resolver
code. Placing the body in the class declaration itself does not
compile, since the generated header declares cir.call before cir.func
and the return type is incomplete there.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td | 10 ++++++++++
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 16 ----------------
 2 files changed, 10 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 6be329f6f48e8..762ef56248e4c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4302,6 +4302,16 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
     }
   }];
 
+  let extraClassDefinition = [{
+    cir::FuncOp $cppClass::resolveCalleeInTable(
+        mlir::SymbolTableCollection &symbolTable) {
+      mlir::FlatSymbolRefAttr callee = getCalleeAttr();
+      if (!callee)
+        return {};
+      return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(*this, callee);
+    }
+  }];
+
   let hasCustomAssemblyFormat = 1;
   let skipDefaultBuilders = 1;
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index bf8f48fe86415..c519229a02f26 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1051,14 +1051,6 @@ unsigned cir::CallOp::getNumArgOperands() {
   return this->getOperation()->getNumOperands();
 }
 
-cir::FuncOp
-cir::CallOp::resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable) {
-  mlir::FlatSymbolRefAttr callee = getCalleeAttr();
-  if (!callee)
-    return {};
-  return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(*this, callee);
-}
-
 static mlir::ParseResult
 parseTryCallDestinations(mlir::OpAsmParser &parser,
                          mlir::OperationState &result) {
@@ -1370,14 +1362,6 @@ unsigned cir::TryCallOp::getNumArgOperands() {
   return this->getOperation()->getNumOperands();
 }
 
-cir::FuncOp
-cir::TryCallOp::resolveCalleeInTable(mlir::SymbolTableCollection &symbolTable) 
{
-  mlir::FlatSymbolRefAttr callee = getCalleeAttr();
-  if (!callee)
-    return {};
-  return symbolTable.lookupNearestSymbolFrom<cir::FuncOp>(*this, callee);
-}
-
 LogicalResult
 cir::TryCallOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   return verifyCallCommInSymbolUses(*this, symbolTable);

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to