[PATCH] [ms-cxxabi] Implement codegen for dereferencing member pointers

John McCall rjmccall at apple.com
Tue Apr 9 16:50:26 PDT 2013

On Apr 9, 2013, at 3:02 PM, Reid Kleckner <rnk at google.com> wrote:
> On Mon, Apr 8, 2013 at 7:58 PM, John McCall <rjmccall at apple.com> wrote:
>> On Apr 2, 2013, at 11:00 AM, Reid Kleckner <rnk at google.com> wrote:
>>> Handles all inheritance models for both data and function member
>>> pointers.  This does not require knowing the layout of the vbtable,
>>> because the member pointer itself carries the offset into the vbtable.
>>> Clang still cannot emit multi-field member pointers because it would
>>> need to know the layout of the vbtable to generate the correct offsets.
>>> Also implements isZeroInitializable() and refactors some of the null
>>> member pointer code.
>> +  llvm::tie(Ptrs, Ints) = MPT->getMSMemberPointerSlots();
>> I missed this particular method getting added.  I think this is probably
>> going a bit too far.  It's not really generally meaningful;  you have two
>> clients of it, and one of them has to do weird special-casing anyway
>> (i.e. filling in -1 in the right place).
> OK.  I ended up having to duplicate switches on the inheritance model,
> so I guess I might as well do it here.  I'll do a separate change on
> the AST layer to get rid of this helper.  Alternatively I could try to
> raise the memptr layout up into the AST layer and query it from here.

I think this is the right balance.  Ultimately, IR-gen needs knowledge of the
member-pointer layout that the AST really just doesn't.

>> +  case MSIM_Unspecified: {
>> +    CharUnits offs = getContext().getASTRecordLayout(RD).getVBPtrOffset();
>> +    llvm::Constant *VBTableOffset =
>> +      llvm::ConstantInt::get(CGM.IntTy, offs.getQuantity());
>> +    llvm::Constant *fields[] = {
>> +      FieldOffset,
>> +      VBTableOffset,
>> +      getZeroInt()    // FIXME: VirtualBaseAdjustmentOffset
>> +    };
>> The VirtualBaseAdjustmentOffset should be some special value to indicate
>> that this isn't a member of a virtual base;  you just need to leave that value
>> in there.
> The first entry of a vbtable points to the base of the current
> subobject, so zero should work for that.


>> Also, are you sure you're supposed to leave a meaningful vb-table offset
>> in here even when the member *isn't* a member of a virtual base?
> I believe zero is correct for that, for the same reason as above.
> It looks like taking a pointer to a member of a virtual base is an
> MSVC language extension.

Yep.  Or, to be more specific, MSVC implemented a very general feature
that got pared down by standardization.

> Clang rejects my attempts to form member
> pointers to virtual bases.  I filed http://llvm.org/PR15713 to track
> any decisions made on that front.  Currently, I don't have the decl so
> I can't assert without changing the API, so I'll just always emit zero
> and comment this.

The emission of a member pointer constant will never need to create a
member of a virtual base;  only member pointer conversion needs to do that.

This might not be obvious, but given:
  struct A { int x; };
  struct B : A {};
&B::x actually has type "int A::*", not "int B::*".  The class name there is
just used for lookup.

Anyway, yes, you'll need to teach Sema to allow these conversions.

>> Also, recall that the class very well might not have a vb-table;  there might
>> be a signal value for that, too.
> You're right, I had to fix that.  Can't go dereferencing non-existent vbptrs.

Right.  I guess this is as simple as testing whether the offset is zero (the offset
within the vb-table, not the offset of the vbptr, which can legitimately be zero).

>> +  llvm::Type *Int8Ptr = Builder.getInt8Ty()->getPointerTo(0);
>> This is CGM.Int8PtrTy.
> OK.
>> +llvm::Value *
>> +MicrosoftCXXABI::AdjustVirtualBase(CGBuilderTy &Builder,
>> +                                   const CXXRecordDecl *RD, llvm::Value *Base,
>> +                                   llvm::Value *VirtualBaseAdjustmentOffset,
>> +                                   llvm::Value *VBTableOffset) {
>> This code is assuming that it *has* to adjust by a virtual base.
>> That is not the case (unless MSVC does something interesting
>> in its vb-table layouts, like leave a zero offset at a particular offset
>> in the table...), and even then it's suspect in the MSIM_unspecified
>> case where it's completely legitimate that the record type might not
>> have a vb-table.
> Good catch: the first vbtable entry points back to the base of the
> current subobject.  That means it's safe to perform the adjustment so
> long as we know there is a vbptr.  MSVC does this so I followed suit.

Seems reasonable.

As an aside, I wonder what the right trade-off here actually is: an extra
branch vs. an unnecessary memory dependence...  Anyway, it's not
important right now.

+  // If the class has an explicit inheritance model attribute, we conservatively
+  // use -1.  This requires different TUs to see the same attribute or they have
+  // an ODR problem.
+  if (RD->hasAttr<MSInheritanceAttr>())
+    return true;

If this is really how MSVC handles this issue — and that seems reasonable
enough to me — then arguably these cases need to be distinguished in the
MSInheritanceModel enum.  I think we'd have six cases:
  - has no bases at non-zero offsets, known not polymorphic or unknown polymorphism
  - has no bases at non-zero offsets, known polymorphic
  - has bases at static non-zero offsets, known not polymorphic or unknown polymorphism
  - has bases at static non-zero offsets, known polymorphic
  - has virtual bases
  - unspecified

The inheritance model is fundamentally about member pointers.  It makes
sense for it to capture everything necessary in order to interpret a member

That would also make it easier to diagnose the following invalid code (unless
we already diagnose attempts to add inheritance keywords after the definition?).
  class A {
    virtual void foo();
  class __single_inheritance A;

+  // Since zero is a valid offset for classes without vtables, they use -1.  For
+  // some reason, classes with vtables use 0 in this ABI.  In this inheritance
+  // model, only polymorphic classes have vtables.
+  return !RD->isPolymorphic();

The reason's pretty straightforward:  it is generally better to use 0 instead of -1
because it's usually easier to produce 0 than -1.  That's true even on the
smallest level — on x86 it's a smaller instruction to load 0 into a register
than to load -1 — but more importantly, it means that the compiler can do things
like put a zero-initialized global in a zero-fill section or zero-initialize an
object with a simple memset.

+  // There is only one kind of class with data member pointers that can be zero
+  // initialized: single or multiple inheritance classes with a vfptr.

I'd put it this way:  MSVC uses 0 as the null offset if it can guarantee that
that won't point at a data member.  It can make that guarantee:
  - if the class is known to be polymorphic (because offset 0 will be the vf-table), or otherwise
  - if the class is known to have virtual bases (because offset 0 will be the vb-table)
class is known to have virtual bases (because offset 0 will be the vb-table).

The way that you've got this logic split between NullFieldOffsetIsAllOnes and
isZeroInitializable is really confusing to me.

+  if (Inheritance >= MSIM_Virtual)

Instead of doing checks like this, consider making little predicate functions:
  static bool hasVirtualBaseAdjustmentField(MSInheritanceModel);
It would remove the need for quite so many little comments everywhere.

+llvm::SmallVector<llvm::Constant *, 4>
+MicrosoftCXXABI::GetNullMemberPointerFields(const MemberPointerType *MPT) {

Please have this take a SmallVectorImpl<llvm::Constant*> by reference instead
of returning one.

+      // http://llvm.org/PR15713 : Taking a pointer to a member of a virtual
+      // base is not standard C++.  Clang will reject this in SemaCast, so we
+      // don't have to handle it here.

As covered above, when emitting a member pointer constant, you'll always
be making something in its declaring class.  This is why you can just ask
for the offset of a field instead of computing offsets all along the base path.

+  // in which case we need to skip the virtual base lookup.  If there were a
+  // vbtable, the first entry is a no-op entry that gives back the original

Grammar:  "If there is a vbtable..."

+  llvm::BasicBlock *OriginalBB;
+  llvm::BasicBlock *SkipAdjustBB;

You'll need to zero-initialize these in order to pacify compiler warnings.

+  // Add it to VBPtr.
+  llvm::Value *AdjustedBase = Builder.CreateInBoundsGEP(VBPtr, VBaseOffs);

On Win64, you need to be sure to sign-extend this.  (vb-offsets can be
negative in very particular circumstances.)

+  assert(MPT->isMemberFunctionPointer());
+  const FunctionProtoType *FPT =
+    MPT->getPointeeType()->getAs<FunctionProtoType>();

This can be castAs<>.

Otherwise, this is looking great, thanks!


More information about the cfe-commits mailing list