https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/174640
This adds code for generating cir.method attributes and lowering them to LLVM IR to implement support the C++ method pointer variables. >From ebdb617d94b778e7d0466aec9be37e993858d893 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Thu, 11 Dec 2025 10:48:09 -0800 Subject: [PATCH] [CIR] Upstream CIR method attribute handling This adds code for generating cir.method attributes and lowering them to LLVM IR to implement support the C++ method pointer variables. --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 5 ++ .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 43 +++++++++++++++++ clang/include/clang/CIR/MissingFeatures.h | 1 + clang/lib/CIR/CodeGen/CIRGenModule.cpp | 15 ++++-- clang/lib/CIR/Dialect/IR/CIRAttrs.cpp | 44 +++++++++++++++++ clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 4 +- .../CIR/Dialect/Transforms/CXXABILowering.cpp | 9 ++++ .../Transforms/TargetLowering/CIRCXXABI.h | 6 +++ .../TargetLowering/LowerItaniumCXXABI.cpp | 48 +++++++++++++++++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 15 +++--- .../CIR/CodeGen/pointer-to-member-func.cpp | 41 ++++++++-------- 11 files changed, 200 insertions(+), 31 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index cc28941aaa079..eadf3dd6ee0f0 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -179,6 +179,11 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return getPointerTo(cir::VoidType::get(getContext()), as); } + cir::MethodAttr getMethodAttr(cir::MethodType ty, cir::FuncOp methodFuncOp) { + auto methodFuncSymbolRef = mlir::FlatSymbolRefAttr::get(methodFuncOp); + return cir::MethodAttr::get(ty, methodFuncSymbolRef); + } + 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 c0279a0b20670..74ca19fb329b3 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -498,6 +498,49 @@ def CIR_DataMemberAttr : CIR_Attr<"DataMember", "data_member", [ }]; } +//===----------------------------------------------------------------------===// +// MethodAttr +//===----------------------------------------------------------------------===// + +def CIR_MethodAttr : CIR_Attr<"Method", "method", [TypedAttrInterface]> { + let summary = "Holds a constant pointer-to-member-function value"; + let description = [{ + A method attribute is a literal attribute that represents a constant + pointer-to-member-function value. + + 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 `symbol` is not present, the attribute represents a null pointer + constant. + }]; + + let parameters = (ins AttributeSelfTypeParameter< + "", "cir::MethodType">:$type, + OptionalParameter< + "std::optional<mlir::FlatSymbolRefAttr>">:$symbol); + + let builders = [ + AttrBuilderWithInferredContext<(ins "cir::MethodType":$type), [{ + return $_get(type.getContext(), type, std::nullopt); + }]>, + AttrBuilderWithInferredContext<(ins "cir::MethodType":$type, + "mlir::FlatSymbolRefAttr":$symbol), [{ + return $_get(type.getContext(), type, symbol); + }]>, + ]; + + let hasCustomAssemblyFormat = 1; + + let extraClassDeclaration = [{ + bool isNull() const { + return !getSymbol().has_value(); + } + }]; +} + //===----------------------------------------------------------------------===// // GlobalViewAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index acfc937a11993..e7b2d7ebbe113 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -345,6 +345,7 @@ 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/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 42df6304628dc..a995a4ceb601d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -1527,9 +1527,18 @@ mlir::Value CIRGenModule::emitMemberPointerConstant(const UnaryOperator *e) { const auto *decl = cast<DeclRefExpr>(e->getSubExpr())->getDecl(); // A member function pointer. - if (isa<CXXMethodDecl>(decl)) { - errorNYI(e->getSourceRange(), "emitMemberPointerConstant: method pointer"); - return {}; + 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 {}; + } + + auto methodFuncOp = getAddrOfFunction(methodDecl); + return cir::ConstantOp::create(builder, loc, + builder.getMethodAttr(ty, methodFuncOp)); } // Otherwise, a member data pointer. diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp index 59d7765198f9e..c69335ccf7960 100644 --- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp @@ -301,6 +301,50 @@ DataMemberAttr::verify(function_ref<InFlightDiagnostic()> emitError, return success(); } +//===----------------------------------------------------------------------===// +// MethodAttr definitions +//===----------------------------------------------------------------------===// + +Attribute MethodAttr::parse(AsmParser &parser, Type odsType) { + auto ty = mlir::cast<cir::MethodType>(odsType); + + if (parser.parseLess().failed()) + return {}; + + // Try to parse the null pointer constant. + if (parser.parseOptionalKeyword("null").succeeded()) { + if (parser.parseGreater()) + return {}; + return get(ty); + } + + // Try to parse a flat symbol ref for a pointer to non-virtual member + // function. + FlatSymbolRefAttr symbol; + auto parseSymbolRefResult = parser.parseOptionalAttribute(symbol); + if (parseSymbolRefResult.has_value()) { + if (parseSymbolRefResult.value().failed()) + return {}; + if (parser.parseGreater()) + return {}; + return get(ty, symbol); + } + + return {}; +} + +void MethodAttr::print(AsmPrinter &printer) const { + auto symbol = getSymbol(); + + printer << '<'; + if (symbol.has_value()) { + printer << *symbol; + } else { + printer << "null"; + } + printer << '>'; +} + //===----------------------------------------------------------------------===// // CIR ConstArrayAttr //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 8f0dc705181e6..0f1997ff8af6e 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -357,9 +357,9 @@ static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType, return success(); } - if (isa<cir::DataMemberAttr>(attrType)) { + if (isa<cir::DataMemberAttr, cir::MethodAttr>(attrType)) { // More detailed type verifications are already done in - // DataMemberAttr::verify. Don't need to repeat here. + // DataMemberAttr::verify or MethodAttr::verify. Don't need to repeat here. return success(); } diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp index 49087ca38d948..4ef277be8ce08 100644 --- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp @@ -171,6 +171,15 @@ mlir::LogicalResult CIRConstantOpABILowering::matchAndRewrite( return mlir::success(); } + if (mlir::isa<cir::MethodType>(op.getType())) { + auto method = mlir::cast<cir::MethodAttr>(op.getValue()); + mlir::DataLayout layout(op->getParentOfType<mlir::ModuleOp>()); + mlir::TypedAttr abiValue = lowerModule->getCXXABI().lowerMethodConstant( + method, layout, *getTypeConverter()); + rewriter.replaceOpWithNewOp<ConstantOp>(op, abiValue); + return mlir::success(); + } + llvm_unreachable("constant operand is not an CXXABI-dependent type"); } diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h index edfe29f1471cb..bcbda7b94a56f 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h @@ -59,6 +59,12 @@ class CIRCXXABI { lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy, mlir::Value loweredAddr, mlir::Value loweredMember, mlir::OpBuilder &builder) const = 0; + + /// Lower the given member function pointer constant to a constant of the ABI + /// type. The returned constant is represented as an attribute as well. + virtual mlir::TypedAttr + lowerMethodConstant(cir::MethodAttr attr, const mlir::DataLayout &layout, + const mlir::TypeConverter &typeConverter) const = 0; }; /// Creates an Itanium-family ABI. diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp index 323ecc46909d4..b3a858a072f8b 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp @@ -51,6 +51,10 @@ class LowerItaniumCXXABI : public CIRCXXABI { lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy, mlir::Value loweredAddr, mlir::Value loweredMember, mlir::OpBuilder &builder) const override; + + mlir::TypedAttr + lowerMethodConstant(cir::MethodAttr attr, const mlir::DataLayout &layout, + const mlir::TypeConverter &typeConverter) const override; }; } // namespace @@ -133,4 +137,48 @@ mlir::Operation *LowerItaniumCXXABI::lowerGetRuntimeMember( cir::CastKind::bitcast, memberBytesPtr); } +mlir::TypedAttr LowerItaniumCXXABI::lowerMethodConstant( + cir::MethodAttr attr, const mlir::DataLayout &layout, + const mlir::TypeConverter &typeConverter) const { + cir::IntType ptrdiffCIRTy = getPtrDiffCIRTy(lm); + auto loweredMethodTy = mlir::cast<cir::RecordType>( + lowerMethodType(attr.getType(), typeConverter)); + + auto zero = cir::IntAttr::get(ptrdiffCIRTy, 0); + + // Itanium C++ ABI 2.3.2: + // In all representations, the basic ABI properties of member function + // pointer types are those of the following class, where fnptr_t is the + // appropriate function-pointer type for a member function of this type: + // + // struct { + // fnptr_t ptr; + // ptrdiff_t adj; + // }; + + if (attr.isNull()) { + // Itanium C++ ABI 2.3.2: + // + // In the standard representation, a null member function pointer is + // represented with ptr set to a null pointer. The value of adj is + // unspecified for null member function pointers. + // + // clang CodeGen emits struct{null, null} for null member function pointers. + // Let's do the same here. + return cir::ConstRecordAttr::get( + loweredMethodTy, mlir::ArrayAttr::get(attr.getContext(), {zero, zero})); + } + + assert(!cir::MissingFeatures::virtualMethodAttr()); + + // Itanium C++ ABI 2.3.2: + // + // A member function pointer for a non-virtual member function is + // represented with ptr set to a pointer to the function, using the base + // 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})); +} + } // namespace cir diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 7686add38250e..03245cc6963e9 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -577,11 +577,11 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::GlobalViewAttr globalAttr) { mlir::LLVM::GEPNoWrapFlags::none); } - // The incubator has handling here for the attribute having integer type, but - // the only test case I could find that reaches it is a direct CIR-to-LLVM IR - // lowering with no clear indication of how the CIR might have been generated. - // We'll hit the unreachable below if this happens. - assert(!cir::MissingFeatures::globalViewIntLowering()); + if (auto intTy = mlir::dyn_cast<cir::IntType>(globalAttr.getType())) { + auto llvmDstTy = converter->convertType(globalAttr.getType()); + return mlir::LLVM::PtrToIntOp::create(rewriter, parentOp->getLoc(), + llvmDstTy, addrOp); + } if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(globalAttr.getType())) { mlir::Type llvmEltTy = @@ -1801,7 +1801,10 @@ mlir::LogicalResult CIRToLLVMConstantOpLowering::matchAndRewrite( } else if (mlir::isa<cir::IntType>(op.getType())) { // Lower GlobalViewAttr to llvm.mlir.addressof + llvm.mlir.ptrtoint if (auto ga = mlir::dyn_cast<cir::GlobalViewAttr>(op.getValue())) { - // See the comment in visitCirAttr for why this isn't implemented. + // We can have a global view with an integer type in the case of method + // pointers, but the lowering of those doesn't go through this path. + // They are handled in the visitCirAttr. This is left as an error until + // we have a test case that reaches it. assert(!cir::MissingFeatures::globalViewIntLowering()); op.emitError() << "global view with integer type"; return mlir::failure(); diff --git a/clang/test/CIR/CodeGen/pointer-to-member-func.cpp b/clang/test/CIR/CodeGen/pointer-to-member-func.cpp index e3fe05dfa34fa..47c5871e72290 100644 --- a/clang/test/CIR/CodeGen/pointer-to-member-func.cpp +++ b/clang/test/CIR/CodeGen/pointer-to-member-func.cpp @@ -12,28 +12,29 @@ struct Foo { virtual void m3(int); }; -void unused_pointer_to_member_func(void (Foo::*func)(int)) { +auto make_non_virtual() -> void (Foo::*)(int) { + return &Foo::m1; } -// CIR-BEFORE: cir.func {{.*}} @_Z29unused_pointer_to_member_funcM3FooFviE(%[[ARG:.*]]: !cir.method<!cir.func<(!s32i)> in !rec_Foo>) -// CIR-BEFORE: %[[FUNC:.*]] = cir.alloca !cir.method<!cir.func<(!s32i)> in !rec_Foo>, !cir.ptr<!cir.method<!cir.func<(!s32i)> in !rec_Foo>>, ["func", init] +// CIR-BEFORE: cir.func {{.*}} @_Z16make_non_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<@_ZN3Foo2m1Ei> : !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: !rec_anon_struct = !cir.record<struct {!s64i, !s64i}> -// CIR-AFTER: cir.func {{.*}} @_Z29unused_pointer_to_member_funcM3FooFviE(%[[ARG:.*]]: !rec_anon_struct {{.*}}) -// CIR-AFTER %[[FUNC:.*]] = cir.alloca !rec_anon_struct, !cir.ptr<!rec_anon_struct>, ["func", init] +// CIR-AFTER: cir.func {{.*}} @_Z16make_non_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.global_view<@_ZN3Foo2m1Ei> : !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 -// NOTE: The difference between LLVM and OGCG are due to the lack of calling convention handling in CIR. +// LLVM: define {{.*}} { i64, i64 } @_Z16make_non_virtualv() +// LLVM: %[[RETVAL:.*]] = alloca { i64, i64 } +// LLVM: store { i64, i64 } { i64 ptrtoint (ptr @_ZN3Foo2m1Ei to i64), i64 0 }, ptr %[[RETVAL]] +// LLVM: %[[RET:.*]] = load { i64, i64 }, ptr %[[RETVAL]] +// LLVM: ret { i64, i64 } %[[RET]] -// LLVM: define {{.*}} void @_Z29unused_pointer_to_member_funcM3FooFviE({ i64, i64 } %[[ARG:.*]]) -// LLVM: %[[FUNC:.*]] = alloca { i64, i64 } -// LLVM: store { i64, i64 } %[[ARG]], ptr %[[FUNC]] - -// OGCG: define {{.*}} void @_Z29unused_pointer_to_member_funcM3FooFviE(i64 %[[FUNC_COERCE0:.*]], i64 %[[FUNC_COERCE1:.*]]) -// OGCG: %[[FUNC:.*]] = alloca { i64, i64 } -// OGCG: %[[FUNC_ADDR:.*]] = alloca { i64, i64 } -// OGCG: %[[FUNC_0:.*]] = getelementptr inbounds nuw { i64, i64 }, ptr %[[FUNC]], i32 0, i32 0 -// OGCG: store i64 %[[FUNC_COERCE0]], ptr %[[FUNC_0]] -// OGCG: %[[FUNC_1:.*]] = getelementptr inbounds nuw { i64, i64 }, ptr %[[FUNC]], i32 0, i32 1 -// OGCG: store i64 %[[FUNC_COERCE1]], ptr %[[FUNC_1]] -// OGCG: %[[FUNC1:.*]] = load { i64, i64 }, ptr %[[FUNC]] -// OGCG: store { i64, i64 } %[[FUNC1]], ptr %[[FUNC_ADDR]] +// OGCG: define {{.*}} { i64, i64 } @_Z16make_non_virtualv() +// OGCG: ret { i64, i64 } { i64 ptrtoint (ptr @_ZN3Foo2m1Ei to i64), i64 0 } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
