From 7d19fc1c375cc883b216ce0033671aa20de5f35d Mon Sep 17 00:00:00 2001 From: Timur Iskhodzhanov Date: Mon, 17 Nov 2014 15:11:05 +0000 Subject: [PATCH] Add a couple more examples illustrating why we need vtordisps and how they work llvm-svn: 222133 --- clang/lib/AST/VTableBuilder.cpp | 99 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/clang/lib/AST/VTableBuilder.cpp b/clang/lib/AST/VTableBuilder.cpp index 4813546..b78c080 100644 --- a/clang/lib/AST/VTableBuilder.cpp +++ b/clang/lib/AST/VTableBuilder.cpp @@ -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(FinalOverriders::OverriderInfo Overrider) { 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) { -- 2.7.4