Skip to content

Commit 5c5c237

Browse files
committed
[CIR] Implement APValue emission for member function pointers
This change implements APValue emission for member function pointers in constexpr contexts, fixing crashes when encountering member function pointers in constant expressions. Problem: Previously, attempting to emit a member function pointer in a constant expression would hit an assertion at CIRGenExprConst.cpp:2075 with "not implemented". This prevented compilation of valid C++ code that uses member function pointers as compile-time constants. Solution: The implementation follows CodeGen's approach in CGExprConstant.cpp, delegating to the CXX ABI for member pointer emission. Key changes: 1. Extended MethodAttr to include an 'adjustment' field for the this-pointer adjustment needed for multiple inheritance and virtual base classes, per Itanium C++ ABI 2.3.2 (member function pointers are represented as struct { fnptr_t ptr; ptrdiff_t adj }). 2. Added CIRGenCXXABI interface methods: - emitMemberPointer: Emit a member pointer from an APValue - buildMemberFunctionPointer: Build with method and adjustment - buildMemberDataPointer: Build data member pointer (currently NYI) 3. Implemented these methods in CIRGenItaniumCXXABI: - Virtual methods: Store vtable offset + 1 - Non-virtual methods: Store function pointer - Null pointers: Create null MethodAttr - All with proper this-pointer adjustments 4. Updated ConstantEmitter::tryEmitPrivate to delegate member pointer emission to the ABI implementation, with proper llvm_unreachable guard for NYI derived-to-base conversions 5. Updated lowering in ItaniumCXXABI.cpp to use the adjustment field when lowering MethodAttr to LLVM IR 6. Added support in LowerToLLVM.cpp for lowering MethodAttr global initializers by delegating to the ABI layer (matching the pattern used for DataMemberAttr) Testing (member-ptr-init.cpp): - CIR output: Validates #cir.method attributes for non-virtual, virtual, and null member function pointers - LLVM lowering: Validates correct { i64, i64 } struct layout matching Itanium ABI with function pointer/vtable offset and adjustment - OGCG comparison: Confirms CIR's LLVM output exactly matches original CodeGen for all test cases All 385 existing CIR CodeGen tests continue to pass. Implementation follows ClangIR conventions: - Copies CodeGen's delegation pattern to CXXABI - Has tests for CIR output, LLVM lowering, and OGCG comparison - All NYI code paths properly guarded with llvm_unreachable - Code formatted with clang-format
1 parent 94e34ef commit 5c5c237

File tree

8 files changed

+202
-18
lines changed

8 files changed

+202
-18
lines changed

clang/include/clang/CIR/Dialect/IR/CIRAttrs.td

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,10 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> {
696696
gives the offset of the vtable entry corresponding to the virtual member
697697
function.
698698

699+
The `adjustment` parameter specifies the this-pointer adjustment required
700+
for the member function pointer, in bytes. This is needed for multiple
701+
inheritance and virtual base classes.
702+
699703
`symbol` and `vtable_offset` cannot be present at the same time. If both of
700704
`symbol` and `vtable_offset` are not present, the attribute represents a
701705
null pointer constant.
@@ -706,19 +710,31 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> {
706710
OptionalParameter<
707711
"std::optional<mlir::FlatSymbolRefAttr>">:$symbol,
708712
OptionalParameter<
709-
"std::optional<uint64_t>">:$vtable_offset);
713+
"std::optional<uint64_t>">:$vtable_offset,
714+
DefaultValuedParameter<
715+
"int64_t", "0">:$adjustment);
710716

711717
let builders = [
712718
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type), [{
713-
return $_get(type.getContext(), type, std::nullopt, std::nullopt);
719+
return $_get(type.getContext(), type, std::nullopt, std::nullopt, 0);
714720
}]>,
715721
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
716722
"mlir::FlatSymbolRefAttr":$symbol), [{
717-
return $_get(type.getContext(), type, symbol, std::nullopt);
723+
return $_get(type.getContext(), type, symbol, std::nullopt, 0);
724+
}]>,
725+
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
726+
"mlir::FlatSymbolRefAttr":$symbol,
727+
"int64_t":$adjustment), [{
728+
return $_get(type.getContext(), type, symbol, std::nullopt, adjustment);
718729
}]>,
719730
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
720731
"uint64_t":$vtable_offset), [{
721-
return $_get(type.getContext(), type, std::nullopt, vtable_offset);
732+
return $_get(type.getContext(), type, std::nullopt, vtable_offset, 0);
733+
}]>,
734+
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
735+
"uint64_t":$vtable_offset,
736+
"int64_t":$adjustment), [{
737+
return $_get(type.getContext(), type, std::nullopt, vtable_offset, adjustment);
722738
}]>,
723739
];
724740

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,21 @@ class CIRGenCXXABI {
411411
virtual cir::MethodAttr buildVirtualMethodAttr(cir::MethodType MethodTy,
412412
const CXXMethodDecl *MD) = 0;
413413

414+
/// Emit a member pointer constant from an APValue.
415+
virtual mlir::TypedAttr emitMemberPointer(const APValue &memberPointer,
416+
QualType mpType) = 0;
417+
418+
/// Build a member function pointer constant with the given method and
419+
/// adjustment.
420+
virtual mlir::TypedAttr
421+
buildMemberFunctionPointer(cir::MethodType methodTy,
422+
const CXXMethodDecl *methodDecl,
423+
CharUnits thisAdjustment) = 0;
424+
425+
/// Build a member data pointer constant with the given field offset.
426+
virtual mlir::TypedAttr buildMemberDataPointer(const MemberPointerType *mpt,
427+
CharUnits offset) = 0;
428+
414429
/**************************** Array cookies ******************************/
415430

416431
/// Returns the extra size required in order to store the array

clang/lib/CIR/CodeGen/CIRGenExprConst.cpp

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2033,16 +2033,10 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &Value,
20332033
case APValue::MemberPointer: {
20342034
assert(!cir::MissingFeatures::cxxABI());
20352035

2036-
const ValueDecl *memberDecl = Value.getMemberPointerDecl();
2037-
assert(!Value.isMemberPointerToDerivedMember() && "NYI");
2036+
if (Value.isMemberPointerToDerivedMember())
2037+
llvm_unreachable("NYI: derived-to-base member pointer conversions");
20382038

2039-
if (isa<CXXMethodDecl>(memberDecl))
2040-
assert(0 && "not implemented");
2041-
2042-
auto cirTy = mlir::cast<cir::DataMemberType>(CGM.convertType(DestType));
2043-
2044-
const auto *fieldDecl = cast<FieldDecl>(memberDecl);
2045-
return builder.getDataMemberAttr(cirTy, fieldDecl->getFieldIndex());
2039+
return CGM.getCXXABI().emitMemberPointer(Value, DestType);
20462040
}
20472041
case APValue::LValue:
20482042
return ConstantLValueEmitter(*this, Value, DestType).tryEmit();

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,16 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
362362
cir::MethodAttr buildVirtualMethodAttr(cir::MethodType MethodTy,
363363
const CXXMethodDecl *MD) override;
364364

365+
mlir::TypedAttr emitMemberPointer(const APValue &memberPointer,
366+
QualType mpType) override;
367+
368+
mlir::TypedAttr buildMemberFunctionPointer(cir::MethodType methodTy,
369+
const CXXMethodDecl *methodDecl,
370+
CharUnits thisAdjustment) override;
371+
372+
mlir::TypedAttr buildMemberDataPointer(const MemberPointerType *mpt,
373+
CharUnits offset) override;
374+
365375
Address initializeArrayCookie(CIRGenFunction &CGF, Address NewPtr,
366376
mlir::Value NumElements, const CXXNewExpr *E,
367377
QualType ElementType) override;
@@ -2987,6 +2997,82 @@ CIRGenItaniumCXXABI::buildVirtualMethodAttr(cir::MethodType MethodTy,
29872997
return cir::MethodAttr::get(MethodTy, VTableOffset);
29882998
}
29892999

3000+
mlir::TypedAttr
3001+
CIRGenItaniumCXXABI::buildMemberFunctionPointer(cir::MethodType methodTy,
3002+
const CXXMethodDecl *methodDecl,
3003+
CharUnits thisAdjustment) {
3004+
assert(methodDecl->isInstance() && "Member function must not be static!");
3005+
3006+
// Get the function pointer (or index if this is a virtual function).
3007+
if (methodDecl->isVirtual()) {
3008+
uint64_t index =
3009+
CGM.getItaniumVTableContext().getMethodVTableIndex(methodDecl);
3010+
uint64_t vTableOffset;
3011+
if (CGM.getItaniumVTableContext().isRelativeLayout()) {
3012+
// Multiply by 4-byte relative offsets.
3013+
vTableOffset = index * 4;
3014+
} else {
3015+
const ASTContext &astContext = getContext();
3016+
CharUnits pointerWidth = astContext.toCharUnitsFromBits(
3017+
astContext.getTargetInfo().getPointerWidth(LangAS::Default));
3018+
vTableOffset = index * pointerWidth.getQuantity();
3019+
}
3020+
3021+
// Itanium C++ ABI 2.3:
3022+
// For a virtual function, [the pointer field] is 1 plus the
3023+
// virtual table offset (in bytes) of the function,
3024+
// represented as a ptrdiff_t.
3025+
return cir::MethodAttr::get(methodTy, vTableOffset,
3026+
thisAdjustment.getQuantity());
3027+
}
3028+
3029+
// For non-virtual functions, get the function symbol.
3030+
auto methodFuncOp = CGM.GetAddrOfFunction(methodDecl);
3031+
auto methodFuncSymbolRef = mlir::FlatSymbolRefAttr::get(methodFuncOp);
3032+
return cir::MethodAttr::get(methodTy, methodFuncSymbolRef,
3033+
thisAdjustment.getQuantity());
3034+
}
3035+
3036+
mlir::TypedAttr
3037+
CIRGenItaniumCXXABI::buildMemberDataPointer(const MemberPointerType *mpt,
3038+
CharUnits offset) {
3039+
// This method is not currently used for APValue emission.
3040+
// Data member pointers are handled directly in emitMemberPointer.
3041+
llvm_unreachable("NYI: buildMemberDataPointer");
3042+
}
3043+
3044+
mlir::TypedAttr
3045+
CIRGenItaniumCXXABI::emitMemberPointer(const APValue &memberPointer,
3046+
QualType mpType) {
3047+
const auto *mpt = mpType->castAs<MemberPointerType>();
3048+
const ValueDecl *mpd = memberPointer.getMemberPointerDecl();
3049+
3050+
if (!mpd) {
3051+
// Null member pointer.
3052+
if (mpt->isMemberDataPointer()) {
3053+
auto ty = mlir::cast<cir::DataMemberType>(CGM.convertType(mpType));
3054+
return cir::DataMemberAttr::get(ty);
3055+
}
3056+
// Null member function pointer.
3057+
auto ty = mlir::cast<cir::MethodType>(CGM.convertType(mpType));
3058+
return cir::MethodAttr::get(ty);
3059+
}
3060+
3061+
CharUnits thisAdjustment =
3062+
getContext().getMemberPointerPathAdjustment(memberPointer);
3063+
3064+
if (const auto *md = dyn_cast<CXXMethodDecl>(mpd)) {
3065+
auto ty = mlir::cast<cir::MethodType>(CGM.convertType(mpType));
3066+
return buildMemberFunctionPointer(ty, md, thisAdjustment);
3067+
}
3068+
3069+
// Data member pointer.
3070+
auto &builder = CGM.getBuilder();
3071+
auto ty = mlir::cast<cir::DataMemberType>(CGM.convertType(mpType));
3072+
const auto *fieldDecl = cast<FieldDecl>(mpd);
3073+
return builder.getDataMemberAttr(ty, fieldDecl->getFieldIndex());
3074+
}
3075+
29903076
/// The Itanium ABI requires non-zero initialization only for data
29913077
/// member pointers, for which '0' is a valid offset.
29923078
bool CIRGenItaniumCXXABI::isZeroInitializable(const MemberPointerType *MPT) {

clang/lib/CIR/Dialect/IR/CIRAttrs.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,8 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> emitError,
431431
LogicalResult MethodAttr::verify(function_ref<InFlightDiagnostic()> emitError,
432432
cir::MethodType type,
433433
std::optional<FlatSymbolRefAttr> symbol,
434-
std::optional<uint64_t> vtable_offset) {
434+
std::optional<uint64_t> vtable_offset,
435+
int64_t adjustment) {
435436
if (symbol.has_value() && vtable_offset.has_value())
436437
return emitError()
437438
<< "at most one of symbol and vtable_offset can be present "
@@ -460,9 +461,18 @@ Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
460461
if (parseSymbolRefResult.has_value()) {
461462
if (parseSymbolRefResult.value().failed())
462463
return {};
464+
465+
// Try to parse optional adjustment.
466+
int64_t adjustment = 0;
467+
if (parser.parseOptionalComma().succeeded()) {
468+
if (parser.parseKeyword("adjustment") || parser.parseEqual() ||
469+
parser.parseInteger(adjustment))
470+
return {};
471+
}
472+
463473
if (parser.parseGreater())
464474
return {};
465-
return get(ty, symbol);
475+
return get(ty, symbol, adjustment);
466476
}
467477

468478
// Parse a uint64 that represents the vtable offset.
@@ -474,21 +484,36 @@ Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
474484
if (parser.parseInteger(vtableOffset))
475485
return {};
476486

487+
// Try to parse optional adjustment.
488+
int64_t adjustment = 0;
489+
if (parser.parseOptionalComma().succeeded()) {
490+
if (parser.parseKeyword("adjustment") || parser.parseEqual() ||
491+
parser.parseInteger(adjustment))
492+
return {};
493+
}
494+
477495
if (parser.parseGreater())
478496
return {};
479497

480-
return get(ty, vtableOffset);
498+
return get(ty, vtableOffset, adjustment);
481499
}
482500

483501
void MethodAttr::print(AsmPrinter &printer) const {
484502
auto symbol = getSymbol();
485503
auto vtableOffset = getVtableOffset();
504+
auto adjustment = getAdjustment();
486505

487506
printer << '<';
488507
if (symbol.has_value()) {
489508
printer << *symbol;
509+
if (adjustment != 0) {
510+
printer << ", adjustment = " << adjustment;
511+
}
490512
} else if (vtableOffset.has_value()) {
491513
printer << "vtable_offset = " << *vtableOffset;
514+
if (adjustment != 0) {
515+
printer << ", adjustment = " << adjustment;
516+
}
492517
} else {
493518
printer << "null";
494519
}

clang/lib/CIR/Dialect/Transforms/TargetLowering/ItaniumCXXABI.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ mlir::TypedAttr ItaniumCXXABI::lowerMethodConstant(
212212
lowerMethodType(attr.getType(), typeConverter));
213213

214214
auto zero = cir::IntAttr::get(ptrdiffCIRTy, 0);
215+
auto adj = cir::IntAttr::get(ptrdiffCIRTy, attr.getAdjustment());
215216

216217
// Itanium C++ ABI 2.3.2:
217218
// In all representations, the basic ABI properties of member function
@@ -257,7 +258,7 @@ mlir::TypedAttr ItaniumCXXABI::lowerMethodConstant(
257258
auto ptr =
258259
cir::IntAttr::get(ptrdiffCIRTy, 1 + attr.getVtableOffset().value());
259260
return cir::ConstRecordAttr::get(
260-
loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, zero}));
261+
loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, adj}));
261262
}
262263

263264
// Itanium C++ ABI 2.3.2:
@@ -267,7 +268,7 @@ mlir::TypedAttr ItaniumCXXABI::lowerMethodConstant(
267268
// ABI's representation of function pointers.
268269
auto ptr = cir::GlobalViewAttr::get(ptrdiffCIRTy, attr.getSymbol().value());
269270
return cir::ConstRecordAttr::get(
270-
loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, zero}));
271+
loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, adj}));
271272
}
272273

273274
mlir::Operation *ItaniumCXXABI::lowerGetRuntimeMember(

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,6 +2746,18 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::lowerInitializer(
27462746
return lowerInitializer(rewriter, op, abiLlvmType, init,
27472747
useInitializerRegion);
27482748
}
2749+
if (auto methodAttr = mlir::dyn_cast<cir::MethodAttr>(init)) {
2750+
assert(lowerMod && "lower module is not available");
2751+
mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
2752+
mlir::TypedAttr abiValue = lowerMod->getCXXABI().lowerMethodConstant(
2753+
methodAttr, layout, *typeConverter);
2754+
init = abiValue;
2755+
auto abiLlvmType = convertTypeForMemory(*getTypeConverter(), dataLayout,
2756+
abiValue.getType());
2757+
// Recursively lower the CIR attribute produced by the C++ ABI.
2758+
return lowerInitializer(rewriter, op, abiLlvmType, init,
2759+
useInitializerRegion);
2760+
}
27492761

27502762
op.emitError() << "unsupported initializer '" << init << "'";
27512763
return mlir::failure();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
5+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s
7+
8+
// Test APValue emission for member function pointers with CIR, LLVM lowering,
9+
// and comparison to original CodeGen.
10+
11+
struct S {
12+
void foo();
13+
virtual void bar();
14+
};
15+
16+
// Test 1: Non-virtual member function pointer
17+
// CIR: cir.global external @pmf1 = #cir.method<@_ZN1S3fooEv>
18+
// LLVM: @pmf1 = global { i64, i64 } { i64 ptrtoint (ptr @_ZN1S3fooEv to i64), i64 0 }
19+
// OGCG: @pmf1 = global { i64, i64 } { i64 ptrtoint (ptr @_ZN1S3fooEv to i64), i64 0 }
20+
extern void (S::*pmf1)();
21+
void (S::*pmf1)() = &S::foo;
22+
23+
// Test 2: Virtual member function pointer
24+
// CIR: cir.global external @pmf2 = #cir.method<vtable_offset = {{[0-9]+}}>
25+
// LLVM: @pmf2 = global { i64, i64 } { i64 {{[0-9]+}}, i64 0 }
26+
// OGCG: @pmf2 = global { i64, i64 } { i64 {{[0-9]+}}, i64 0 }
27+
extern void (S::*pmf2)();
28+
void (S::*pmf2)() = &S::bar;
29+
30+
// Test 3: Null member function pointer
31+
// CIR: cir.global external @pmf3 = #cir.method<null>
32+
// LLVM: @pmf3 = global { i64, i64 } zeroinitializer
33+
// OGCG: @pmf3 = global { i64, i64 } zeroinitializer
34+
extern void (S::*pmf3)();
35+
void (S::*pmf3)() = nullptr;

0 commit comments

Comments
 (0)