Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,10 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> {
gives the offset of the vtable entry corresponding to the virtual member
function.

The `adjustment` parameter specifies the this-pointer adjustment required
for the member function pointer, in bytes. This is needed for multiple
inheritance and virtual base classes.

`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.
Expand All @@ -706,19 +710,31 @@ def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> {
OptionalParameter<
"std::optional<mlir::FlatSymbolRefAttr>">:$symbol,
OptionalParameter<
"std::optional<uint64_t>">:$vtable_offset);
"std::optional<uint64_t>">:$vtable_offset,
DefaultValuedParameter<
"int64_t", "0">:$adjustment);

let builders = [
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type), [{
return $_get(type.getContext(), type, std::nullopt, std::nullopt);
return $_get(type.getContext(), type, std::nullopt, std::nullopt, 0);
}]>,
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
"mlir::FlatSymbolRefAttr":$symbol), [{
return $_get(type.getContext(), type, symbol, std::nullopt);
return $_get(type.getContext(), type, symbol, std::nullopt, 0);
}]>,
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
"mlir::FlatSymbolRefAttr":$symbol,
"int64_t":$adjustment), [{
return $_get(type.getContext(), type, symbol, std::nullopt, adjustment);
}]>,
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
"uint64_t":$vtable_offset), [{
return $_get(type.getContext(), type, std::nullopt, vtable_offset);
return $_get(type.getContext(), type, std::nullopt, vtable_offset, 0);
}]>,
AttrBuilderWithInferredContext<(ins "cir::MethodType":$type,
"uint64_t":$vtable_offset,
"int64_t":$adjustment), [{
return $_get(type.getContext(), type, std::nullopt, vtable_offset, adjustment);
}]>,
];

Expand Down
15 changes: 15 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,21 @@ class CIRGenCXXABI {
virtual cir::MethodAttr buildVirtualMethodAttr(cir::MethodType MethodTy,
const CXXMethodDecl *MD) = 0;

/// Emit a member pointer constant from an APValue.
virtual mlir::TypedAttr emitMemberPointer(const APValue &memberPointer,
QualType mpType) = 0;

/// Build a member function pointer constant with the given method and
/// adjustment.
virtual mlir::TypedAttr
buildMemberFunctionPointer(cir::MethodType methodTy,
const CXXMethodDecl *methodDecl,
CharUnits thisAdjustment) = 0;

/// Build a member data pointer constant with the given field offset.
virtual mlir::TypedAttr buildMemberDataPointer(const MemberPointerType *mpt,
CharUnits offset) = 0;

/**************************** Array cookies ******************************/

/// Returns the extra size required in order to store the array
Expand Down
12 changes: 3 additions & 9 deletions clang/lib/CIR/CodeGen/CIRGenExprConst.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2033,16 +2033,10 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &Value,
case APValue::MemberPointer: {
assert(!cir::MissingFeatures::cxxABI());

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

if (isa<CXXMethodDecl>(memberDecl))
assert(0 && "not implemented");

auto cirTy = mlir::cast<cir::DataMemberType>(CGM.convertType(DestType));

const auto *fieldDecl = cast<FieldDecl>(memberDecl);
return builder.getDataMemberAttr(cirTy, fieldDecl->getFieldIndex());
return CGM.getCXXABI().emitMemberPointer(Value, DestType);
}
case APValue::LValue:
return ConstantLValueEmitter(*this, Value, DestType).tryEmit();
Expand Down
86 changes: 86 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,16 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
cir::MethodAttr buildVirtualMethodAttr(cir::MethodType MethodTy,
const CXXMethodDecl *MD) override;

mlir::TypedAttr emitMemberPointer(const APValue &memberPointer,
QualType mpType) override;

mlir::TypedAttr buildMemberFunctionPointer(cir::MethodType methodTy,
const CXXMethodDecl *methodDecl,
CharUnits thisAdjustment) override;

mlir::TypedAttr buildMemberDataPointer(const MemberPointerType *mpt,
CharUnits offset) override;

Address initializeArrayCookie(CIRGenFunction &CGF, Address NewPtr,
mlir::Value NumElements, const CXXNewExpr *E,
QualType ElementType) override;
Expand Down Expand Up @@ -2987,6 +2997,82 @@ CIRGenItaniumCXXABI::buildVirtualMethodAttr(cir::MethodType MethodTy,
return cir::MethodAttr::get(MethodTy, VTableOffset);
}

mlir::TypedAttr
CIRGenItaniumCXXABI::buildMemberFunctionPointer(cir::MethodType methodTy,
const CXXMethodDecl *methodDecl,
CharUnits thisAdjustment) {
assert(methodDecl->isInstance() && "Member function must not be static!");

// Get the function pointer (or index if this is a virtual function).
if (methodDecl->isVirtual()) {
uint64_t index =
CGM.getItaniumVTableContext().getMethodVTableIndex(methodDecl);
uint64_t vTableOffset;
if (CGM.getItaniumVTableContext().isRelativeLayout()) {
// Multiply by 4-byte relative offsets.
vTableOffset = index * 4;
} else {
const ASTContext &astContext = getContext();
CharUnits pointerWidth = astContext.toCharUnitsFromBits(
astContext.getTargetInfo().getPointerWidth(LangAS::Default));
vTableOffset = index * pointerWidth.getQuantity();
}

// Itanium C++ ABI 2.3:
// For a virtual function, [the pointer field] is 1 plus the
// virtual table offset (in bytes) of the function,
// represented as a ptrdiff_t.
return cir::MethodAttr::get(methodTy, vTableOffset,
thisAdjustment.getQuantity());
}

// For non-virtual functions, get the function symbol.
auto methodFuncOp = CGM.GetAddrOfFunction(methodDecl);
auto methodFuncSymbolRef = mlir::FlatSymbolRefAttr::get(methodFuncOp);
return cir::MethodAttr::get(methodTy, methodFuncSymbolRef,
thisAdjustment.getQuantity());
}

mlir::TypedAttr
CIRGenItaniumCXXABI::buildMemberDataPointer(const MemberPointerType *mpt,
CharUnits offset) {
// This method is not currently used for APValue emission.
// Data member pointers are handled directly in emitMemberPointer.
llvm_unreachable("NYI: buildMemberDataPointer");
}

mlir::TypedAttr
CIRGenItaniumCXXABI::emitMemberPointer(const APValue &memberPointer,
QualType mpType) {
const auto *mpt = mpType->castAs<MemberPointerType>();
const ValueDecl *mpd = memberPointer.getMemberPointerDecl();

if (!mpd) {
// Null member pointer.
if (mpt->isMemberDataPointer()) {
auto ty = mlir::cast<cir::DataMemberType>(CGM.convertType(mpType));
return cir::DataMemberAttr::get(ty);
}
// Null member function pointer.
auto ty = mlir::cast<cir::MethodType>(CGM.convertType(mpType));
return cir::MethodAttr::get(ty);
}

CharUnits thisAdjustment =
getContext().getMemberPointerPathAdjustment(memberPointer);

if (const auto *md = dyn_cast<CXXMethodDecl>(mpd)) {
auto ty = mlir::cast<cir::MethodType>(CGM.convertType(mpType));
return buildMemberFunctionPointer(ty, md, thisAdjustment);
}

// Data member pointer.
auto &builder = CGM.getBuilder();
auto ty = mlir::cast<cir::DataMemberType>(CGM.convertType(mpType));
const auto *fieldDecl = cast<FieldDecl>(mpd);
return builder.getDataMemberAttr(ty, fieldDecl->getFieldIndex());
}

/// The Itanium ABI requires non-zero initialization only for data
/// member pointers, for which '0' is a valid offset.
bool CIRGenItaniumCXXABI::isZeroInitializable(const MemberPointerType *MPT) {
Expand Down
31 changes: 28 additions & 3 deletions clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,8 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> emitError,
LogicalResult MethodAttr::verify(function_ref<InFlightDiagnostic()> emitError,
cir::MethodType type,
std::optional<FlatSymbolRefAttr> symbol,
std::optional<uint64_t> vtable_offset) {
std::optional<uint64_t> vtable_offset,
int64_t adjustment) {
if (symbol.has_value() && vtable_offset.has_value())
return emitError()
<< "at most one of symbol and vtable_offset can be present "
Expand Down Expand Up @@ -460,9 +461,18 @@ Attribute MethodAttr::parse(AsmParser &parser, Type odsType) {
if (parseSymbolRefResult.has_value()) {
if (parseSymbolRefResult.value().failed())
return {};

// Try to parse optional adjustment.
int64_t adjustment = 0;
if (parser.parseOptionalComma().succeeded()) {
if (parser.parseKeyword("adjustment") || parser.parseEqual() ||
parser.parseInteger(adjustment))
return {};
}

if (parser.parseGreater())
return {};
return get(ty, symbol);
return get(ty, symbol, adjustment);
}

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

// Try to parse optional adjustment.
int64_t adjustment = 0;
if (parser.parseOptionalComma().succeeded()) {
if (parser.parseKeyword("adjustment") || parser.parseEqual() ||
parser.parseInteger(adjustment))
return {};
}

if (parser.parseGreater())
return {};

return get(ty, vtableOffset);
return get(ty, vtableOffset, adjustment);
}

void MethodAttr::print(AsmPrinter &printer) const {
auto symbol = getSymbol();
auto vtableOffset = getVtableOffset();
auto adjustment = getAdjustment();

printer << '<';
if (symbol.has_value()) {
printer << *symbol;
if (adjustment != 0) {
printer << ", adjustment = " << adjustment;
}
} else if (vtableOffset.has_value()) {
printer << "vtable_offset = " << *vtableOffset;
if (adjustment != 0) {
printer << ", adjustment = " << adjustment;
}
} else {
printer << "null";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ mlir::TypedAttr ItaniumCXXABI::lowerMethodConstant(
lowerMethodType(attr.getType(), typeConverter));

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

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

// Itanium C++ ABI 2.3.2:
Expand All @@ -267,7 +268,7 @@ mlir::TypedAttr ItaniumCXXABI::lowerMethodConstant(
// 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}));
loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {ptr, adj}));
}

mlir::Operation *ItaniumCXXABI::lowerGetRuntimeMember(
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2730,6 +2730,18 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::lowerInitializer(
return lowerInitializer(rewriter, op, abiLlvmType, init,
useInitializerRegion);
}
if (auto methodAttr = mlir::dyn_cast<cir::MethodAttr>(init)) {
assert(lowerMod && "lower module is not available");
mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>());
mlir::TypedAttr abiValue = lowerMod->getCXXABI().lowerMethodConstant(
methodAttr, layout, *typeConverter);
init = abiValue;
auto abiLlvmType = convertTypeForMemory(*getTypeConverter(), dataLayout,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*abiLLVMType

abiValue.getType());
// Recursively lower the CIR attribute produced by the C++ ABI.
return lowerInitializer(rewriter, op, abiLlvmType, init,
useInitializerRegion);
}

op.emitError() << "unsupported initializer '" << init << "'";
return mlir::failure();
Expand Down
35 changes: 35 additions & 0 deletions clang/test/CIR/CodeGen/member-ptr-init.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s

// Test APValue emission for member function pointers with CIR, LLVM lowering,
// and comparison to original CodeGen.

struct S {
void foo();
virtual void bar();
};

// Test 1: Non-virtual member function pointer
// CIR: cir.global external @pmf1 = #cir.method<@_ZN1S3fooEv>
// LLVM: @pmf1 = global { i64, i64 } { i64 ptrtoint (ptr @_ZN1S3fooEv to i64), i64 0 }
// OGCG: @pmf1 = global { i64, i64 } { i64 ptrtoint (ptr @_ZN1S3fooEv to i64), i64 0 }
extern void (S::*pmf1)();
void (S::*pmf1)() = &S::foo;

// Test 2: Virtual member function pointer
// CIR: cir.global external @pmf2 = #cir.method<vtable_offset = {{[0-9]+}}>
// LLVM: @pmf2 = global { i64, i64 } { i64 {{[0-9]+}}, i64 0 }
// OGCG: @pmf2 = global { i64, i64 } { i64 {{[0-9]+}}, i64 0 }
extern void (S::*pmf2)();
void (S::*pmf2)() = &S::bar;

// Test 3: Null member function pointer
// CIR: cir.global external @pmf3 = #cir.method<null>
// LLVM: @pmf3 = global { i64, i64 } zeroinitializer
// OGCG: @pmf3 = global { i64, i64 } zeroinitializer
extern void (S::*pmf3)();
void (S::*pmf3)() = nullptr;