[clang] [CIR] Upstream support for null and virtual method pointers (PR #176522)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jan 16 17:09:25 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
@llvm/pr-subscribers-clang
Author: Andy Kaylor (andykaylor)
<details>
<summary>Changes</summary>
This upstreams support for pointer-to-member value representations of null and virtual member pointers and the lowering of virtual member pointers.
---
Full diff: https://github.com/llvm/llvm-project/pull/176522.diff
11 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+4)
- (modified) clang/include/clang/CIR/Dialect/IR/CIRAttrs.td (+23-8)
- (modified) clang/include/clang/CIR/MissingFeatures.h (-1)
- (modified) clang/lib/CIR/CodeGen/CIRGenBuilder.h (+4)
- (modified) clang/lib/CIR/CodeGen/CIRGenCXXABI.h (+3)
- (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+2-3)
- (modified) clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp (+22)
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+3-6)
- (modified) clang/lib/CIR/Dialect/IR/CIRAttrs.cpp (+28-1)
- (modified) clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp (+23-1)
- (modified) clang/test/CIR/CodeGen/pointer-to-member-func.cpp (+54)
``````````diff
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);
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/176522
More information about the cfe-commits
mailing list