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

This upstreams support for pointer-to-member value representations of null and 
virtual member pointers and the lowering of virtual member pointers.

>From 9741c1a77b6597a3607899bc10911ca235139977 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <[email protected]>
Date: Fri, 16 Jan 2026 14:23:41 -0800
Subject: [PATCH] [CIR] Upstream support for null and virtual method pointers

This upstreams support for pointer-to-member value representations of
null and virtual member pointers.
---
 .../CIR/Dialect/Builder/CIRBaseBuilder.h      |  4 ++
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 31 ++++++++---
 clang/include/clang/CIR/MissingFeatures.h     |  1 -
 clang/lib/CIR/CodeGen/CIRGenBuilder.h         |  4 ++
 clang/lib/CIR/CodeGen/CIRGenCXXABI.h          |  3 ++
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp    |  5 +-
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 22 ++++++++
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |  9 ++--
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         | 29 +++++++++-
 .../TargetLowering/LowerItaniumCXXABI.cpp     | 24 ++++++++-
 .../CIR/CodeGen/pointer-to-member-func.cpp    | 54 +++++++++++++++++++
 11 files changed, 166 insertions(+), 20 deletions(-)

diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h 
b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 2aaae86240cf2..c35c42c8c506b 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -184,6 +184,10 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
     return cir::MethodAttr::get(ty, methodFuncSymbolRef);
   }
 
+  cir::MethodAttr getNullMethodAttr(cir::MethodType ty) {
+    return cir::MethodAttr::get(ty);
+  }
+
   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 2309811815f27..9447ae629b445 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -511,10 +511,13 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", 
[TypedAttrInterface]> {
     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 the member function is a virtual function, the `vtable_offset` parameter
+    gives the offset of the vtable entry corresponding to the virtual member
+    function.
 
-    If `symbol` is not present, the attribute represents a null pointer
-    constant.
+    `symbol` and `vtable_offset` cannot be present at the same time. If both of
+    `symbol` and `vtable_offset` are not present, the attribute represents a
+    null pointer constant.
 
     Examples:
     ```
@@ -522,7 +525,7 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", 
[TypedAttrInterface]> {
     %0 = cir.const #cir.method<@_ZN1S2m1Ei> :
              !cir.method<!cir.func<(!s32i)> in !rec_S>
 
-    // Virtual method (not yet implemented)
+    // Virtual method
     %1 = cir.const #cir.method<vtable_offset = 8> :
              !cir.method<!cir.func<(!s32i)> in !rec_S>
 
@@ -534,23 +537,35 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", 
[TypedAttrInterface]> {
   let parameters = (ins AttributeSelfTypeParameter<
                             "", "cir::MethodType">:$type,
                         OptionalParameter<
-                            "std::optional<mlir::FlatSymbolRefAttr>">:$symbol);
+                            "std::optional<mlir::FlatSymbolRefAttr>">:$symbol,
+                        OptionalParameter<
+                            "std::optional<uint64_t>">:$vtable_offset);
 
   let builders = [
     AttrBuilderWithInferredContext<(ins "cir::MethodType":$type), [{
-      return $_get(type.getContext(), type, std::nullopt);
+      return $_get(type.getContext(), type, std::nullopt, std::nullopt);
     }]>,
     AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
                                         "mlir::FlatSymbolRefAttr":$symbol), [{
-      return $_get(type.getContext(), type, symbol);
+      return $_get(type.getContext(), type, symbol, std::nullopt);
+    }]>,
+    AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
+                                        "uint64_t":$vtable_offset), [{
+      return $_get(type.getContext(), type, std::nullopt, vtable_offset);
     }]>,
   ];
 
   let hasCustomAssemblyFormat = 1;
 
+  let genVerifyDecl = 1;
+
   let extraClassDeclaration = [{
     bool isNull() const {
-      return !getSymbol().has_value();
+      return !getSymbol().has_value() && !getVtableOffset().has_value();
+    }
+
+    bool isVirtual() const {
+      return getVtableOffset().has_value();
     }
   }];
 }
diff --git a/clang/include/clang/CIR/MissingFeatures.h 
b/clang/include/clang/CIR/MissingFeatures.h
index 359d813171294..10ef749b51dd3 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -354,7 +354,6 @@ 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/CIRGenBuilder.h 
b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index ff492edf0b04e..0f43429803596 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -370,6 +370,10 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
     return cir::ConstantOp::create(*this, loc, getNullDataMemberAttr(ty));
   }
 
+  cir::ConstantOp getNullMethodPtr(cir::MethodType ty, mlir::Location loc) {
+    return cir::ConstantOp::create(*this, loc, getNullMethodAttr(ty));
+  }
+
   // TODO: split this to createFPExt/createFPTrunc when we have dedicated cast
   // operations.
   mlir::Value createFloatingCast(mlir::Value v, mlir::Type destType) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h 
b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 1d5fce3036bf4..27d48bfabeb38 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -61,6 +61,9 @@ class CIRGenCXXABI {
                                       cir::PointerType destCIRTy,
                                       bool isRefCast, Address src) = 0;
 
+  virtual cir::MethodAttr buildVirtualMethodAttr(cir::MethodType methodTy,
+                                                 const CXXMethodDecl *md) = 0;
+
 public:
   /// Similar to AddedStructorArgs, but only notes the number of additional
   /// arguments.
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 4ce38f4e0a1f9..48ceb742a8787 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -2235,9 +2235,8 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr 
*ce) {
 
     const MemberPointerType *mpt = ce->getType()->getAs<MemberPointerType>();
     if (mpt->isMemberFunctionPointerType()) {
-      cgf.cgm.errorNYI(subExpr->getSourceRange(),
-                       "CK_NullToMemberPointer: member function pointer");
-      return {};
+      auto Ty = mlir::cast<cir::MethodType>(cgf.convertType(destTy));
+      return builder.getNullMethodPtr(Ty, cgf.getLoc(subExpr->getExprLoc()));
     }
 
     auto ty = mlir::cast<cir::DataMemberType>(cgf.convertType(destTy));
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp 
b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index b749336df4068..26465a804f1e6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -156,6 +156,9 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
                               cir::PointerType destCIRTy, bool isRefCast,
                               Address src) override;
 
+  cir::MethodAttr buildVirtualMethodAttr(cir::MethodType methodTy,
+                                         const CXXMethodDecl *md) override;
+
   Address initializeArrayCookie(CIRGenFunction &cgf, Address newPtr,
                                 mlir::Value numElements, const CXXNewExpr *e,
                                 QualType elementType) override;
@@ -2195,6 +2198,25 @@ mlir::Value 
CIRGenItaniumCXXABI::emitDynamicCast(CIRGenFunction &cgf,
                                         isRefCast, castInfo);
 }
 
+cir::MethodAttr
+CIRGenItaniumCXXABI::buildVirtualMethodAttr(cir::MethodType methodTy,
+                                            const CXXMethodDecl *md) {
+  assert(md->isVirtual() && "only deal with virtual member functions");
+
+  uint64_t index = cgm.getItaniumVTableContext().getMethodVTableIndex(md);
+  uint64_t vtableOffset;
+  if (cgm.getItaniumVTableContext().isRelativeLayout()) {
+    // Multiply by 4-byte relative offsets.
+    vtableOffset = index * 4;
+  } else {
+    const ASTContext &astContext = cgm.getASTContext();
+    CharUnits pointerWidth = astContext.toCharUnitsFromBits(
+        astContext.getTargetInfo().getPointerWidth(LangAS::Default));
+    vtableOffset = index * pointerWidth.getQuantity();
+  }
+
+  return cir::MethodAttr::get(methodTy, vtableOffset);
+}
 /// The Itanium ABI always places an offset to the complete object
 /// at entry -2 in the vtable.
 void CIRGenItaniumCXXABI::emitVirtualObjectDelete(
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 44fe9cbd96879..d223158f99e9e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1529,12 +1529,9 @@ mlir::Value 
CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) {
   // A member function pointer.
   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 {};
-    }
+    if (methodDecl->isVirtual())
+      return cir::ConstantOp::create(
+          builder, loc, getCXXABI().buildVirtualMethodAttr(ty, methodDecl));
 
     cir::FuncOp methodFuncOp = getAddrOfFunction(methodDecl);
     return cir::ConstantOp::create(builder, loc,
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp 
b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 2f4240c385cab..43bd33759fba9 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -305,6 +305,18 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> 
emitError,
 // MethodAttr definitions
 
//===----------------------------------------------------------------------===//
 
+LogicalResult MethodAttr::verify(function_ref<InFlightDiagnostic()> emitError,
+                                 cir::MethodType type,
+                                 std::optional<FlatSymbolRefAttr> symbol,
+                                 std::optional<uint64_t> vtable_offset) {
+  if (symbol.has_value() && vtable_offset.has_value())
+    return emitError()
+           << "at most one of symbol and vtable_offset can be present "
+              "in #cir.method";
+
+  return success();
+}
+
 Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
   auto ty = mlir::cast<cir::MethodType>(odsType);
 
@@ -331,15 +343,30 @@ Attribute MethodAttr::parse(AsmParser &parser, Type 
odsType) {
     return get(ty, symbol);
   }
 
-  return {};
+  // Parse a uint64 that represents the vtable offset.
+  std::uint64_t vtableOffset = 0;
+  if (parser.parseKeyword("vtable_offset"))
+    return {};
+  if (parser.parseEqual())
+    return {};
+  if (parser.parseInteger(vtableOffset))
+    return {};
+
+  if (parser.parseGreater())
+    return {};
+
+  return get(ty, vtableOffset);
 }
 
 void MethodAttr::print(AsmPrinter &printer) const {
   auto symbol = getSymbol();
+  auto vtableOffset = getVtableOffset();
 
   printer << '<';
   if (symbol.has_value()) {
     printer << *symbol;
+  } else if (vtableOffset.has_value()) {
+    printer << "vtable_offset = " << *vtableOffset;
   } else {
     printer << "null";
   }
diff --git 
a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp 
b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 94342f864fca6..a66d5f3cbb7a4 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -202,7 +202,29 @@ mlir::TypedAttr LowerItaniumCXXABI::lowerMethodConstant(
         loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {zero, 
zero}));
   }
 
-  assert(!cir::MissingFeatures::virtualMethodAttr());
+  if (attr.isVirtual()) {
+    if (useARMMethodPtrABI) {
+      // ARM C++ ABI 3.2.1:
+      //   This ABI specifies that adj contains twice the this
+      //   adjustment, plus 1 if the member function is virtual. The
+      //   least significant bit of adj then makes exactly the same
+      //   discrimination as the least significant bit of ptr does for
+      //   Itanium.
+      llvm_unreachable("ARM method ptr abi NYI");
+    }
+
+    // Itanium C++ ABI 2.3.2:
+    //
+    //   In the standard representation, a member function pointer for a
+    //   virtual function is represented with ptr set to 1 plus the function's
+    //   v-table entry offset (in bytes), converted to a function pointer as if
+    //   by reinterpret_cast<fnptr_t>(uintfnptr_t(1 + offset)), where
+    //   uintfnptr_t is an unsigned integer of the same size as fnptr_t.
+    auto ptr =
+        cir::IntAttr::get(ptrdiffCIRTy, 1 + attr.getVtableOffset().value());
+    return cir::ConstRecordAttr::get(
+        loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, zero}));
+  }
 
   // Itanium C++ ABI 2.3.2:
   //
diff --git a/clang/test/CIR/CodeGen/pointer-to-member-func.cpp 
b/clang/test/CIR/CodeGen/pointer-to-member-func.cpp
index ad081d0d06dbc..4dedee3e48c45 100644
--- a/clang/test/CIR/CodeGen/pointer-to-member-func.cpp
+++ b/clang/test/CIR/CodeGen/pointer-to-member-func.cpp
@@ -39,6 +39,60 @@ auto make_non_virtual() -> void (Foo::*)(int) {
 // OGCG: define {{.*}} { i64, i64 } @_Z16make_non_virtualv()
 // OGCG:   ret { i64, i64 } { i64 ptrtoint (ptr @_ZN3Foo2m1Ei to i64), i64 0 }
 
+auto make_virtual() -> void (Foo::*)(int) {
+  return &Foo::m3;
+}
+
+// CIR-BEFORE: cir.func {{.*}} @_Z12make_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<vtable_offset = 8> 
: !cir.method<!cir.func<(!s32i)> in !rec_Foo>
+// CIR-BEFORE:   cir.store %[[METHOD_PTR]], %[[RETVAL]]
+// CIR-BEFORE:   %[[RET:.*]] = cir.load %[[RETVAL]] : 
!cir.ptr<!cir.method<!cir.func<(!s32i)> in !rec_Foo>>, 
!cir.method<!cir.func<(!s32i)> in !rec_Foo>
+// CIR-BEFORE:   cir.return %[[RET]] : !cir.method<!cir.func<(!s32i)> in 
!rec_Foo>
+
+// CIR-AFTER: cir.func {{.*}} @_Z12make_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.int<9> 
: !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
+
+// LLVM: define {{.*}} @_Z12make_virtualv()
+// LLVM:   %[[RETVAL:.*]] = alloca { i64, i64 }
+// LLVM:   store { i64, i64 } { i64 9, i64 0 }, ptr %[[RETVAL]]
+// LLVM:   %[[RET:.*]] = load { i64, i64 }, ptr %[[RETVAL]]
+// LLVM:   ret { i64, i64 } %[[RET]]
+
+// OGCG: define {{.*}} @_Z12make_virtualv()
+// OGCG:   ret { i64, i64 } { i64 9, i64 0 }
+
+auto make_null() -> void (Foo::*)(int) {
+  return nullptr;
+}
+
+// CIR-BEFORE: cir.func {{.*}} @_Z9make_nullv() -> 
!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<null> : 
!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: cir.func {{.*}} @_Z9make_nullv() -> !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.int<0> 
: !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
+
+// LLVM: define {{.*}} @_Z9make_nullv()
+// LLVM:   %[[RETVAL:.*]] = alloca { i64, i64 }
+// LLVM:   store { i64, i64 } zeroinitializer, ptr %[[RETVAL]]
+// LLVM:   %[[RET:.*]] = load { i64, i64 }, ptr %[[RETVAL]]
+// LLVM:   ret { i64, i64 } %[[RET]]
+
+// OGCG: define {{.*}} @_Z9make_nullv()
+// OGCG:   ret { i64, i64 } zeroinitializer
+
 void call(Foo *obj, void (Foo::*func)(int), int arg) {
   (obj->*func)(arg);
 }

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

Reply via email to