Skip to content

Commit 7d19fc1

Browse files
committed
Add a couple more examples illustrating why we need vtordisps and how they work
llvm-svn: 222133
1 parent 5a8c466 commit 7d19fc1

File tree

1 file changed

+98
-1
lines changed

1 file changed

+98
-1
lines changed

clang/lib/AST/VTableBuilder.cpp

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class FinalOverriders {
6464
/// Method - The method decl of the overrider.
6565
const CXXMethodDecl *Method;
6666

67-
/// VirtualBase - The virtual base class subobject of this overridder.
67+
/// VirtualBase - The virtual base class subobject of this overrider.
6868
/// Note that this records the closest derived virtual base class subobject.
6969
const CXXRecordDecl *VirtualBase;
7070

@@ -2779,6 +2779,103 @@ VFTableBuilder::ComputeThisOffset(FinalOverriders::OverriderInfo Overrider) {
27792779
return Ret;
27802780
}
27812781

2782+
// Things are getting even more complex when the "this" adjustment has to
2783+
// use a dynamic offset instead of a static one, or even two dynamic offsets.
2784+
// This is sometimes required when a virtual call happens in the middle of
2785+
// a non-most-derived class construction or destruction.
2786+
//
2787+
// Let's take a look at the following example:
2788+
// struct A {
2789+
// virtual void f();
2790+
// };
2791+
//
2792+
// void foo(A *a) { a->f(); } // Knows nothing about siblings of A.
2793+
//
2794+
// struct B : virtual A {
2795+
// virtual void f();
2796+
// B() {
2797+
// foo(this);
2798+
// }
2799+
// };
2800+
//
2801+
// struct C : virtual B {
2802+
// virtual void f();
2803+
// };
2804+
//
2805+
// Record layouts for these classes are:
2806+
// struct A
2807+
// 0 | (A vftable pointer)
2808+
//
2809+
// struct B
2810+
// 0 | (B vbtable pointer)
2811+
// 4 | (vtordisp for vbase A)
2812+
// 8 | struct A (virtual base)
2813+
// 8 | (A vftable pointer)
2814+
//
2815+
// struct C
2816+
// 0 | (C vbtable pointer)
2817+
// 4 | (vtordisp for vbase A)
2818+
// 8 | struct A (virtual base) // A precedes B!
2819+
// 8 | (A vftable pointer)
2820+
// 12 | struct B (virtual base)
2821+
// 12 | (B vbtable pointer)
2822+
//
2823+
// When one creates an object of type C, the C constructor:
2824+
// - initializes all the vbptrs, then
2825+
// - calls the A subobject constructor
2826+
// (initializes A's vfptr with an address of A vftable), then
2827+
// - calls the B subobject constructor
2828+
// (initializes A's vfptr with an address of B vftable and vtordisp for A),
2829+
// that in turn calls foo(), then
2830+
// - initializes A's vfptr with an address of C vftable and zeroes out the
2831+
// vtordisp
2832+
// FIXME: if a structor knows it belongs to MDC, why doesn't it use a vftable
2833+
// without vtordisp thunks?
2834+
//
2835+
// When foo() is called, an object with a layout of class C has a vftable
2836+
// referencing B::f() that assumes a B layout, so the "this" adjustments are
2837+
// incorrect, unless an extra adjustment is done. This adjustment is called
2838+
// "vtordisp adjustment". Vtordisp basically holds the difference between the
2839+
// actual location of a vbase in the layout class and the location assumed by
2840+
// the vftable of the class being constructed/destructed. Vtordisp is only
2841+
// needed if "this" escapes a
2842+
// structor (or we can't prove otherwise).
2843+
// [i.e. vtordisp is a dynamic adjustment for a static adjustment, which is an
2844+
// estimation of a dynamic adjustment]
2845+
//
2846+
// foo() gets a pointer to the A vbase and doesn't know anything about B or C,
2847+
// so it just passes that pointer as "this" in a virtual call.
2848+
// If there was no vtordisp, that would just dispatch to B::f().
2849+
// However, B::f() assumes B+8 is passed as "this",
2850+
// yet the pointer foo() passes along is B-4 (i.e. C+8).
2851+
// An extra adjustment is needed, so we emit a thunk into the B vftable.
2852+
// This vtordisp thunk subtracts the value of vtordisp
2853+
// from the "this" argument (-12) before making a tailcall to B::f().
2854+
//
2855+
// Let's consider an even more complex example:
2856+
// struct D : virtual B, virtual C {
2857+
// D() {
2858+
// foo(this);
2859+
// }
2860+
// };
2861+
//
2862+
// struct D
2863+
// 0 | (D vbtable pointer)
2864+
// 4 | (vtordisp for vbase A)
2865+
// 8 | struct A (virtual base) // A precedes both B and C!
2866+
// 8 | (A vftable pointer)
2867+
// 12 | struct B (virtual base) // B precedes C!
2868+
// 12 | (B vbtable pointer)
2869+
// 16 | struct C (virtual base)
2870+
// 16 | (C vbtable pointer)
2871+
//
2872+
// When D::D() calls foo(), we find ourselves in a thunk that should tailcall
2873+
// to C::f(), which assumes C+8 as its "this" parameter. This time, foo()
2874+
// passes along A, which is C-8. The A vtordisp holds
2875+
// "D.vbptr[index_of_A] - offset_of_A_in_D"
2876+
// and we statically know offset_of_A_in_D, so can get a pointer to D.
2877+
// When we know it, we can make an extra vbtable lookup to locate the C vbase
2878+
// and one extra static adjustment to calculate the expected value of C+8.
27822879
void VFTableBuilder::CalculateVtordispAdjustment(
27832880
FinalOverriders::OverriderInfo Overrider, CharUnits ThisOffset,
27842881
ThisAdjustment &TA) {

0 commit comments

Comments
 (0)