[clang] [Clang] Add support for GCC bound member functions extension (PR #135649)
Yingwei Zheng via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 15 08:03:16 PDT 2025
https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/135649
>From 445c08bcb007f157f6c66c5fabb01c2aa88b3a89 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 15 Apr 2025 22:58:44 +0800
Subject: [PATCH 1/2] [Clang] Add support for GCC bound member functions
extension
---
clang/include/clang/AST/OperationKinds.def | 4 +
clang/include/clang/Basic/DiagnosticGroups.td | 32 +++---
.../clang/Basic/DiagnosticSemaKinds.td | 4 +
clang/include/clang/CIR/Dialect/IR/CIROps.td | 1 +
clang/lib/AST/Expr.cpp | 7 ++
clang/lib/AST/ExprConstant.cpp | 2 +
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 1 +
clang/lib/CodeGen/CGExpr.cpp | 1 +
clang/lib/CodeGen/CGExprAgg.cpp | 2 +
clang/lib/CodeGen/CGExprComplex.cpp | 1 +
clang/lib/CodeGen/CGExprConstant.cpp | 1 +
clang/lib/CodeGen/CGExprScalar.cpp | 32 ++++++
clang/lib/CodeGen/ItaniumCXXABI.cpp | 22 +++-
clang/lib/Edit/RewriteObjCFoundationAPI.cpp | 1 +
clang/lib/Sema/SemaCast.cpp | 31 ++++--
clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp | 3 +-
clang/test/CodeGenCXX/pmf-conversions.cpp | 105 ++++++++++++++++++
clang/test/SemaCXX/pmf-conversions.cpp | 50 +++++++++
18 files changed, 271 insertions(+), 29 deletions(-)
create mode 100644 clang/test/CodeGenCXX/pmf-conversions.cpp
create mode 100644 clang/test/SemaCXX/pmf-conversions.cpp
diff --git a/clang/include/clang/AST/OperationKinds.def b/clang/include/clang/AST/OperationKinds.def
index 790dd572a7c99..489d89a697dc3 100644
--- a/clang/include/clang/AST/OperationKinds.def
+++ b/clang/include/clang/AST/OperationKinds.def
@@ -152,6 +152,10 @@ CAST_OPERATION(MemberPointerToBoolean)
/// many ABIs do not guarantee this on all possible intermediate types).
CAST_OPERATION(ReinterpretMemberPointer)
+/// CK_BoundPointerToMemberFunctionToFunctionPointer - Convert a bound
+/// member function pointer to a function pointer. This is a GNU extension.
+CAST_OPERATION(BoundMemberFunctionToFunctionPointer)
+
/// CK_UserDefinedConversion - Conversion using a user defined type
/// conversion function.
/// struct A { operator int(); }; int i = int(A());
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index d97bbfee2e4d5..8e5a4cba87c95 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -795,6 +795,7 @@ def DuplicateDeclSpecifier : DiagGroup<"duplicate-decl-specifier">;
def CompareDistinctPointerType : DiagGroup<"compare-distinct-pointer-types">;
def GNUUnionCast : DiagGroup<"gnu-union-cast">;
def GNUVariableSizedTypeNotAtEnd : DiagGroup<"gnu-variable-sized-type-not-at-end">;
+def GNUPMFCast : DiagGroup<"pmf-conversions">;
def Varargs : DiagGroup<"varargs">;
def XorUsedAsPow : DiagGroup<"xor-used-as-pow">;
@@ -1294,22 +1295,21 @@ def C2y : DiagGroup<"c2y-extensions">;
def GNUBinaryLiteral : DiagGroup<"gnu-binary-literal">;
// A warning group for warnings about GCC extensions.
-def GNU : DiagGroup<"gnu", [GNUAlignofExpression, GNUAnonymousStruct,
- GNUAutoType, GNUBinaryLiteral, GNUCaseRange,
- GNUComplexInteger, GNUCompoundLiteralInitializer,
- GNUConditionalOmittedOperand, GNUDesignator,
- GNUEmptyStruct,
- VLAExtension, GNUFlexibleArrayInitializer,
- GNUFlexibleArrayUnionMember, GNUFoldingConstant,
- GNUImaginaryConstant, GNUIncludeNext,
- GNULabelsAsValue, GNULineMarker, GNUNullPointerArithmetic,
- GNUOffsetofExtensions, GNUPointerArith,
- RedeclaredClassMember, GNURedeclaredEnum,
- GNUStatementExpression, GNUStaticFloatInit,
- GNUStringLiteralOperatorTemplate, GNUUnionCast,
- GNUVariableSizedTypeNotAtEnd, ZeroLengthArray,
- GNUZeroLineDirective,
- GNUZeroVariadicMacroArguments]>;
+def GNU
+ : DiagGroup<
+ "gnu", [GNUAlignofExpression, GNUAnonymousStruct, GNUAutoType,
+ GNUBinaryLiteral, GNUCaseRange, GNUComplexInteger,
+ GNUCompoundLiteralInitializer, GNUConditionalOmittedOperand,
+ GNUDesignator, GNUEmptyStruct, VLAExtension,
+ GNUFlexibleArrayInitializer, GNUFlexibleArrayUnionMember,
+ GNUFoldingConstant, GNUImaginaryConstant, GNUIncludeNext,
+ GNULabelsAsValue, GNULineMarker, GNUNullPointerArithmetic,
+ GNUOffsetofExtensions, GNUPointerArith, RedeclaredClassMember,
+ GNURedeclaredEnum, GNUStatementExpression, GNUStaticFloatInit,
+ GNUStringLiteralOperatorTemplate, GNUUnionCast,
+ GNUVariableSizedTypeNotAtEnd, ZeroLengthArray,
+ GNUZeroLineDirective, GNUZeroVariadicMacroArguments,
+ GNUPMFCast]>;
// A warning group for warnings about code that clang accepts but gcc doesn't.
def GccCompat : DiagGroup<"gcc-compat">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 180ca39bc07e9..57ff6435daee8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5120,6 +5120,10 @@ def err_ovl_unresolvable : Error<
def err_bound_member_function : Error<
"reference to non-static member function must be called"
"%select{|; did you mean to call it with no arguments?}0">;
+def ext_bound_member_function_conversion
+ : ExtWarn<"converting the bound member function %1 to a function pointer "
+ "%2 is a GNU extension">,
+ InGroup<GNUPMFCast>;
def note_possible_target_of_call : Note<"possible target for call">;
def err_no_viable_destructor : Error<
"no viable destructor found for class %0">;
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 0d3c2065cd58c..7dc299827916b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -109,6 +109,7 @@ def CK_ArrayToPointerDecay : I32EnumAttrCase<"array_to_ptrdecay", 11>;
// CK_DerivedToBaseMemberPointer
def CK_MemberPointerToBoolean : I32EnumAttrCase<"member_ptr_to_bool", 17>;
// CK_ReinterpretMemberPointer
+// CK_BoundMemberFunctionToFunctionPointer
// CK_UserDefinedConversion
// CK_ConstructorConversion
def CK_IntegralToPointer : I32EnumAttrCase<"int_to_ptr", 21>;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 4deed08d693ac..967fb42668902 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -1864,6 +1864,13 @@ bool CastExpr::CastConsistency() const {
assert(getSubExpr()->getType()->isMemberPointerType());
goto CheckNoBasePath;
+ case CK_BoundMemberFunctionToFunctionPointer:
+ assert(getType()->isFunctionPointerType());
+ assert(getSubExpr()->getType()->isMemberPointerType() ||
+ getSubExpr()->getType()->isSpecificPlaceholderType(
+ BuiltinType::BoundMember));
+ goto CheckNoBasePath;
+
case CK_BitCast:
// Arbitrary casts to C pointer types count as bitcasts.
// Otherwise, we should only have block and ObjC pointer casts
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d1cc722fb7945..b104a4e9416c0 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -15103,6 +15103,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
case CK_BaseToDerivedMemberPointer:
case CK_DerivedToBaseMemberPointer:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_ConstructorConversion:
case CK_IntegralToPointer:
case CK_ToVoid:
@@ -15960,6 +15961,7 @@ bool ComplexExprEvaluator::VisitCastExpr(const CastExpr *E) {
case CK_DerivedToBaseMemberPointer:
case CK_MemberPointerToBoolean:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_ConstructorConversion:
case CK_IntegralToPointer:
case CK_PointerToIntegral:
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index f0732a8ea60af..a6f4541cc7e80 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -83,6 +83,7 @@ Address CIRGenFunction::emitPointerWithAlignment(const Expr *expr,
case CK_NullToMemberPointer:
case CK_NullToPointer:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
// Common pointer conversions, nothing to do here.
// TODO: Is there any reason to treat base-to-derived conversions
// specially?
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 5f028f6d8c6ac..eeeb5c8f7a21f 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5387,6 +5387,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
case CK_BaseToDerivedMemberPointer:
case CK_MemberPointerToBoolean:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_AnyPointerToBlockPointerCast:
case CK_ARCProduceObject:
case CK_ARCConsumeObject:
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 87b2a73fb0c03..91fdf8a072111 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -1043,6 +1043,7 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) {
case CK_DerivedToBaseMemberPointer:
case CK_MemberPointerToBoolean:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_IntegralToPointer:
case CK_PointerToIntegral:
case CK_PointerToBoolean:
@@ -1600,6 +1601,7 @@ static bool castPreservesZero(const CastExpr *CE) {
case CK_MemberPointerToBoolean:
case CK_NullToMemberPointer:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
// FIXME: ABI-dependent.
return false;
diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp
index f556594f4a9ec..008082a493a2a 100644
--- a/clang/lib/CodeGen/CGExprComplex.cpp
+++ b/clang/lib/CodeGen/CGExprComplex.cpp
@@ -575,6 +575,7 @@ ComplexPairTy ComplexExprEmitter::EmitCast(CastKind CK, Expr *Op,
case CK_DerivedToBaseMemberPointer:
case CK_MemberPointerToBoolean:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_ConstructorConversion:
case CK_IntegralToPointer:
case CK_PointerToIntegral:
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index b016c6e36d1a8..bb665e6634e5a 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -1272,6 +1272,7 @@ class ConstExprEmitter
llvm_unreachable("builtin functions are handled elsewhere");
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_DerivedToBaseMemberPointer:
case CK_BaseToDerivedMemberPointer: {
auto C = Emitter.tryEmitPrivate(subExpr, subExpr->getType());
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index e9a7ba509350c..b2cca91f1b733 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2581,6 +2581,38 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) {
return CGF.CGM.getCXXABI().EmitNullMemberPointer(MPT);
}
+ case CK_BoundMemberFunctionToFunctionPointer: {
+ // Special handling bound member functions
+ if (E->isBoundMemberFunction(CGF.getContext())) {
+ auto *BO = cast<BinaryOperator>(E->IgnoreParens());
+ const Expr *BaseExpr = BO->getLHS();
+ const Expr *MemFnExpr = BO->getRHS();
+
+ const auto *MPT = MemFnExpr->getType()->castAs<MemberPointerType>();
+ const auto *FPT = MPT->getPointeeType()->castAs<FunctionProtoType>();
+ const auto *RD = MPT->getMostRecentCXXRecordDecl();
+
+ // Emit the 'this' pointer.
+ Address This = Address::invalid();
+ if (BO->getOpcode() == BO_PtrMemI)
+ This = CGF.EmitPointerWithAlignment(BaseExpr, nullptr, nullptr,
+ KnownNonNull);
+ else
+ This = CGF.EmitLValue(BaseExpr, KnownNonNull).getAddress();
+
+ // Get the member function pointer.
+ llvm::Value *MemFnPtr = CGF.EmitScalarExpr(MemFnExpr);
+
+ // Ask the ABI to load the callee. Note that This is modified.
+ llvm::Value *ThisPtrForCall = nullptr;
+ CGCallee Callee = CGF.CGM.getCXXABI().EmitLoadOfMemberFunctionPointer(
+ CGF, BO, This, ThisPtrForCall, MemFnPtr, MPT);
+ return Callee.getFunctionPointer();
+ }
+
+ // fallback to the case without the base object address
+ }
+ [[fallthrough]];
case CK_ReinterpretMemberPointer:
case CK_BaseToDerivedMemberPointer:
case CK_DerivedToBaseMemberPointer: {
diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp
index 35485dc6d867f..e846ed52f8d90 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -930,7 +930,8 @@ ItaniumCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF,
assert(E->getCastKind() == CK_DerivedToBaseMemberPointer ||
E->getCastKind() == CK_BaseToDerivedMemberPointer ||
- E->getCastKind() == CK_ReinterpretMemberPointer);
+ E->getCastKind() == CK_ReinterpretMemberPointer ||
+ E->getCastKind() == CK_BoundMemberFunctionToFunctionPointer);
CGBuilderTy &Builder = CGF.Builder;
QualType DstType = E->getType();
@@ -977,6 +978,8 @@ ItaniumCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF,
// Under Itanium, reinterprets don't require any additional processing.
if (E->getCastKind() == CK_ReinterpretMemberPointer) return src;
+ if (E->getCastKind() == CK_BoundMemberFunctionToFunctionPointer)
+ return Builder.CreateExtractValue(src, 0, "src.ptr");
llvm::Constant *adj = getMemberPointerAdjustment(E);
if (!adj) return src;
@@ -1051,7 +1054,8 @@ ItaniumCXXABI::EmitMemberPointerConversion(const CastExpr *E,
llvm::Constant *src) {
assert(E->getCastKind() == CK_DerivedToBaseMemberPointer ||
E->getCastKind() == CK_BaseToDerivedMemberPointer ||
- E->getCastKind() == CK_ReinterpretMemberPointer);
+ E->getCastKind() == CK_ReinterpretMemberPointer ||
+ E->getCastKind() == CK_BoundMemberFunctionToFunctionPointer);
QualType DstType = E->getType();
@@ -1061,6 +1065,20 @@ ItaniumCXXABI::EmitMemberPointerConversion(const CastExpr *E,
// Under Itanium, reinterprets don't require any additional processing.
if (E->getCastKind() == CK_ReinterpretMemberPointer) return src;
+ if (E->getCastKind() == CK_BoundMemberFunctionToFunctionPointer) {
+ llvm::Type *PtrTy = llvm::PointerType::getUnqual(CGM.getLLVMContext());
+ llvm::Constant *FuncPtr = llvm::ConstantExpr::getIntToPtr(
+ ConstantFoldExtractValueInstruction(src, 0), PtrTy);
+
+ const auto &NewAuthInfo = CGM.getFunctionPointerAuthInfo(DstType);
+ const auto &CurAuthInfo =
+ CGM.getMemberFunctionPointerAuthInfo(E->getSubExpr()->getType());
+
+ if (!NewAuthInfo && !CurAuthInfo)
+ return FuncPtr;
+
+ return pointerAuthResignConstant(FuncPtr, CurAuthInfo, NewAuthInfo, CGM);
+ }
// If the adjustment is trivial, we don't need to do anything.
llvm::Constant *adj = getMemberPointerAdjustment(E);
diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
index 627a1d6fb3dd5..51f1de0edae4b 100644
--- a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
+++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
@@ -1054,6 +1054,7 @@ static bool rewriteToNumericBoxedExpression(const ObjCMessageExpr *Msg,
case CK_DerivedToBaseMemberPointer:
case CK_MemberPointerToBoolean:
case CK_ReinterpretMemberPointer:
+ case CK_BoundMemberFunctionToFunctionPointer:
case CK_ConstructorConversion:
case CK_IntegralToPointer:
case CK_PointerToIntegral:
diff --git a/clang/lib/Sema/SemaCast.cpp b/clang/lib/Sema/SemaCast.cpp
index 2824dfce1572c..734ccfa2c4223 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -243,13 +243,11 @@ static TryCastResult TryStaticDowncast(Sema &Self, CanQualType SrcType,
QualType OrigDestType, unsigned &msg,
CastKind &Kind,
CXXCastPath &BasePath);
-static TryCastResult TryStaticMemberPointerUpcast(Sema &Self, ExprResult &SrcExpr,
- QualType SrcType,
- QualType DestType,bool CStyle,
- SourceRange OpRange,
- unsigned &msg,
- CastKind &Kind,
- CXXCastPath &BasePath);
+static TryCastResult
+TryStaticMemberPointerUpcast(Sema &Self, ExprResult &SrcExpr, QualType SrcType,
+ QualType DestType, bool CStyle,
+ SourceRange OpRange, unsigned &msg, CastKind &Kind,
+ CXXCastPath &BasePath);
static TryCastResult
TryStaticImplicitCast(Sema &Self, ExprResult &SrcExpr, QualType DestType,
@@ -1198,9 +1196,12 @@ static unsigned int checkCastFunctionType(Sema &Self, const ExprResult &SrcExpr,
/// like this:
/// char *bytes = reinterpret_cast\<char*\>(int_ptr);
void CastOperation::CheckReinterpretCast() {
- if (ValueKind == VK_PRValue && !isPlaceholder(BuiltinType::Overload))
- SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
- else
+ if (ValueKind == VK_PRValue) {
+ if (!isPlaceholder(BuiltinType::Overload) &&
+ !(isPlaceholder(BuiltinType::BoundMember) &&
+ DestType->isFunctionPointerType()))
+ SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
+ } else
checkNonOverloadPlaceholders();
if (SrcExpr.isInvalid()) // if conversion failed, don't report another error
return;
@@ -2327,6 +2328,16 @@ static TryCastResult TryReinterpretCast(Sema &Self, ExprResult &SrcExpr,
return TC_Success;
}
+ // GNU extension: check if we can convert a pmf to a function pointer
+ if (DestType->isFunctionPointerType() &&
+ (SrcType->isMemberFunctionPointerType() ||
+ SrcExpr.get()->isBoundMemberFunction(Self.Context)) &&
+ Self.Context.getTargetInfo().getCXXABI().isItaniumFamily()) {
+ Kind = CK_BoundMemberFunctionToFunctionPointer;
+ msg = diag::ext_bound_member_function_conversion;
+ return TC_Extension;
+ }
+
// See below for the enumeral issue.
if (SrcType->isNullPtrType() && DestType->isIntegralType(Self.Context)) {
// C++0x 5.2.10p4: A pointer can be explicitly converted to any integral
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index 3d0a69a515ab8..d28752febce43 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -524,7 +524,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
case CK_VectorSplat:
case CK_HLSLElementwiseCast:
case CK_HLSLAggregateSplatCast:
- case CK_HLSLVectorTruncation: {
+ case CK_HLSLVectorTruncation:
+ case CK_BoundMemberFunctionToFunctionPointer: {
QualType resultType = CastE->getType();
if (CastE->isGLValue())
resultType = getContext().getPointerType(resultType);
diff --git a/clang/test/CodeGenCXX/pmf-conversions.cpp b/clang/test/CodeGenCXX/pmf-conversions.cpp
new file mode 100644
index 0000000000000..c224870b19ff9
--- /dev/null
+++ b/clang/test/CodeGenCXX/pmf-conversions.cpp
@@ -0,0 +1,105 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --check-globals all --version 5
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -Wno-pmf-conversions %s -O3 -emit-llvm -o - | FileCheck %s
+
+struct A {
+ int data;
+//.
+// CHECK: @method = local_unnamed_addr global ptr @_ZN1A6methodEv, align 8
+//.
+// CHECK-LABEL: define linkonce_odr noundef i32 @_ZN1A6methodEv(
+// CHECK-SAME: ptr noundef nonnull align 8 dereferenceable(12) [[THIS:%.*]]) #[[ATTR0:[0-9]+]] comdat align 2 {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: ret i32 0
+//
+ int method() { return 0; }
+ virtual int virtual_method() { return 1; }
+ virtual ~A() = default;
+};
+
+struct C {
+ int data;
+};
+
+struct B : C, A {
+ virtual int virtual_method() override { return 2; }
+};
+
+using pmf_type = int (A::*)();
+using pf_type = int (*)(A*);
+
+pf_type method = reinterpret_cast<pf_type>(&A::method);
+
+// CHECK-LABEL: define dso_local noundef ptr @_Z11convert_pmfP1AMS_FivE(
+// CHECK-SAME: ptr noundef readonly captures(none) [[P:%.*]], i64 [[METHOD_COERCE0:%.*]], i64 [[METHOD_COERCE1:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[TMP0:%.*]] = and i64 [[METHOD_COERCE0]], 1
+// CHECK-NEXT: [[MEMPTR_ISVIRTUAL_NOT:%.*]] = icmp eq i64 [[TMP0]], 0
+// CHECK-NEXT: br i1 [[MEMPTR_ISVIRTUAL_NOT]], label %[[MEMPTR_NONVIRTUAL:.*]], label %[[MEMPTR_VIRTUAL:.*]]
+// CHECK: [[MEMPTR_VIRTUAL]]:
+// CHECK-NEXT: [[TMP1:%.*]] = getelementptr inbounds i8, ptr [[P]], i64 [[METHOD_COERCE1]]
+// CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr [[TMP1]], align 8, !tbaa [[TBAA2:![0-9]+]]
+// CHECK-NEXT: [[TMP2:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 [[METHOD_COERCE0]]
+// CHECK-NEXT: [[TMP3:%.*]] = getelementptr i8, ptr [[TMP2]], i64 -1
+// CHECK-NEXT: [[MEMPTR_VIRTUALFN:%.*]] = load ptr, ptr [[TMP3]], align 8, !nosanitize [[META5:![0-9]+]]
+// CHECK-NEXT: br label %[[MEMPTR_END:.*]]
+// CHECK: [[MEMPTR_NONVIRTUAL]]:
+// CHECK-NEXT: [[MEMPTR_NONVIRTUALFN:%.*]] = inttoptr i64 [[METHOD_COERCE0]] to ptr
+// CHECK-NEXT: br label %[[MEMPTR_END]]
+// CHECK: [[MEMPTR_END]]:
+// CHECK-NEXT: [[TMP4:%.*]] = phi ptr [ [[MEMPTR_VIRTUALFN]], %[[MEMPTR_VIRTUAL]] ], [ [[MEMPTR_NONVIRTUALFN]], %[[MEMPTR_NONVIRTUAL]] ]
+// CHECK-NEXT: ret ptr [[TMP4]]
+//
+pf_type convert_pmf(A* p, pmf_type method) {
+ return reinterpret_cast<pf_type>(p->*method);
+}
+
+// CHECK-LABEL: define dso_local noundef nonnull ptr @_Z17convert_pmf_constP1A(
+// CHECK-SAME: ptr noundef readnone captures(none) [[P:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: ret ptr @_ZN1A6methodEv
+//
+pf_type convert_pmf_const(A* p) {
+ return reinterpret_cast<pf_type>(p->*(&A::method));
+}
+
+// CHECK-LABEL: define dso_local noundef ptr @_Z18convert_vpmf_constP1A(
+// CHECK-SAME: ptr noundef readonly captures(none) [[P:%.*]]) local_unnamed_addr #[[ATTR1]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr [[P]], align 8, !tbaa [[TBAA2]]
+// CHECK-NEXT: [[MEMPTR_VIRTUALFN:%.*]] = load ptr, ptr [[VTABLE]], align 8, !nosanitize [[META5]]
+// CHECK-NEXT: ret ptr [[MEMPTR_VIRTUALFN]]
+//
+pf_type convert_vpmf_const(A* p) {
+ return reinterpret_cast<pf_type>(p->*(&A::virtual_method));
+}
+
+// CHECK-LABEL: define dso_local noundef range(i32 0, 2) i32 @_Z21call_b_virtual_methodP1B(
+// CHECK-SAME: ptr noundef [[P:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// CHECK-NEXT: [[ENTRY:.*:]]
+// CHECK-NEXT: [[VTABLE_I:%.*]] = load ptr, ptr [[P]], align 8, !tbaa [[TBAA2]]
+// CHECK-NEXT: [[MEMPTR_VIRTUALFN_I:%.*]] = load ptr, ptr [[VTABLE_I]], align 8, !nosanitize [[META5]]
+// CHECK-NEXT: [[CALL1:%.*]] = tail call noundef i32 [[MEMPTR_VIRTUALFN_I]](ptr noundef nonnull [[P]]) #[[ATTR3:[0-9]+]]
+// CHECK-NEXT: [[VTABLE:%.*]] = load ptr, ptr [[P]], align 8, !tbaa [[TBAA2]]
+// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[VTABLE]], align 8
+// CHECK-NEXT: [[CALL2:%.*]] = tail call noundef i32 [[TMP0]](ptr noundef nonnull align 8 dereferenceable(16) [[P]]) #[[ATTR3]]
+// CHECK-NEXT: [[CMP:%.*]] = icmp eq i32 [[CALL1]], [[CALL2]]
+// CHECK-NEXT: [[CONV:%.*]] = zext i1 [[CMP]] to i32
+// CHECK-NEXT: ret i32 [[CONV]]
+//
+int call_b_virtual_method(B* p) {
+ return convert_pmf(p, &A::virtual_method)(p) == p->virtual_method();
+}
+
+//.
+// CHECK: attributes #[[ATTR0]] = { mustprogress nounwind "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+// CHECK: attributes #[[ATTR1]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(read, inaccessiblemem: none) "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+// CHECK: attributes #[[ATTR2]] = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+// CHECK: attributes #[[ATTR3]] = { nounwind }
+//.
+// CHECK: [[META0:![0-9]+]] = !{i32 1, !"wchar_size", i32 4}
+// CHECK: [[META1:![0-9]+]] = !{!"{{.*}}clang version {{.*}}"}
+// CHECK: [[TBAA2]] = !{[[META3:![0-9]+]], [[META3]], i64 0}
+// CHECK: [[META3]] = !{!"vtable pointer", [[META4:![0-9]+]], i64 0}
+// CHECK: [[META4]] = !{!"Simple C++ TBAA"}
+// CHECK: [[META5]] = !{}
+//.
diff --git a/clang/test/SemaCXX/pmf-conversions.cpp b/clang/test/SemaCXX/pmf-conversions.cpp
new file mode 100644
index 0000000000000..e683d5d314072
--- /dev/null
+++ b/clang/test/SemaCXX/pmf-conversions.cpp
@@ -0,0 +1,50 @@
+// RUN: %clang_cc1 -triple %itanium_abi_triple -fsyntax-only %s -verify
+
+struct S {
+ int a;
+ void method();
+ void method_overload();
+ void method_overload(int);
+};
+
+using pmf_type = void (S::*)();
+using pm_type = int S::*;
+using pf_type = void (*)(S*);
+using pf_type_mismatched = void (*)(S*, int);
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpmf-conversions"
+
+void pmf_convert_no_object(pmf_type method, pm_type field) {
+ (void)reinterpret_cast<pf_type>(&S::method);
+ (void)reinterpret_cast<pf_type>(method);
+ (void)reinterpret_cast<pf_type>(((method)));
+ (void)(pf_type)(&S::method);
+ (void)(pf_type)(method);
+ (void)reinterpret_cast<pf_type_mismatched>(&S::method);
+ (void)reinterpret_cast<pf_type_mismatched>(method);
+ (void)reinterpret_cast<pf_type>(&S::a); // expected-error {{reinterpret_cast from 'int S::*' to 'pf_type' (aka 'void (*)(S *)') is not allowed}}
+ (void)reinterpret_cast<pf_type>(field); // expected-error {{reinterpret_cast from 'pm_type' (aka 'int S::*') to 'pf_type' (aka 'void (*)(S *)') is not allowed}}
+}
+
+void pmf_convert_with_base(S* p, S& r, pmf_type method, pm_type field) {
+ (void)reinterpret_cast<pf_type>(p->*(&S::method));
+ (void)reinterpret_cast<pf_type>(((p)->*((&S::method))));
+ (void)reinterpret_cast<pf_type>(p->*method);
+ (void)reinterpret_cast<pf_type>(((p)->*(method)));
+ (void)reinterpret_cast<pf_type>(p->*(static_cast<pmf_type>(&S::method_overload)));
+ (void)(pf_type)(p->*(&S::method)); // expected-error {{reference to non-static member function must be called; did you mean to call it with no arguments?}} expected-error {{cannot cast from type 'void' to pointer type 'pf_type' (aka 'void (*)(S *)')}}
+ (void)(pf_type)(p->*method); // expected-error {{reference to non-static member function must be called; did you mean to call it with no arguments?}} expected-error {{cannot cast from type 'void' to pointer type 'pf_type' (aka 'void (*)(S *)')}}
+ (void)reinterpret_cast<pf_type_mismatched>(p->*method);
+ (void)reinterpret_cast<pf_type>(r.*method);
+ (void)reinterpret_cast<pf_type_mismatched>(r.*method);
+ (void)reinterpret_cast<pf_type>(p->*(&S::a));
+ (void)reinterpret_cast<pf_type>(p->*field);
+}
+
+#pragma clang diagnostic pop
+
+void pmf_convert_warning(S *p, pmf_type method) {
+ (void)reinterpret_cast<pf_type>(method); // expected-warning {{converting the bound member function 'pmf_type' (aka 'void (S::*)()') to a function pointer 'pf_type' (aka 'void (*)(S *)') is a GNU extension}}
+ (void)reinterpret_cast<pf_type>(p->*method); // expected-warning {{converting the bound member function '<bound member function type>' to a function pointer 'pf_type' (aka 'void (*)(S *)') is a GNU extension}}
+}
>From fa2948de1d19b3b95deab89927e0b0a8c8bc98a5 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Tue, 15 Apr 2025 23:02:57 +0800
Subject: [PATCH 2/2] Add more tests. NFC.
---
clang/test/SemaCXX/pmf-conversions.cpp | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/clang/test/SemaCXX/pmf-conversions.cpp b/clang/test/SemaCXX/pmf-conversions.cpp
index e683d5d314072..5187ab0538b6b 100644
--- a/clang/test/SemaCXX/pmf-conversions.cpp
+++ b/clang/test/SemaCXX/pmf-conversions.cpp
@@ -15,6 +15,10 @@ using pf_type_mismatched = void (*)(S*, int);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpmf-conversions"
+// constexpr pmf conversions are not supported yet.
+constexpr pf_type method_constexpr = reinterpret_cast<pf_type>(&S::method); // expected-error {{constexpr variable 'method_constexpr' must be initialized by a constant expression}} expected-note {{reinterpret_cast is not allowed in a constant expression}}
+pf_type method = reinterpret_cast<pf_type>(&S::method);
+
void pmf_convert_no_object(pmf_type method, pm_type field) {
(void)reinterpret_cast<pf_type>(&S::method);
(void)reinterpret_cast<pf_type>(method);
More information about the cfe-commits
mailing list