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