Skip to content

Commit 1af4960

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 b1eee7c commit 1af4960

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
@@ -697,6 +697,10 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> {
697697
gives the offset of the vtable entry corresponding to the virtual member
698698
function.
699699

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

712718
let builders = [
713719
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type), [{
714-
return $_get(type.getContext(), type, std::nullopt, std::nullopt);
720+
return $_get(type.getContext(), type, std::nullopt, std::nullopt, 0);
715721
}]>,
716722
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
717723
"mlir::FlatSymbolRefAttr":$symbol), [{
718-
return $_get(type.getContext(), type, symbol, std::nullopt);
724+
return $_get(type.getContext(), type, symbol, std::nullopt, 0);
725+
}]>,
726+
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
727+
"mlir::FlatSymbolRefAttr":$symbol,
728+
"int64_t":$adjustment), [{
729+
return $_get(type.getContext(), type, symbol, std::nullopt, adjustment);
719730
}]>,
720731
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
721732
"uint64_t":$vtable_offset), [{
722-
return $_get(type.getContext(), type, std::nullopt, vtable_offset);
733+
return $_get(type.getContext(), type, std::nullopt, vtable_offset, 0);
734+
}]>,
735+
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
736+
"uint64_t":$vtable_offset,
737+
"int64_t":$adjustment), [{
738+
return $_get(type.getContext(), type, std::nullopt, vtable_offset, adjustment);
723739
}]>,
724740
];
725741

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

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

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

420435
/// 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
@@ -547,7 +547,8 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> emitError,
547547
LogicalResult MethodAttr::verify(function_ref<InFlightDiagnostic()> emitError,
548548
cir::MethodType type,
549549
std::optional<FlatSymbolRefAttr> symbol,
550-
std::optional<uint64_t> vtable_offset) {
550+
std::optional<uint64_t> vtable_offset,
551+
int64_t adjustment) {
551552
if (symbol.has_value() && vtable_offset.has_value())
552553
return emitError()
553554
<< "at most one of symbol and vtable_offset can be present "
@@ -576,9 +577,18 @@ Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
576577
if (parseSymbolRefResult.has_value()) {
577578
if (parseSymbolRefResult.value().failed())
578579
return {};
580+
581+
// Try to parse optional adjustment.
582+
int64_t adjustment = 0;
583+
if (parser.parseOptionalComma().succeeded()) {
584+
if (parser.parseKeyword("adjustment") || parser.parseEqual() ||
585+
parser.parseInteger(adjustment))
586+
return {};
587+
}
588+
579589
if (parser.parseGreater())
580590
return {};
581-
return get(ty, symbol);
591+
return get(ty, symbol, adjustment);
582592
}
583593

584594
// Parse a uint64 that represents the vtable offset.
@@ -590,21 +600,36 @@ Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
590600
if (parser.parseInteger(vtableOffset))
591601
return {};
592602

603+
// Try to parse optional adjustment.
604+
int64_t adjustment = 0;
605+
if (parser.parseOptionalComma().succeeded()) {
606+
if (parser.parseKeyword("adjustment") || parser.parseEqual() ||
607+
parser.parseInteger(adjustment))
608+
return {};
609+
}
610+
593611
if (parser.parseGreater())
594612
return {};
595613

596-
return get(ty, vtableOffset);
614+
return get(ty, vtableOffset, adjustment);
597615
}
598616

599617
void MethodAttr::print(AsmPrinter &printer) const {
600618
auto symbol = getSymbol();
601619
auto vtableOffset = getVtableOffset();
620+
auto adjustment = getAdjustment();
602621

603622
printer << '<';
604623
if (symbol.has_value()) {
605624
printer << *symbol;
625+
if (adjustment != 0) {
626+
printer << ", adjustment = " << adjustment;
627+
}
606628
} else if (vtableOffset.has_value()) {
607629
printer << "vtable_offset = " << *vtableOffset;
630+
if (adjustment != 0) {
631+
printer << ", adjustment = " << adjustment;
632+
}
608633
} else {
609634
printer << "null";
610635
}

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)