Skip to content

[Clang] Add support for GCC bound member functions extension #135649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

dtcxzyw
Copy link
Member

@dtcxzyw dtcxzyw commented Apr 14, 2025

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

Related issue: #22495
Closes #82727

Copy link

github-actions bot commented Apr 15, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@dtcxzyw dtcxzyw force-pushed the clang-bpmf-support branch 3 times, most recently from fa2948d to 43c487c Compare April 15, 2025 16:24
@dtcxzyw dtcxzyw force-pushed the clang-bpmf-support branch from 43c487c to 24ed3a2 Compare April 16, 2025 10:27
@dtcxzyw dtcxzyw marked this pull request as ready for review April 16, 2025 10:36
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. clang:static analyzer ClangIR Anything related to the ClangIR project labels Apr 16, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 16, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Yingwei Zheng (dtcxzyw)

Changes

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

Closes #82727


Patch is 29.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/135649.diff

19 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/AST/OperationKinds.def (+4)
  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+16-16)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4)
  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+1)
  • (modified) clang/lib/AST/Expr.cpp (+7)
  • (modified) clang/lib/AST/ExprConstant.cpp (+2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+2)
  • (modified) clang/lib/CodeGen/CGExprComplex.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+32)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+20-2)
  • (modified) clang/lib/Edit/RewriteObjCFoundationAPI.cpp (+1)
  • (modified) clang/lib/Sema/SemaCast.cpp (+40-20)
  • (modified) clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp (+2-1)
  • (added) clang/test/CodeGenCXX/pmf-conversions.cpp (+105)
  • (added) clang/test/SemaCXX/pmf-conversions.cpp (+54)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 5af4c08f64cd8..768497712ecd6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -96,6 +96,11 @@ C++ Language Changes
       asm((std::string_view("nop")) ::: (std::string_view("memory")));
     }
 
+- Implemented `GCC bound member functions extension <https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html>`_ for Itanium ABI.
+  This extension allows extracting the function pointer from a bound pointer to member function.
+  It is useful to save vtable lookups when the same member function is executed multiple times inside a loop.
+  When using this extension, a warning is emitted unless ``-Wno-pmf-conversions`` is passed.
+
 C++2c Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
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 3f7499d8656bd..5c882df7fcbdf 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5144,6 +5144,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 59c0e47c7c195..e42d626acacb6 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 cffe5c5cd1ec3..1c4a06ad8d61d 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 abb88477062fc..bf22a62d20c11 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5417,6 +5417,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 b21ebeee4bed1..ca3a2cd9af3dd 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 8dbbcdaef25d8..fa64df8be8c27 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2628,6 +2628,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 70b53be7e77a3..6e4732ea977a9 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -926,7 +926,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();
@@ -973,6 +974,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;
@@ -1047,7 +1050,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();
 
@@ -1057,6 +1061,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 14e16bc39eb3a..fe1e485cc92a6 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -153,6 +153,11 @@ namespace {
     bool isPlaceholder(BuiltinType::Kind K) const {
       return PlaceholderKind == K;
     }
+    bool isBoundPMFConversion() const {
+      return isPlaceholder(BuiltinType::BoundMember) &&
+             DestType->isFunctionPointerType() &&
+             SrcExpr.get()->isBoundMemberFunction(Self.Context);
+    }
 
     // Language specific cast restrictions for address spaces.
     void checkAddressSpaceCast(QualType SrcType, QualType DestType);
@@ -254,13 +259,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,
@@ -1234,9 +1237,10 @@ 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) && !isBoundPMFConversion())
+      SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
+  } else
     checkNonOverloadPlaceholders();
   if (SrcExpr.isInvalid()) // if conversion failed, don't report another error
     return;
@@ -2363,6 +2367,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
@@ -2729,9 +2743,11 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
       return;
     }
 
-    checkNonOverloadPlaceholders();
-    if (SrcExpr.isInvalid())
-      return;
+    if (!isBoundPMFConversion()) {
+      checkNonOverloadPlaceholders();
+      if (SrcExpr.isInvalid())
+        return;
+    }
   }
 
   // C++ 5.2.9p4: Any expression can be explicitly converted to type "cv void".
@@ -2769,7 +2785,7 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
   }
 
   if (ValueKind == VK_PRValue && !DestType->isRecordType() &&
-      !isPlaceholder(BuiltinType::Overload)) {
+      !isPlaceholder(BuiltinType::Overload) && !isBoundPMFConversion()) {
     SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
     if (SrcExpr.isInvalid())
       return;
@@ -2827,12 +2843,16 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
       return;
 
     if (tcr == TC_NotApplicable) {
-      // ... or if that is not possible, a static_cast, ignoring const and
-      // addr space, ...
-      tcr = TryStaticCast(Self, SrcExpr, DestType, CCK, OpRange, msg, Kind,
-                          BasePath, ListInitialization);
-      if (SrcExpr.isInvalid())
-        return;
+      // FIXME: Bound member function to function pointer conversion is blocked
+      // by an immediat...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Apr 16, 2025

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Yingwei Zheng (dtcxzyw)

Changes

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

Closes #82727


Patch is 29.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/135649.diff

19 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/AST/OperationKinds.def (+4)
  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+16-16)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4)
  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+1)
  • (modified) clang/lib/AST/Expr.cpp (+7)
  • (modified) clang/lib/AST/ExprConstant.cpp (+2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+2)
  • (modified) clang/lib/CodeGen/CGExprComplex.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+32)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+20-2)
  • (modified) clang/lib/Edit/RewriteObjCFoundationAPI.cpp (+1)
  • (modified) clang/lib/Sema/SemaCast.cpp (+40-20)
  • (modified) clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp (+2-1)
  • (added) clang/test/CodeGenCXX/pmf-conversions.cpp (+105)
  • (added) clang/test/SemaCXX/pmf-conversions.cpp (+54)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 5af4c08f64cd8..768497712ecd6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -96,6 +96,11 @@ C++ Language Changes
       asm((std::string_view("nop")) ::: (std::string_view("memory")));
     }
 
+- Implemented `GCC bound member functions extension <https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html>`_ for Itanium ABI.
+  This extension allows extracting the function pointer from a bound pointer to member function.
+  It is useful to save vtable lookups when the same member function is executed multiple times inside a loop.
+  When using this extension, a warning is emitted unless ``-Wno-pmf-conversions`` is passed.
+
 C++2c Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
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 3f7499d8656bd..5c882df7fcbdf 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5144,6 +5144,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 59c0e47c7c195..e42d626acacb6 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 cffe5c5cd1ec3..1c4a06ad8d61d 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 abb88477062fc..bf22a62d20c11 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5417,6 +5417,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 b21ebeee4bed1..ca3a2cd9af3dd 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 8dbbcdaef25d8..fa64df8be8c27 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2628,6 +2628,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 70b53be7e77a3..6e4732ea977a9 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -926,7 +926,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();
@@ -973,6 +974,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;
@@ -1047,7 +1050,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();
 
@@ -1057,6 +1061,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 14e16bc39eb3a..fe1e485cc92a6 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -153,6 +153,11 @@ namespace {
     bool isPlaceholder(BuiltinType::Kind K) const {
       return PlaceholderKind == K;
     }
+    bool isBoundPMFConversion() const {
+      return isPlaceholder(BuiltinType::BoundMember) &&
+             DestType->isFunctionPointerType() &&
+             SrcExpr.get()->isBoundMemberFunction(Self.Context);
+    }
 
     // Language specific cast restrictions for address spaces.
     void checkAddressSpaceCast(QualType SrcType, QualType DestType);
@@ -254,13 +259,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,
@@ -1234,9 +1237,10 @@ 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) && !isBoundPMFConversion())
+      SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
+  } else
     checkNonOverloadPlaceholders();
   if (SrcExpr.isInvalid()) // if conversion failed, don't report another error
     return;
@@ -2363,6 +2367,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
@@ -2729,9 +2743,11 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
       return;
     }
 
-    checkNonOverloadPlaceholders();
-    if (SrcExpr.isInvalid())
-      return;
+    if (!isBoundPMFConversion()) {
+      checkNonOverloadPlaceholders();
+      if (SrcExpr.isInvalid())
+        return;
+    }
   }
 
   // C++ 5.2.9p4: Any expression can be explicitly converted to type "cv void".
@@ -2769,7 +2785,7 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
   }
 
   if (ValueKind == VK_PRValue && !DestType->isRecordType() &&
-      !isPlaceholder(BuiltinType::Overload)) {
+      !isPlaceholder(BuiltinType::Overload) && !isBoundPMFConversion()) {
     SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
     if (SrcExpr.isInvalid())
       return;
@@ -2827,12 +2843,16 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
       return;
 
     if (tcr == TC_NotApplicable) {
-      // ... or if that is not possible, a static_cast, ignoring const and
-      // addr space, ...
-      tcr = TryStaticCast(Self, SrcExpr, DestType, CCK, OpRange, msg, Kind,
-                          BasePath, ListInitialization);
-      if (SrcExpr.isInvalid())
-        return;
+      // FIXME: Bound member function to function pointer conversion is blocked
+      // by an immediat...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Apr 16, 2025

@llvm/pr-subscribers-clangir

Author: Yingwei Zheng (dtcxzyw)

Changes

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

Closes #82727


Patch is 29.68 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/135649.diff

19 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+5)
  • (modified) clang/include/clang/AST/OperationKinds.def (+4)
  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+16-16)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+4)
  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+1)
  • (modified) clang/lib/AST/Expr.cpp (+7)
  • (modified) clang/lib/AST/ExprConstant.cpp (+2)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprAgg.cpp (+2)
  • (modified) clang/lib/CodeGen/CGExprComplex.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+1)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+32)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+20-2)
  • (modified) clang/lib/Edit/RewriteObjCFoundationAPI.cpp (+1)
  • (modified) clang/lib/Sema/SemaCast.cpp (+40-20)
  • (modified) clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp (+2-1)
  • (added) clang/test/CodeGenCXX/pmf-conversions.cpp (+105)
  • (added) clang/test/SemaCXX/pmf-conversions.cpp (+54)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 5af4c08f64cd8..768497712ecd6 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -96,6 +96,11 @@ C++ Language Changes
       asm((std::string_view("nop")) ::: (std::string_view("memory")));
     }
 
+- Implemented `GCC bound member functions extension <https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html>`_ for Itanium ABI.
+  This extension allows extracting the function pointer from a bound pointer to member function.
+  It is useful to save vtable lookups when the same member function is executed multiple times inside a loop.
+  When using this extension, a warning is emitted unless ``-Wno-pmf-conversions`` is passed.
+
 C++2c Feature Support
 ^^^^^^^^^^^^^^^^^^^^^
 
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 3f7499d8656bd..5c882df7fcbdf 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5144,6 +5144,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 59c0e47c7c195..e42d626acacb6 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 cffe5c5cd1ec3..1c4a06ad8d61d 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 abb88477062fc..bf22a62d20c11 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5417,6 +5417,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 b21ebeee4bed1..ca3a2cd9af3dd 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 8dbbcdaef25d8..fa64df8be8c27 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2628,6 +2628,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 70b53be7e77a3..6e4732ea977a9 100644
--- a/clang/lib/CodeGen/ItaniumCXXABI.cpp
+++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp
@@ -926,7 +926,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();
@@ -973,6 +974,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;
@@ -1047,7 +1050,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();
 
@@ -1057,6 +1061,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 14e16bc39eb3a..fe1e485cc92a6 100644
--- a/clang/lib/Sema/SemaCast.cpp
+++ b/clang/lib/Sema/SemaCast.cpp
@@ -153,6 +153,11 @@ namespace {
     bool isPlaceholder(BuiltinType::Kind K) const {
       return PlaceholderKind == K;
     }
+    bool isBoundPMFConversion() const {
+      return isPlaceholder(BuiltinType::BoundMember) &&
+             DestType->isFunctionPointerType() &&
+             SrcExpr.get()->isBoundMemberFunction(Self.Context);
+    }
 
     // Language specific cast restrictions for address spaces.
     void checkAddressSpaceCast(QualType SrcType, QualType DestType);
@@ -254,13 +259,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,
@@ -1234,9 +1237,10 @@ 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) && !isBoundPMFConversion())
+      SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
+  } else
     checkNonOverloadPlaceholders();
   if (SrcExpr.isInvalid()) // if conversion failed, don't report another error
     return;
@@ -2363,6 +2367,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
@@ -2729,9 +2743,11 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
       return;
     }
 
-    checkNonOverloadPlaceholders();
-    if (SrcExpr.isInvalid())
-      return;
+    if (!isBoundPMFConversion()) {
+      checkNonOverloadPlaceholders();
+      if (SrcExpr.isInvalid())
+        return;
+    }
   }
 
   // C++ 5.2.9p4: Any expression can be explicitly converted to type "cv void".
@@ -2769,7 +2785,7 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
   }
 
   if (ValueKind == VK_PRValue && !DestType->isRecordType() &&
-      !isPlaceholder(BuiltinType::Overload)) {
+      !isPlaceholder(BuiltinType::Overload) && !isBoundPMFConversion()) {
     SrcExpr = Self.DefaultFunctionArrayLvalueConversion(SrcExpr.get());
     if (SrcExpr.isInvalid())
       return;
@@ -2827,12 +2843,16 @@ void CastOperation::CheckCXXCStyleCast(bool FunctionalStyle,
       return;
 
     if (tcr == TC_NotApplicable) {
-      // ... or if that is not possible, a static_cast, ignoring const and
-      // addr space, ...
-      tcr = TryStaticCast(Self, SrcExpr, DestType, CCK, OpRange, msg, Kind,
-                          BasePath, ListInitialization);
-      if (SrcExpr.isInvalid())
-        return;
+      // FIXME: Bound member function to function pointer conversion is blocked
+      // by an immediat...
[truncated]

@@ -0,0 +1,54 @@
// RUN: %clang_cc1 -triple %itanium_abi_triple -fsyntax-only %s -verify
Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html

Related issue: #22495 Closes #82727

I think this requires an RFC justifying carrying the extension. It's a non-conforming extension that we previously decided we would not implement (#22495 was closed as WONTFIX), and there's no real motivation for the feature request listed in #82727.

(Personally, I think #82727 should be closed as a duplicate of #22495 unless there's a very strong justification for carrying the extension.)

@dtcxzyw
Copy link
Member Author

dtcxzyw commented Apr 16, 2025

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html
Related issue: #22495 Closes #82727

I think this requires an RFC justifying carrying the extension. It's a non-conforming extension that we previously decided we would not implement (#22495 was closed as WONTFIX), and there's no real motivation for the feature request listed in #82727.

(Personally, I think #82727 should be closed as a duplicate of #22495 unless there's a very strong justification for carrying the extension.)

#82727 was filed because this extension is used by topling-zip: https://github.com/topling/topling-zip/blob/4c57da837bf1a1612a1524ddf8d258a69f1c3874/src/terark/zbs/blob_store.hpp#L56-L68

I decided to implement this feature after I read the post by @rockeet :https://zhuanlan.zhihu.com/p/683534962 (in Chinese). TBH I don't think this feature is useful as modern compilers should be smart enough to optimize out redundant vtable lookups.

cc @rockeet Are you willing to propose an RFC in https://discourse.llvm.org/ to demonstrate its real-world usefulness in ToplingDB?

@erichkeane
Copy link
Collaborator

I too don't think this is an extension that we really want. It is a pretty awful extension that does some pretty awful things to the language (and isn't implemented in a way that 'works right' in GCC anyway). Unless there is a REALLY important workload that we absolutely need to compile, I'd prefer we don't.

Copy link
Collaborator

@shafik shafik left a comment

Choose a reason for hiding this comment

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

I am going to chime in, in support of both Aaron's and Erich's position here. This does not seem like an extension we want to support.

So given the objections, we need at minimum an RFC and see how that goes. Maybe during that discussion a more compelling case can be made.

@rockeet
Copy link

rockeet commented Apr 19, 2025

This feature is very useful for improving performance:

We used this extension to improve virtual function calling performance, there are simple and small virtual functions which are frequently called and can not be eliminated and it is in a delegation thus compiler can not optimize.

toplingdb and topling-zip used pmf to pre-bind the functions in several places, like this:

#if defined(_MSC_VER) || defined(__clang__)
  #define IF_BOUND_PMF(Then, Else) Else
#else
  #define IF_BOUND_PMF(Then, Else) Then
#endif
  void SetSubReader(const ToplingZipSubReader* sub) {
    subReader_ = sub;
    iter_ = sub->index_->NewIterator();
    store_ = sub->store_.get();
    get_record_append_ = store_->m_get_record_append_CacheOffsets;
   #if defined(_MSC_VER) || defined(__clang__)
   #else
    // bind the virtual function by pmf
    iter_next_ = (IterScanFN)(iter_->*(&COIndex::Iterator::Next));
    iter_prev_ = (IterScanFN)(iter_->*(&COIndex::Iterator::Prev));
   #endif
    tag_rs_kind_ = sub->tag_rs_kind_;
  }

  inline bool IndexIterInvokeNext() {
    return IF_BOUND_PMF(iter_next_(iter_), iter_->Next());
  }
  inline bool IndexIterInvokePrev() {
    return IF_BOUND_PMF(iter_prev_(iter_), iter_->Prev());
  }

In which iter_->Next() is a small virtual function for different index type, we save the pmf as a member, thus reduced the virtual function calling overhead.

@rockeet
Copy link

rockeet commented Apr 19, 2025

This patch adds support for GCC bound member functions extension: https://gcc.gnu.org/onlinedocs/gcc/Bound-member-functions.html
Related issue: #22495 Closes #82727

I think this requires an RFC justifying carrying the extension. It's a non-conforming extension that we previously decided we would not implement (#22495 was closed as WONTFIX), and there's no real motivation for the feature request listed in #82727.
(Personally, I think #82727 should be closed as a duplicate of #22495 unless there's a very strong justification for carrying the extension.)

#82727 was filed because this extension is used by topling-zip: https://github.com/topling/topling-zip/blob/4c57da837bf1a1612a1524ddf8d258a69f1c3874/src/terark/zbs/blob_store.hpp#L56-L68

I decided to implement this feature after I read the post by @rockeet :https://zhuanlan.zhihu.com/p/683534962 (in Chinese). TBH I don't think this feature is useful as modern compilers should be smart enough to optimize out redundant vtable lookups.

cc @rockeet Are you willing to propose an RFC in https://discourse.llvm.org/ to demonstrate its real-world usefulness in ToplingDB?

Yes, I'm very eager for this feature, let me investigate the RFC proposal routine.

@AaronBallman
Copy link
Collaborator

Yes, I'm very eager for this feature, let me investigate the RFC proposal routine.

I would recommend you also include details about how the extension meets our usual criteria when you write the RFC.

@efriedma-quic
Copy link
Collaborator

Not sure if anyone has mentioned this elsewhere, but there's a significant problem with the type of the returned function pointer: a normal function pointer doesn't necessarily have the correct calling convention. In practice, this is likely to cause problems on Windows.

@hubert-reinterpretcast
Copy link
Collaborator

We used this extension to improve virtual function calling performance, there are simple and small virtual functions which are frequently called and can not be eliminated and it is in a delegation thus compiler can not optimize.

toplingdb and topling-zip used pmf to pre-bind the functions in several places, like this:

Can declcall (https://wg21.link/p2825) be used for this? It is on track for C++26.

@zygoloid
Copy link
Collaborator

Not sure if anyone has mentioned this elsewhere, but there's a significant problem with the type of the returned function pointer: a normal function pointer doesn't necessarily have the correct calling convention. In practice, this is likely to cause problems on Windows.

The GCC extension is also broken if a this adjustment is needed, even when the calling convention happens to line up. (And it's not possible to detect this problem locally -- it can happen even if there is no multiple inheritance visible at the point where the extension is used.)

declcall doesn't have these problems and seems like a more robust way forward. (It does introduce some unnecessary overhead still, though, such as branching on whether the pointer-to-member is for a virtual function and emitting dead code for a virtual call; maybe we could consider some mechanism to declaratively state that a pointer-to-member type is known to not be a virtual pointer if we really care about that.)

@dtcxzyw
Copy link
Member Author

dtcxzyw commented Apr 22, 2025

We used this extension to improve virtual function calling performance, there are simple and small virtual functions which are frequently called and can not be eliminated and it is in a delegation thus compiler can not optimize.
toplingdb and topling-zip used pmf to pre-bind the functions in several places, like this:

Can declcall (https://wg21.link/p2825) be used for this? It is on track for C++26.

IIRC this feature is orthogonal to GCC bound member functions. declcall with a virtual method just returns a pmf, and we still need to perform vtable lookup at the callsite: https://compiler-explorer.com/z/YrT3nPTEz

@hubert-reinterpretcast
Copy link
Collaborator

IIRC this feature is orthogonal to GCC bound member functions. declcall with a virtual method just returns a pmf, and we still need to perform vtable lookup at the callsite: https://compiler-explorer.com/z/YrT3nPTEz

Try declcall(p->B::virtual_method())?: https://compiler-explorer.com/z/7d9KPE8zK

@rockeet
Copy link

rockeet commented Apr 22, 2025

Yes, I'm very eager for this feature, let me investigate the RFC proposal routine.

I would recommend you also include details about how the extension meets our usual criteria when you write the RFC.

Thank you for your information, I'm new to llvm project, I've written the [RFC] Implement gcc Bound PMF in clang .

@dtcxzyw
Copy link
Member Author

dtcxzyw commented Apr 22, 2025

IIRC this feature is orthogonal to GCC bound member functions. declcall with a virtual method just returns a pmf, and we still need to perform vtable lookup at the callsite: https://compiler-explorer.com/z/YrT3nPTEz

Try declcall(p->B::virtual_method())?: https://compiler-explorer.com/z/7d9KPE8zK

We don't know which function is called until the object p is provided.
See case 2 in https://discourse.llvm.org/t/rfc-implement-gcc-bound-pmf-in-clang/85951.

Comment on lines +45 to +47
// CHECK: [[MEMPTR_NONVIRTUAL]]:
// CHECK-NEXT: [[MEMPTR_NONVIRTUALFN:%.*]] = inttoptr i64 [[METHOD_COERCE0]] to ptr
// CHECK-NEXT: br label %[[MEMPTR_END]]
Copy link
Member

Choose a reason for hiding this comment

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

Seems like the non-virtual path does not take into account the offset in the pmf, and unfortunately this is also what gcc is doing. Although this PR is primarily for the virtual path, the erroneous non-virtual path could really cause problems very easily.

@zygoloid
Copy link
Collaborator

We don't know which function is called until the object p is provided. See case 2 in https://discourse.llvm.org/t/rfc-implement-gcc-bound-pmf-in-clang/85951.

Perhaps we could provide a builtin function that takes an object pointer and a pointer-to-member-function and returns a devirtualized pointer-to-member-function (or returns the original PMF if it wasn't a pointer to a virtual function)? Unlike the GCC extension, that kind of interface can actually work in general.

@lichray
Copy link

lichray commented Apr 22, 2025

We used this extension to improve virtual function calling performance, there are simple and small virtual functions which are frequently called and can not be eliminated and it is in a delegation thus compiler can not optimize. [...]

Can declcall (https://wg21.link/p2825) be used for this? It is on track for C++26.

Not according to the design as of R5, because the operand of declcall is an unevaluated operand, hence it will not walk into this value to find the entry in the actual vtable. Devirtualized pointer in p2825r5 can only be obtained when qualified-id is involved.

@lichray
Copy link

lichray commented Apr 22, 2025

Perhaps we could provide a builtin function that takes an object pointer and a pointer-to-member-function and returns a devirtualized pointer-to-member-function (or returns the original PMF if it wasn't a pointer to a virtual function)? Unlike the GCC extension, that kind of interface can actually work in general.

I suggest viewing the demands at the same time and taking a leveled approach:

  1. invoking base implementation via a handle
  2. saving a snapshot of a pending virtual call (delegate)
  3. resolve virtual call on one object, apply the results on other objects

Each level is less safe than the previous one. 1 slightly troubles Liskov substitution, 2 doesn't work before the object establishes the runtime type (in a constructor), and 3 can apply the call to objects without such a runtime type.

1 is addressed by p2825.

C++26 std::function_ref (with nontype<pmf>) can address 2 at the cost of a thunk, usually optimized by sibling call optimization, and safe to use, but you can argue that "simply" assigning from the bit pattern of a C++Builder __closure object may just be what user wants.

The dynamic portion of GCC's bound method can address 3, but the feature can also be deemed a building block of __closure.

Imagine that you can evaluate p->mem_fn, literally a pending virtual call, and get a struct of two members, this will allow the use of structured binding:

auto [bpf, pobj] = p->mem_fn;

Then, to fulfill 3, we only need a way to package the components back into such a struct to substitute the pobj with a pointer to a different object:

auto rebound_method = decltype(p->mem_fn){ bpf, pother };
rebound_method();

This option is explored by C folks in n2862. In which paper, the rebinding procedure is done by the wide_set_context API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:static analyzer clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

clang compile error on gcc bound member function