https://github.com/andykaylor created 
https://github.com/llvm/llvm-project/pull/174640

This adds code for generating cir.method attributes and lowering them to LLVM 
IR to implement support the C++ method pointer variables.

>From ebdb617d94b778e7d0466aec9be37e993858d893 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Thu, 11 Dec 2025 10:48:09 -0800
Subject: [PATCH] [CIR] Upstream CIR method attribute handling

This adds code for generating cir.method attributes and lowering them to
LLVM IR to implement support the C++ method pointer variables.
---
 .../CIR/Dialect/Builder/CIRBaseBuilder.h      |  5 ++
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 43 +++++++++++++++++
 clang/include/clang/CIR/MissingFeatures.h     |  1 +
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        | 15 ++++--
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         | 44 +++++++++++++++++
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  4 +-
 .../CIR/Dialect/Transforms/CXXABILowering.cpp |  9 ++++
 .../Transforms/TargetLowering/CIRCXXABI.h     |  6 +++
 .../TargetLowering/LowerItaniumCXXABI.cpp     | 48 +++++++++++++++++++
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 15 +++---
 .../CIR/CodeGen/pointer-to-member-func.cpp    | 41 ++++++++--------
 11 files changed, 200 insertions(+), 31 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h 
b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index cc28941aaa079..eadf3dd6ee0f0 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -179,6 +179,11 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
     return getPointerTo(cir::VoidType::get(getContext()), as);
   }
 
+  cir::MethodAttr getMethodAttr(cir::MethodType ty, cir::FuncOp methodFuncOp) {
+    auto methodFuncSymbolRef = mlir::FlatSymbolRefAttr::get(methodFuncOp);
+    return cir::MethodAttr::get(ty, methodFuncSymbolRef);
+  }
+
   cir::BoolAttr getCIRBoolAttr(bool state) {
     return cir::BoolAttr::get(getContext(), state);
   }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td 
b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index c0279a0b20670..74ca19fb329b3 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -498,6 +498,49 @@ def CIR_DataMemberAttr : CIR_Attr<"DataMember", 
"data_member", [
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// MethodAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> {
+  let summary = "Holds a constant pointer-to-member-function value";
+  let description = [{
+    A method attribute is a literal attribute that represents a constant
+    pointer-to-member-function value.
+
+    If the member function is a non-virtual function, the `symbol` parameter
+    gives the global symbol for the non-virtual member function.
+
+    Virtual function handling is not yet implemented.
+
+    If `symbol` is not present, the attribute represents a null pointer
+    constant.
+  }];
+
+  let parameters = (ins AttributeSelfTypeParameter<
+                            "", "cir::MethodType">:$type,
+                        OptionalParameter<
+                            "std::optional<mlir::FlatSymbolRefAttr>">:$symbol);
+
+  let builders = [
+    AttrBuilderWithInferredContext<(ins "cir::MethodType":$type), [{
+      return $_get(type.getContext(), type, std::nullopt);
+    }]>,
+    AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
+                                        "mlir::FlatSymbolRefAttr":$symbol), [{
+      return $_get(type.getContext(), type, symbol);
+    }]>,
+  ];
+
+  let hasCustomAssemblyFormat = 1;
+
+  let extraClassDeclaration = [{
+    bool isNull() const {
+      return !getSymbol().has_value();
+    }
+  }];
+}
+
 
//===----------------------------------------------------------------------===//
 // GlobalViewAttr
 
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index acfc937a11993..e7b2d7ebbe113 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -345,6 +345,7 @@ struct MissingFeatures {
   static bool useEHCleanupForArray() { return false; }
   static bool vaArgABILowering() { return false; }
   static bool vectorConstants() { return false; }
+  static bool virtualMethodAttr() { return false; }
   static bool vlas() { return false; }
   static bool vtableInitialization() { return false; }
   static bool vtableEmitMetadata() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 42df6304628dc..a995a4ceb601d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1527,9 +1527,18 @@ mlir::Value 
CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) {
   const auto *decl = cast<DeclRefExpr>(e->getSubExpr())->getDecl();
 
   // A member function pointer.
-  if (isa<CXXMethodDecl>(decl)) {
-    errorNYI(e->getSourceRange(), "emitMemberPointerConstant: method pointer");
-    return {};
+  if (const auto *methodDecl = dyn_cast<CXXMethodDecl>(decl)) {
+    auto ty = mlir::cast<cir::MethodType>(convertType(e->getType()));
+    if (methodDecl->isVirtual()) {
+      assert(!cir::MissingFeatures::virtualMethodAttr());
+      errorNYI(e->getSourceRange(),
+               "emitMemberPointerConstant: virtual method pointer");
+      return {};
+    }
+
+    auto methodFuncOp = getAddrOfFunction(methodDecl);
+    return cir::ConstantOp::create(builder, loc,
+                                   builder.getMethodAttr(ty, methodFuncOp));
   }
 
   // Otherwise, a member data pointer.
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp 
b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 59d7765198f9e..c69335ccf7960 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -301,6 +301,50 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> 
emitError,
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// MethodAttr definitions
+//===----------------------------------------------------------------------===//
+
+Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
+  auto ty = mlir::cast<cir::MethodType>(odsType);
+
+  if (parser.parseLess().failed())
+    return {};
+
+  // Try to parse the null pointer constant.
+  if (parser.parseOptionalKeyword("null").succeeded()) {
+    if (parser.parseGreater())
+      return {};
+    return get(ty);
+  }
+
+  // Try to parse a flat symbol ref for a pointer to non-virtual member
+  // function.
+  FlatSymbolRefAttr symbol;
+  auto parseSymbolRefResult = parser.parseOptionalAttribute(symbol);
+  if (parseSymbolRefResult.has_value()) {
+    if (parseSymbolRefResult.value().failed())
+      return {};
+    if (parser.parseGreater())
+      return {};
+    return get(ty, symbol);
+  }
+
+  return {};
+}
+
+void MethodAttr::print(AsmPrinter &printer) const {
+  auto symbol = getSymbol();
+
+  printer << '<';
+  if (symbol.has_value()) {
+    printer << *symbol;
+  } else {
+    printer << "null";
+  }
+  printer << '>';
+}
+
 
//===----------------------------------------------------------------------===//
 // CIR ConstArrayAttr
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 8f0dc705181e6..0f1997ff8af6e 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -357,9 +357,9 @@ static LogicalResult checkConstantTypes(mlir::Operation 
*op, mlir::Type opType,
     return success();
   }
 
-  if (isa<cir::DataMemberAttr>(attrType)) {
+  if (isa<cir::DataMemberAttr, cir::MethodAttr>(attrType)) {
     // More detailed type verifications are already done in
-    // DataMemberAttr::verify. Don't need to repeat here.
+    // DataMemberAttr::verify or MethodAttr::verify. Don't need to repeat here.
     return success();
   }
 
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 49087ca38d948..4ef277be8ce08 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -171,6 +171,15 @@ mlir::LogicalResult 
CIRConstantOpABILowering::matchAndRewrite(
     return mlir::success();
   }
 
+  if (mlir::isa<cir::MethodType>(op.getType())) {
+    auto method = mlir::cast<cir::MethodAttr>(op.getValue());
+    mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
+    mlir::TypedAttr abiValue = lowerModule->getCXXABI().lowerMethodConstant(
+        method, layout, *getTypeConverter());
+    rewriter.replaceOpWithNewOp<ConstantOp>(op, abiValue);
+    return mlir::success();
+  }
+
   llvm_unreachable("constant operand is not an CXXABI-dependent type");
 }
 
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h 
b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
index edfe29f1471cb..bcbda7b94a56f 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
@@ -59,6 +59,12 @@ class CIRCXXABI {
   lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
                         mlir::Value loweredAddr, mlir::Value loweredMember,
                         mlir::OpBuilder &builder) const = 0;
+
+  /// Lower the given member function pointer constant to a constant of the ABI
+  /// type. The returned constant is represented as an attribute as well.
+  virtual mlir::TypedAttr
+  lowerMethodConstant(cir::MethodAttr attr, const mlir::DataLayout &layout,
+                      const mlir::TypeConverter &typeConverter) const = 0;
 };
 
 /// Creates an Itanium-family ABI.
diff --git 
a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp 
b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 323ecc46909d4..b3a858a072f8b 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -51,6 +51,10 @@ class LowerItaniumCXXABI : public CIRCXXABI {
   lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
                         mlir::Value loweredAddr, mlir::Value loweredMember,
                         mlir::OpBuilder &builder) const override;
+
+  mlir::TypedAttr
+  lowerMethodConstant(cir::MethodAttr attr, const mlir::DataLayout &layout,
+                      const mlir::TypeConverter &typeConverter) const override;
 };
 
 } // namespace
@@ -133,4 +137,48 @@ mlir::Operation *LowerItaniumCXXABI::lowerGetRuntimeMember(
                              cir::CastKind::bitcast, memberBytesPtr);
 }
 
+mlir::TypedAttr LowerItaniumCXXABI::lowerMethodConstant(
+    cir::MethodAttr attr, const mlir::DataLayout &layout,
+    const mlir::TypeConverter &typeConverter) const {
+  cir::IntType ptrdiffCIRTy = getPtrDiffCIRTy(lm);
+  auto loweredMethodTy = mlir::cast<cir::RecordType>(
+      lowerMethodType(attr.getType(), typeConverter));
+
+  auto zero = cir::IntAttr::get(ptrdiffCIRTy, 0);
+
+  // Itanium C++ ABI 2.3.2:
+  //   In all representations, the basic ABI properties of member function
+  //   pointer types are those of the following class, where fnptr_t is the
+  //   appropriate function-pointer type for a member function of this type:
+  //
+  //   struct {
+  //     fnptr_t ptr;
+  //     ptrdiff_t adj;
+  //   };
+
+  if (attr.isNull()) {
+    // Itanium C++ ABI 2.3.2:
+    //
+    //   In the standard representation, a null member function pointer is
+    //   represented with ptr set to a null pointer. The value of adj is
+    //   unspecified for null member function pointers.
+    //
+    // clang CodeGen emits struct{null, null} for null member function 
pointers.
+    // Let's do the same here.
+    return cir::ConstRecordAttr::get(
+        loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {zero, 
zero}));
+  }
+
+  assert(!cir::MissingFeatures::virtualMethodAttr());
+
+  // Itanium C++ ABI 2.3.2:
+  //
+  //   A member function pointer for a non-virtual member function is
+  //   represented with ptr set to a pointer to the function, using the base
+  //   ABI's representation of function pointers.
+  auto ptr = cir::GlobalViewAttr::get(ptrdiffCIRTy, attr.getSymbol().value());
+  return cir::ConstRecordAttr::get(
+      loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, zero}));
+}
+
 } // namespace cir
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 7686add38250e..03245cc6963e9 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -577,11 +577,11 @@ mlir::Value 
CIRAttrToValue::visitCirAttr(cir::GlobalViewAttr globalAttr) {
                                   mlir::LLVM::GEPNoWrapFlags::none);
   }
 
-  // The incubator has handling here for the attribute having integer type, but
-  // the only test case I could find that reaches it is a direct CIR-to-LLVM IR
-  // lowering with no clear indication of how the CIR might have been 
generated.
-  // We'll hit the unreachable below if this happens.
-  assert(!cir::MissingFeatures::globalViewIntLowering());
+  if (auto intTy = mlir::dyn_cast<cir::IntType>(globalAttr.getType())) {
+    auto llvmDstTy = converter->convertType(globalAttr.getType());
+    return mlir::LLVM::PtrToIntOp::create(rewriter, parentOp->getLoc(),
+                                          llvmDstTy, addrOp);
+  }
 
   if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(globalAttr.getType())) {
     mlir::Type llvmEltTy =
@@ -1801,7 +1801,10 @@ mlir::LogicalResult 
CIRToLLVMConstantOpLowering::matchAndRewrite(
   } else if (mlir::isa<cir::IntType>(op.getType())) {
     // Lower GlobalViewAttr to llvm.mlir.addressof + llvm.mlir.ptrtoint
     if (auto ga = mlir::dyn_cast<cir::GlobalViewAttr>(op.getValue())) {
-      // See the comment in visitCirAttr for why this isn't implemented.
+      // We can have a global view with an integer type in the case of method
+      // pointers, but the lowering of those doesn't go through this path.
+      // They are handled in the visitCirAttr. This is left as an error until
+      // we have a test case that reaches it.
       assert(!cir::MissingFeatures::globalViewIntLowering());
       op.emitError() << "global view with integer type";
       return mlir::failure();
diff --git a/clang/test/CIR/CodeGen/pointer-to-member-func.cpp 
b/clang/test/CIR/CodeGen/pointer-to-member-func.cpp
index e3fe05dfa34fa..47c5871e72290 100644
--- a/clang/test/CIR/CodeGen/pointer-to-member-func.cpp
+++ b/clang/test/CIR/CodeGen/pointer-to-member-func.cpp
@@ -12,28 +12,29 @@ struct Foo {
   virtual void m3(int);
 };
 
-void unused_pointer_to_member_func(void (Foo::*func)(int)) {
+auto make_non_virtual() -> void (Foo::*)(int) {
+  return &Foo::m1;
 }
 
-// CIR-BEFORE: cir.func {{.*}} 
@_Z29unused_pointer_to_member_funcM3FooFviE(%[[ARG:.*]]: 
!cir.method<!cir.func<(!s32i)> in !rec_Foo>)
-// CIR-BEFORE:   %[[FUNC:.*]] = cir.alloca !cir.method<!cir.func<(!s32i)> in 
!rec_Foo>, !cir.ptr<!cir.method<!cir.func<(!s32i)> in !rec_Foo>>, ["func", init]
+// CIR-BEFORE: cir.func {{.*}} @_Z16make_non_virtualv() -> 
!cir.method<!cir.func<(!s32i)> in !rec_Foo>
+// CIR-BEFORE:   %[[RETVAL:.*]] = cir.alloca !cir.method<!cir.func<(!s32i)> in 
!rec_Foo>, !cir.ptr<!cir.method<!cir.func<(!s32i)> in !rec_Foo>>, ["__retval"]
+// CIR-BEFORE:   %[[METHOD_PTR:.*]] = cir.const #cir.method<@_ZN3Foo2m1Ei> : 
!cir.method<!cir.func<(!s32i)> in !rec_Foo>
+// CIR-BEFORE:   cir.store %[[METHOD_PTR]], %[[RETVAL]]
+// CIR-BEFORE:   %[[RET:.*]] = cir.load %[[RETVAL]]
+// CIR-BEFORE:   cir.return %[[RET]] : !cir.method<!cir.func<(!s32i)> in 
!rec_Foo>
 
-// CIR-AFTER: !rec_anon_struct = !cir.record<struct  {!s64i, !s64i}>
-// CIR-AFTER: cir.func {{.*}} 
@_Z29unused_pointer_to_member_funcM3FooFviE(%[[ARG:.*]]: !rec_anon_struct 
{{.*}})
-// CIR-AFTER    %[[FUNC:.*]] = cir.alloca !rec_anon_struct, 
!cir.ptr<!rec_anon_struct>, ["func", init]
+// CIR-AFTER: cir.func {{.*}} @_Z16make_non_virtualv() -> !rec_anon_struct {
+// CIR-AFTER:   %[[RETVAL:.*]] = cir.alloca !rec_anon_struct, 
!cir.ptr<!rec_anon_struct>, ["__retval"]
+// CIR-AFTER:   %[[METHOD_PTR:.*]] = cir.const 
#cir.const_record<{#cir.global_view<@_ZN3Foo2m1Ei> : !s64i, #cir.int<0> : 
!s64i}> : !rec_anon_struct
+// CIR-AFTER:   cir.store %[[METHOD_PTR]], %[[RETVAL]]
+// CIR-AFTER:   %[[RET:.*]] = cir.load %[[RETVAL]]
+// CIR-AFTER:   cir.return %[[RET]] : !rec_anon_struct
 
-// NOTE: The difference between LLVM and OGCG are due to the lack of calling 
convention handling in CIR.
+// LLVM: define {{.*}} { i64, i64 } @_Z16make_non_virtualv()
+// LLVM:   %[[RETVAL:.*]] = alloca { i64, i64 }
+// LLVM:   store { i64, i64 } { i64 ptrtoint (ptr @_ZN3Foo2m1Ei to i64), i64 0 
}, ptr %[[RETVAL]]
+// LLVM:   %[[RET:.*]] = load { i64, i64 }, ptr %[[RETVAL]]
+// LLVM:   ret { i64, i64 } %[[RET]]
 
-// LLVM: define {{.*}} void @_Z29unused_pointer_to_member_funcM3FooFviE({ i64, 
i64 } %[[ARG:.*]])
-// LLVM:   %[[FUNC:.*]] = alloca { i64, i64 }
-// LLVM:   store { i64, i64 } %[[ARG]], ptr %[[FUNC]]
-
-// OGCG: define {{.*}} void @_Z29unused_pointer_to_member_funcM3FooFviE(i64 
%[[FUNC_COERCE0:.*]], i64 %[[FUNC_COERCE1:.*]])
-// OGCG:   %[[FUNC:.*]] = alloca { i64, i64 }
-// OGCG:   %[[FUNC_ADDR:.*]] = alloca { i64, i64 }
-// OGCG:   %[[FUNC_0:.*]] = getelementptr inbounds nuw { i64, i64 }, ptr 
%[[FUNC]], i32 0, i32 0
-// OGCG:   store i64 %[[FUNC_COERCE0]], ptr %[[FUNC_0]]
-// OGCG:   %[[FUNC_1:.*]] = getelementptr inbounds nuw { i64, i64 }, ptr 
%[[FUNC]], i32 0, i32 1
-// OGCG:   store i64 %[[FUNC_COERCE1]], ptr %[[FUNC_1]]
-// OGCG:   %[[FUNC1:.*]] = load { i64, i64 }, ptr %[[FUNC]]
-// OGCG:   store { i64, i64 } %[[FUNC1]], ptr %[[FUNC_ADDR]]
+// OGCG: define {{.*}} { i64, i64 } @_Z16make_non_virtualv()
+// OGCG:   ret { i64, i64 } { i64 ptrtoint (ptr @_ZN3Foo2m1Ei to i64), i64 0 }

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

Reply via email to