]> granicus.if.org Git - clang/commitdiff
Split reinterpret_casts of member pointers out from CK_BitCast; this
authorJohn McCall <rjmccall@apple.com>
Wed, 15 Feb 2012 01:22:51 +0000 (01:22 +0000)
committerJohn McCall <rjmccall@apple.com>
Wed, 15 Feb 2012 01:22:51 +0000 (01:22 +0000)
is general goodness because representations of member pointers are
not always equivalent across member pointer types on all ABIs
(even though this isn't really standard-endorsed).

Take advantage of the new information to teach IR-generation how
to do these reinterprets in constant initializers.  Make sure this
works when intermingled with hierarchy conversions (although
this is not part of our motivating use case).  Doing this in the
constant-evaluator would probably have been better, but that would
require a *lot* of extra structure in the representation of
constant member pointers:  you'd really have to track an arbitrary
chain of hierarchy conversions and reinterpretations in order to
get this right.  Ultimately, this seems less complex.  I also
wasn't quite sure how to extend the constant evaluator to handle
foldings that we don't actually want to treat as extended
constant expressions.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@150551 91177308-0d34-0410-b5e6-96231b3b80d8

14 files changed:
include/clang/AST/OperationKinds.h
lib/AST/Expr.cpp
lib/AST/ExprConstant.cpp
lib/CodeGen/CGCXXABI.cpp
lib/CodeGen/CGCXXABI.h
lib/CodeGen/CGExpr.cpp
lib/CodeGen/CGExprAgg.cpp
lib/CodeGen/CGExprComplex.cpp
lib/CodeGen/CGExprConstant.cpp
lib/CodeGen/CGExprScalar.cpp
lib/CodeGen/ItaniumCXXABI.cpp
lib/Sema/SemaCast.cpp
lib/StaticAnalyzer/Core/ExprEngineC.cpp
test/CodeGenCXX/member-function-pointers.cpp

index 83480338461fdd28efdc05b128ce4c793e3939fe..8cf79b2bfa8718ad8812153f7d5bbb7b6e20f942 100644 (file)
@@ -117,6 +117,15 @@ enum CastKind {
   /// against the null member pointer.
   CK_MemberPointerToBoolean,
 
+  /// CK_ReinterpretMemberPointer - Reinterpret a member pointer as a
+  /// different kind of member pointer.  C++ forbids this from
+  /// crossing between function and object types, but otherwise does
+  /// not restrict it.  However, the only operation that is permitted
+  /// on a "punned" member pointer is casting it back to the original
+  /// type, which is required to be a lossless operation (although
+  /// many ABIs do not guarantee this on all possible intermediate types).
+  CK_ReinterpretMemberPointer,
+
   /// CK_UserDefinedConversion - Conversion using a user defined type
   /// conversion function.
   ///    struct A { operator int(); }; int i = int(A());
index 5b511cc88315c87dcb5317c0c818b40a8144b8dd..f9f7ba542cdff29ea056dd0c4e641a3f983dd310 100644 (file)
@@ -1053,6 +1053,11 @@ void CastExpr::CheckCastConsistency() const {
     assert(getSubExpr()->getType()->isBlockPointerType());
     goto CheckNoBasePath;
 
+  case CK_ReinterpretMemberPointer:
+    assert(getType()->isMemberPointerType());
+    assert(getSubExpr()->getType()->isMemberPointerType());
+    goto CheckNoBasePath;
+
   case CK_BitCast:
     // Arbitrary casts to C pointer types count as bitcasts.
     // Otherwise, we should only have block and ObjC pointer casts
@@ -1156,6 +1161,8 @@ const char *CastExpr::getCastKindName() const {
     return "BaseToDerivedMemberPointer";
   case CK_DerivedToBaseMemberPointer:
     return "DerivedToBaseMemberPointer";
+  case CK_ReinterpretMemberPointer:
+    return "ReinterpretMemberPointer";
   case CK_UserDefinedConversion:
     return "UserDefinedConversion";
   case CK_ConstructorConversion:
index e33ca6dc650e43b3705ea86d05d99bea4200f61d..6ad9938906007e5af35bf3955d1d5d19dbed4af2 100644 (file)
@@ -4974,6 +4974,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
   case CK_NullToMemberPointer:
   case CK_BaseToDerivedMemberPointer:
   case CK_DerivedToBaseMemberPointer:
+  case CK_ReinterpretMemberPointer:
   case CK_ConstructorConversion:
   case CK_IntegralToPointer:
   case CK_ToVoid:
@@ -5450,6 +5451,7 @@ bool ComplexExprEvaluator::VisitCastExpr(const CastExpr *E) {
   case CK_BaseToDerivedMemberPointer:
   case CK_DerivedToBaseMemberPointer:
   case CK_MemberPointerToBoolean:
+  case CK_ReinterpretMemberPointer:
   case CK_ConstructorConversion:
   case CK_IntegralToPointer:
   case CK_PointerToIntegral:
index e5d3f2da6b6ceb25f82084bbcc8defd6cac2f7dd..fc3f45d75bf59841134141ac839f3eb64b0749f4 100644 (file)
@@ -71,6 +71,11 @@ llvm::Value *CGCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF,
   return GetBogusMemberPointer(CGM, E->getType());
 }
 
+llvm::Constant *CGCXXABI::EmitMemberPointerConversion(const CastExpr *E,
+                                                      llvm::Constant *Src) {
+  return GetBogusMemberPointer(CGM, E->getType());
+}
+
 llvm::Value *
 CGCXXABI::EmitMemberPointerComparison(CodeGenFunction &CGF,
                                       llvm::Value *L,
@@ -172,3 +177,24 @@ void CGCXXABI::EmitGuardedInit(CodeGenFunction &CGF,
                                bool PerformInit) {
   ErrorUnsupportedABI(CGF, "static local variable initialization");
 }
+
+/// Returns the adjustment, in bytes, required for the given
+/// member-pointer operation.  Returns null if no adjustment is
+/// required.
+llvm::Constant *CGCXXABI::getMemberPointerAdjustment(const CastExpr *E) {
+  assert(E->getCastKind() == CK_DerivedToBaseMemberPointer ||
+         E->getCastKind() == CK_BaseToDerivedMemberPointer);
+
+  QualType derivedType;
+  if (E->getCastKind() == CK_DerivedToBaseMemberPointer)
+    derivedType = E->getSubExpr()->getType();
+  else
+    derivedType = E->getType();
+
+  const CXXRecordDecl *derivedClass =
+    derivedType->castAs<MemberPointerType>()->getClass()->getAsCXXRecordDecl();
+
+  return CGM.GetNonVirtualBaseClassOffset(derivedClass,
+                                          E->path_begin(),
+                                          E->path_end());
+}
index 4bacd014036a73cb463c1e45552dbd66a0ae20f0..4e045f5f3275d83363baa425135aa6e2946e486c 100644 (file)
@@ -100,12 +100,17 @@ public:
                                                     llvm::Value *MemPtr,
                                             const MemberPointerType *MPT);
 
-  /// Perform a derived-to-base or base-to-derived member pointer
-  /// conversion.
+  /// Perform a derived-to-base, base-to-derived, or bitcast member
+  /// pointer conversion.
   virtual llvm::Value *EmitMemberPointerConversion(CodeGenFunction &CGF,
                                                    const CastExpr *E,
                                                    llvm::Value *Src);
 
+  /// Perform a derived-to-base, base-to-derived, or bitcast member
+  /// pointer conversion on a constant value.
+  virtual llvm::Constant *EmitMemberPointerConversion(const CastExpr *E,
+                                                      llvm::Constant *Src);
+
   /// Return true if the given member pointer can be zero-initialized
   /// (in the C++ sense) with an LLVM zeroinitializer.
   virtual bool isZeroInitializable(const MemberPointerType *MPT);
@@ -137,6 +142,15 @@ public:
                              llvm::Value *MemPtr,
                              const MemberPointerType *MPT);
 
+protected:
+  /// A utility method for computing the offset required for the given
+  /// base-to-derived or derived-to-base member-pointer conversion.
+  /// Does not handle virtual conversions (in case we ever fully
+  /// support an ABI that allows this).  Returns null if no adjustment
+  /// is required.
+  llvm::Constant *getMemberPointerAdjustment(const CastExpr *E);
+
+public:
   /// Build the signature of the given constructor variant by adding
   /// any required parameters.  For convenience, ResTy has been
   /// initialized to 'void', and ArgTys has been initialized with the
index 95bed1b0e6177687401c059780d75ae2ec7ac243..f67025a32cf0568b21d3a23eced0b433f685aabb 100644 (file)
@@ -2090,6 +2090,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
   case CK_DerivedToBaseMemberPointer:
   case CK_BaseToDerivedMemberPointer:
   case CK_MemberPointerToBoolean:
+  case CK_ReinterpretMemberPointer:
   case CK_AnyPointerToBlockPointerCast:
   case CK_ARCProduceObject:
   case CK_ARCConsumeObject:
index a85cb9dbc3acd8e81f637a7f25099196ff2f0de9..91f9db0bfeba04fdc25c4df9bc3c1c4379bd99cd 100644 (file)
@@ -360,6 +360,7 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) {
   case CK_BaseToDerivedMemberPointer:
   case CK_DerivedToBaseMemberPointer:
   case CK_MemberPointerToBoolean:
+  case CK_ReinterpretMemberPointer:
   case CK_IntegralToPointer:
   case CK_PointerToIntegral:
   case CK_PointerToBoolean:
index 53f7d65ef1b2b178f27c6ec264f77461e8d4b145..15fa225eb85b28117250faed97856fcc7fd58040 100644 (file)
@@ -388,6 +388,7 @@ ComplexPairTy ComplexExprEmitter::EmitCast(CastExpr::CastKind CK, Expr *Op,
   case CK_BaseToDerivedMemberPointer:
   case CK_DerivedToBaseMemberPointer:
   case CK_MemberPointerToBoolean:
+  case CK_ReinterpretMemberPointer:
   case CK_ConstructorConversion:
   case CK_IntegralToPointer:
   case CK_PointerToIntegral:
index 44531c0992c88356418d4c023cae2a768675541b..985e53227e7881029ec6048303a2847d0cedfe74 100644 (file)
@@ -620,6 +620,11 @@ public:
 
     case CK_Dependent: llvm_unreachable("saw dependent cast!");
 
+    case CK_ReinterpretMemberPointer:
+    case CK_DerivedToBaseMemberPointer:
+    case CK_BaseToDerivedMemberPointer:
+      return CGM.getCXXABI().EmitMemberPointerConversion(E, C);
+
     // These will never be supported.
     case CK_ObjCObjectLValueCast:
     case CK_ARCProduceObject:
@@ -630,18 +635,16 @@ public:
 
     // These don't need to be handled here because Evaluate knows how to
     // evaluate them in the cases where they can be folded.
+    case CK_BitCast:
     case CK_ToVoid:
     case CK_Dynamic:
     case CK_LValueBitCast:
     case CK_NullToMemberPointer:
-    case CK_DerivedToBaseMemberPointer:
-    case CK_BaseToDerivedMemberPointer:
     case CK_UserDefinedConversion:
     case CK_ConstructorConversion:
     case CK_CPointerToObjCPointerCast:
     case CK_BlockPointerToObjCPointerCast:
     case CK_AnyPointerToBlockPointerCast:
-    case CK_BitCast:
     case CK_ArrayToPointerDecay:
     case CK_FunctionToPointerDecay:
     case CK_BaseToDerived:
index d9b0e21238b1a82cac6b011184b9f7e2aa334c80..8bd4c2b5aa2e110f33d130664cf3312d87ae5e27 100644 (file)
@@ -1122,6 +1122,7 @@ Value *ScalarExprEmitter::VisitCastExpr(CastExpr *CE) {
     return CGF.CGM.getCXXABI().EmitNullMemberPointer(MPT);
   }
 
+  case CK_ReinterpretMemberPointer:
   case CK_BaseToDerivedMemberPointer:
   case CK_DerivedToBaseMemberPointer: {
     Value *Src = Visit(E);
index a8b994288292be1b3e173872b26b327c177e6683..8d06b6011c01aa2c194293a474423db8d5e73e05 100644 (file)
@@ -73,6 +73,8 @@ public:
   llvm::Value *EmitMemberPointerConversion(CodeGenFunction &CGF,
                                            const CastExpr *E,
                                            llvm::Value *Src);
+  llvm::Constant *EmitMemberPointerConversion(const CastExpr *E,
+                                              llvm::Constant *Src);
 
   llvm::Constant *EmitNullMemberPointer(const MemberPointerType *MPT);
 
@@ -312,7 +314,10 @@ llvm::Value *ItaniumCXXABI::EmitMemberDataPointerAddress(CodeGenFunction &CGF,
   return Builder.CreateBitCast(Addr, PType);
 }
 
-/// Perform a derived-to-base or base-to-derived member pointer conversion.
+/// Perform a bitcast, derived-to-base, or base-to-derived member pointer
+/// conversion.
+///
+/// Bitcast conversions are always a no-op under Itanium.
 ///
 /// Obligatory offset/adjustment diagram:
 ///         <-- offset -->          <-- adjustment -->
@@ -335,64 +340,105 @@ llvm::Value *ItaniumCXXABI::EmitMemberDataPointerAddress(CodeGenFunction &CGF,
 llvm::Value *
 ItaniumCXXABI::EmitMemberPointerConversion(CodeGenFunction &CGF,
                                            const CastExpr *E,
-                                           llvm::Value *Src) {
+                                           llvm::Value *src) {
   assert(E->getCastKind() == CK_DerivedToBaseMemberPointer ||
-         E->getCastKind() == CK_BaseToDerivedMemberPointer);
+         E->getCastKind() == CK_BaseToDerivedMemberPointer ||
+         E->getCastKind() == CK_ReinterpretMemberPointer);
+
+  // Under Itanium, reinterprets don't require any additional processing.
+  if (E->getCastKind() == CK_ReinterpretMemberPointer) return src;
+
+  // Use constant emission if we can.
+  if (isa<llvm::Constant>(src))
+    return EmitMemberPointerConversion(E, cast<llvm::Constant>(src));
+
+  llvm::Constant *adj = getMemberPointerAdjustment(E);
+  if (!adj) return src;
 
   CGBuilderTy &Builder = CGF.Builder;
+  bool isDerivedToBase = (E->getCastKind() == CK_DerivedToBaseMemberPointer);
+
+  const MemberPointerType *destTy =
+    E->getType()->castAs<MemberPointerType>();
 
-  const MemberPointerType *SrcTy =
-    E->getSubExpr()->getType()->getAs<MemberPointerType>();
-  const MemberPointerType *DestTy = E->getType()->getAs<MemberPointerType>();
+  // For member data pointers, this is just a matter of adding the
+  // offset if the source is non-null.
+  if (destTy->isMemberDataPointer()) {
+    llvm::Value *dst;
+    if (isDerivedToBase)
+      dst = Builder.CreateNSWSub(src, adj, "adj");
+    else
+      dst = Builder.CreateNSWAdd(src, adj, "adj");
 
-  const CXXRecordDecl *SrcDecl = SrcTy->getClass()->getAsCXXRecordDecl();
-  const CXXRecordDecl *DestDecl = DestTy->getClass()->getAsCXXRecordDecl();
+    // Null check.
+    llvm::Value *null = llvm::Constant::getAllOnesValue(src->getType());
+    llvm::Value *isNull = Builder.CreateICmpEQ(src, null, "memptr.isnull");
+    return Builder.CreateSelect(isNull, src, dst);
+  }
 
-  bool DerivedToBase =
-    E->getCastKind() == CK_DerivedToBaseMemberPointer;
+  // The this-adjustment is left-shifted by 1 on ARM.
+  if (IsARM) {
+    uint64_t offset = cast<llvm::ConstantInt>(adj)->getZExtValue();
+    offset <<= 1;
+    adj = llvm::ConstantInt::get(adj->getType(), offset);
+  }
 
-  const CXXRecordDecl *DerivedDecl;
-  if (DerivedToBase)
-    DerivedDecl = SrcDecl;
+  llvm::Value *srcAdj = Builder.CreateExtractValue(src, 1, "src.adj");
+  llvm::Value *dstAdj;
+  if (isDerivedToBase)
+    dstAdj = Builder.CreateNSWSub(srcAdj, adj, "adj");
   else
-    DerivedDecl = DestDecl;
+    dstAdj = Builder.CreateNSWAdd(srcAdj, adj, "adj");
+
+  return Builder.CreateInsertValue(src, dstAdj, 1);
+}
+
+llvm::Constant *
+ItaniumCXXABI::EmitMemberPointerConversion(const CastExpr *E,
+                                           llvm::Constant *src) {
+  assert(E->getCastKind() == CK_DerivedToBaseMemberPointer ||
+         E->getCastKind() == CK_BaseToDerivedMemberPointer ||
+         E->getCastKind() == CK_ReinterpretMemberPointer);
 
-  llvm::Constant *Adj = 
-    CGF.CGM.GetNonVirtualBaseClassOffset(DerivedDecl,
-                                         E->path_begin(),
-                                         E->path_end());
-  if (!Adj) return Src;
+  // Under Itanium, reinterprets don't require any additional processing.
+  if (E->getCastKind() == CK_ReinterpretMemberPointer) return src;
+
+  // If the adjustment is trivial, we don't need to do anything.
+  llvm::Constant *adj = getMemberPointerAdjustment(E);
+  if (!adj) return src;
+
+  bool isDerivedToBase = (E->getCastKind() == CK_DerivedToBaseMemberPointer);
+
+  const MemberPointerType *destTy =
+    E->getType()->castAs<MemberPointerType>();
 
   // For member data pointers, this is just a matter of adding the
   // offset if the source is non-null.
-  if (SrcTy->isMemberDataPointer()) {
-    llvm::Value *Dst;
-    if (DerivedToBase)
-      Dst = Builder.CreateNSWSub(Src, Adj, "adj");
-    else
-      Dst = Builder.CreateNSWAdd(Src, Adj, "adj");
+  if (destTy->isMemberDataPointer()) {
+    // null maps to null.
+    if (src->isAllOnesValue()) return src;
 
-    // Null check.
-    llvm::Value *Null = llvm::Constant::getAllOnesValue(Src->getType());
-    llvm::Value *IsNull = Builder.CreateICmpEQ(Src, Null, "memptr.isnull");
-    return Builder.CreateSelect(IsNull, Src, Dst);
+    if (isDerivedToBase)
+      return llvm::ConstantExpr::getNSWSub(src, adj);
+    else
+      return llvm::ConstantExpr::getNSWAdd(src, adj);
   }
 
   // The this-adjustment is left-shifted by 1 on ARM.
   if (IsARM) {
-    uint64_t Offset = cast<llvm::ConstantInt>(Adj)->getZExtValue();
-    Offset <<= 1;
-    Adj = llvm::ConstantInt::get(Adj->getType(), Offset);
+    uint64_t offset = cast<llvm::ConstantInt>(adj)->getZExtValue();
+    offset <<= 1;
+    adj = llvm::ConstantInt::get(adj->getType(), offset);
   }
 
-  llvm::Value *SrcAdj = Builder.CreateExtractValue(Src, 1, "src.adj");
-  llvm::Value *DstAdj;
-  if (DerivedToBase)
-    DstAdj = Builder.CreateNSWSub(SrcAdj, Adj, "adj");
+  llvm::Constant *srcAdj = llvm::ConstantExpr::getExtractValue(src, 1);
+  llvm::Constant *dstAdj;
+  if (isDerivedToBase)
+    dstAdj = llvm::ConstantExpr::getNSWSub(srcAdj, adj);
   else
-    DstAdj = Builder.CreateNSWAdd(SrcAdj, Adj, "adj");
+    dstAdj = llvm::ConstantExpr::getNSWAdd(srcAdj, adj);
 
-  return Builder.CreateInsertValue(Src, DstAdj, 1);
+  return llvm::ConstantExpr::getInsertValue(src, dstAdj, 1);
 }
 
 llvm::Constant *
index c89582ff00782217b3b8d0e7bac0c50473bf5ff3..a36f6cbb009d396e662af467f1bbbdac5c297e3f 100644 (file)
@@ -1576,7 +1576,8 @@ static TryCastResult TryReinterpretCast(Sema &Self, ExprResult &SrcExpr,
     }
 
     // A valid member pointer cast.
-    Kind = IsLValueCast? CK_LValueBitCast : CK_BitCast;
+    assert(!IsLValueCast);
+    Kind = CK_ReinterpretMemberPointer;
     return TC_Success;
   }
 
index 254540468a765cfd6a63b2812e9501cb6d62d41b..c1f804ce09ba9eab0f72adf4b2b2744a677e8cb7 100644 (file)
@@ -289,6 +289,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
       case CK_NullToMemberPointer:
       case CK_BaseToDerivedMemberPointer:
       case CK_DerivedToBaseMemberPointer:
+      case CK_ReinterpretMemberPointer:
       case CK_UserDefinedConversion:
       case CK_ConstructorConversion:
       case CK_VectorSplat:
index 5ce5fbf760dba28c17f0e7a3880e1f01cb660f1a..2417aa41918b2388f0829a8d958a9a1f708eb9f8 100644 (file)
@@ -28,6 +28,16 @@ void (C::*pc2)() = &C::f;
 // CHECK: @pc3 = global { i64, i64 } { i64 1, i64 0 }, align 8
 void (A::*pc3)() = &A::vf1;
 
+// Tests for test10.
+// CHECK: @_ZN6test101aE = global { i64, i64 } { i64 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i64), i64 0 }, align 8
+// CHECK: @_ZN6test101bE = global { i64, i64 } { i64 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i64), i64 8 }, align 8
+// CHECK: @_ZN6test101cE = global { i64, i64 } { i64 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i64), i64 8 }, align 8
+// CHECK: @_ZN6test101dE = global { i64, i64 } { i64 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i64), i64 16 }, align 8
+// CHECK-LP32: @_ZN6test101aE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 0 }, align 4
+// CHECK-LP32: @_ZN6test101bE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 4 }, align 4
+// CHECK-LP32: @_ZN6test101cE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 4 }, align 4
+// CHECK-LP32: @_ZN6test101dE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 8 }, align 4
+
 void f() {
   // CHECK: store { i64, i64 } zeroinitializer, { i64, i64 }* @pa
   pa = 0;
@@ -232,3 +242,33 @@ namespace test9 {
     static S array[] = { (fooptr) &B::foo };
   }
 }
+
+// rdar://problem/10815683 - Verify that we can emit reinterprets of
+// member pointers as constant initializers.  For added trickiness,
+// we also add some non-trivial adjustments.
+namespace test10 {
+  struct A {
+    int nonEmpty;
+    void foo();
+  };
+  struct B : public A {
+    virtual void requireNonZeroAdjustment();
+  };
+  struct C {
+    int nonEmpty;
+  };
+  struct D : public C {
+    virtual void requireNonZeroAdjustment();
+  };
+
+  // Non-ARM tests at top of file.
+  void (A::*a)() = &A::foo;
+  void (B::*b)() = (void (B::*)()) &A::foo;
+  void (C::*c)() = (void (C::*)()) (void (B::*)()) &A::foo;
+  void (D::*d)() = (void (C::*)()) (void (B::*)()) &A::foo;
+}
+// It's not that the offsets are doubled on ARM, it's that they're left-shifted by 1.
+// CHECK-ARM: @_ZN6test101aE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 0 }, align 4
+// CHECK-ARM: @_ZN6test101bE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 8 }, align 4
+// CHECK-ARM: @_ZN6test101cE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 8 }, align 4
+// CHECK-ARM: @_ZN6test101dE = global { i32, i32 } { i32 ptrtoint (void (%"struct.test10::A"*)* @_ZN6test101A3fooEv to i32), i32 16 }, align 4