r222133 - Add a couple more examples illustrating why we need vtordisps and how they work

Timur Iskhodzhanov timurrrr at google.com
Mon Nov 17 07:11:05 PST 2014


Author: timurrrr
Date: Mon Nov 17 09:11:05 2014
New Revision: 222133

URL: http://llvm.org/viewvc/llvm-project?rev=222133&view=rev
Log:
Add a couple more examples illustrating why we need vtordisps and how they work

Modified:
    cfe/trunk/lib/AST/VTableBuilder.cpp

Modified: cfe/trunk/lib/AST/VTableBuilder.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/VTableBuilder.cpp?rev=222133&r1=222132&r2=222133&view=diff
==============================================================================
--- cfe/trunk/lib/AST/VTableBuilder.cpp (original)
+++ cfe/trunk/lib/AST/VTableBuilder.cpp Mon Nov 17 09:11:05 2014
@@ -64,7 +64,7 @@ public:
     /// Method - The method decl of the overrider.
     const CXXMethodDecl *Method;
 
-    /// VirtualBase - The virtual base class subobject of this overridder.
+    /// VirtualBase - The virtual base class subobject of this overrider.
     /// Note that this records the closest derived virtual base class subobject.
     const CXXRecordDecl *VirtualBase;
 
@@ -2779,6 +2779,103 @@ VFTableBuilder::ComputeThisOffset(FinalO
   return Ret;
 }
 
+// Things are getting even more complex when the "this" adjustment has to
+// use a dynamic offset instead of a static one, or even two dynamic offsets.
+// This is sometimes required when a virtual call happens in the middle of
+// a non-most-derived class construction or destruction.
+//
+// Let's take a look at the following example:
+//   struct A {
+//     virtual void f();
+//   };
+//
+//   void foo(A *a) { a->f(); }  // Knows nothing about siblings of A.
+//
+//   struct B : virtual A {
+//     virtual void f();
+//     B() {
+//       foo(this);
+//     }
+//   };
+//
+//   struct C : virtual B {
+//     virtual void f();
+//   };
+//
+// Record layouts for these classes are:
+//   struct A
+//   0 |   (A vftable pointer)
+//
+//   struct B
+//   0 |   (B vbtable pointer)
+//   4 |   (vtordisp for vbase A)
+//   8 |   struct A (virtual base)
+//   8 |     (A vftable pointer)
+//
+//   struct C
+//   0 |   (C vbtable pointer)
+//   4 |   (vtordisp for vbase A)
+//   8 |   struct A (virtual base)  // A precedes B!
+//   8 |     (A vftable pointer)
+//  12 |   struct B (virtual base)
+//  12 |     (B vbtable pointer)
+//
+// When one creates an object of type C, the C constructor:
+// - initializes all the vbptrs, then
+// - calls the A subobject constructor
+//   (initializes A's vfptr with an address of A vftable), then
+// - calls the B subobject constructor
+//   (initializes A's vfptr with an address of B vftable and vtordisp for A),
+//   that in turn calls foo(), then
+// - initializes A's vfptr with an address of C vftable and zeroes out the
+//   vtordisp
+//   FIXME: if a structor knows it belongs to MDC, why doesn't it use a vftable
+//   without vtordisp thunks?
+//
+// When foo() is called, an object with a layout of class C has a vftable
+// referencing B::f() that assumes a B layout, so the "this" adjustments are
+// incorrect, unless an extra adjustment is done.  This adjustment is called
+// "vtordisp adjustment".  Vtordisp basically holds the difference between the
+// actual location of a vbase in the layout class and the location assumed by
+// the vftable of the class being constructed/destructed.  Vtordisp is only
+// needed if "this" escapes a
+// structor (or we can't prove otherwise).
+// [i.e. vtordisp is a dynamic adjustment for a static adjustment, which is an
+// estimation of a dynamic adjustment]
+//
+// foo() gets a pointer to the A vbase and doesn't know anything about B or C,
+// so it just passes that pointer as "this" in a virtual call.
+// If there was no vtordisp, that would just dispatch to B::f().
+// However, B::f() assumes B+8 is passed as "this",
+// yet the pointer foo() passes along is B-4 (i.e. C+8).
+// An extra adjustment is needed, so we emit a thunk into the B vftable.
+// This vtordisp thunk subtracts the value of vtordisp
+// from the "this" argument (-12) before making a tailcall to B::f().
+//
+// Let's consider an even more complex example:
+//   struct D : virtual B, virtual C {
+//     D() {
+//       foo(this);
+//     }
+//   };
+//
+//   struct D
+//   0 |   (D vbtable pointer)
+//   4 |   (vtordisp for vbase A)
+//   8 |   struct A (virtual base)  // A precedes both B and C!
+//   8 |     (A vftable pointer)
+//  12 |   struct B (virtual base)  // B precedes C!
+//  12 |     (B vbtable pointer)
+//  16 |   struct C (virtual base)
+//  16 |     (C vbtable pointer)
+//
+// When D::D() calls foo(), we find ourselves in a thunk that should tailcall
+// to C::f(), which assumes C+8 as its "this" parameter.  This time, foo()
+// passes along A, which is C-8.  The A vtordisp holds
+//   "D.vbptr[index_of_A] - offset_of_A_in_D"
+// and we statically know offset_of_A_in_D, so can get a pointer to D.
+// When we know it, we can make an extra vbtable lookup to locate the C vbase
+// and one extra static adjustment to calculate the expected value of C+8.
 void VFTableBuilder::CalculateVtordispAdjustment(
     FinalOverriders::OverriderInfo Overrider, CharUnits ThisOffset,
     ThisAdjustment &TA) {





More information about the cfe-commits mailing list