Virtual function call stuff, again
Mark Mitchell
mark at codesourcery.com
Tue Feb 22 20:21:18 UTC 2000
>>>>> "Christophe" == Christophe de Dinechin <ddd at cup.hp.com> writes:
Thanks for helping me with this.
Christophe> Mark, I did not find anything containing "Virtual
Christophe> Function Calling Convention" in the
Christophe> documentation. Could you specify an URL and quote the
Christophe> original text, it would help me locating it...
http://reality.sgi.com/dehnert_engr/cxx/abi-layout.html#vtable
There doesn't appear to be a tag for the "VFCC" section, but it's down
just a bit from the URL I gave.
>> Here's an attempted rewrite. Let's see if it's what y'all
>> meant.
>> o Suppose a class `A' defines a virtual function `A::f'. The
>> primary vtable for `A' contains a pointer to an entry point
>> that performs no adjustment.
>>
>> o Suppose that a class `A' declares a virtual function `A::f',
>> and suppose that `A' is a base class in a hierarchy dominated
>> by another class `B'. Suppose that the unique final overrider
>> for `A::f' in `B' is `C::f'. We must determine what entry
>> point is used for `f' in the `A-in-B' secondary vtable. Here
>> is the algorithm:
>>
>> - Find any path from `B' to `C' in the inheritance graph for
>> `B'.
>>
>> - If there is no virtual base along the path, then create an
>> entry point which adjusts the `this' pointer from `A' to `C'.
>> This value can be computed statically when the `A-in-B' vtable
>> is created. Then transfer control to the non-adjusting entry
>> point for `C::f'.
Christophe> Yes. What's more, in that case, the offset is stored
Christophe> in the vtable at a (possibly large) constant negative
Christophe> offset from the vptr. We called this offset
Christophe> 'convert_to_C', and it is used only when the final
Christophe> overrider is a C. The A-in-B vtable contains a
Christophe> 'convert_to_C' value that converts from an A* to a
Christophe> C*. The benefits are explained in
Christophe> http://reality.sgi.com/dehnert_engr/cxx/cxx-closed.html,
Christophe> section B-1.
I understand the high-level points of that discussion: what I don't
understand is exactly what goes where, when. I'm not really trying to
reopen any issue -- I'm trying to force the committe to write down
what it decided. At this point, the decision is not, quite frankly,
written in a form that anyone not on the committee is likely to
decipher. (Well, at least I've tried hard, I'm not an idiot, I know
about this application domain, and I still can't figure it out.)
>> - If there is a virtual base along this path, let `V' be the
>> virtual base nearest to `C' along the path. (In fact, `V' will
>> be `C' itself if `C' is a virtual base.)
Christophe> You are considering a path between B and C, so V would
Christophe> be the closest between B and C. In which case I do not
Christophe> understand the adjustment below ("Adjustment from 'A'
Christophe> to 'V'"). Either you are considering that V is between
Christophe> A and C, in which case you need to adjust from A to V,
Christophe> or you are considering that V is between C and B, in
Christophe> which case if you see it at call site, you would have
Christophe> to adjust from B to V. Did I misunderstand?
You won't in general see `V' at the call site, if it's between C and
B, because you might have `A*' when you're making the call. (If you
had a `B*', or a `C*', then `C::f' would be the unique final overrider
-- by hypothesis -- so you would have converted your pointer to a
`C*', and you be looking at the C-in-B vtable, rather than the A-in-B
vtable, which is what my discussion was talking about.)
I'm not sure where you're supposed to adjust `A' to. That's what I'm
trying to find out. It may not be the V that I suggested -- I'm not
sure. I'm hoping that my words could serve as the model for whatever
the right answer is. They're in the form of an algorithm that
explains exactly what to put in the vtable, in the general case, and
that's a good thing, since that's what an implementor needs to know.
Christophe> So, starting with your example again. Sorry for the
Christophe> verbiage...
Christophe> Case 1 is: V is between A and C.
THere's no discussion of these two different cases in the current
writeup. So, I'm already lost. What you write below seems reasonably
logical -- but I'm not sure it's the same thing as in the document.
In general, "between A and C" doesn't make sense; they may be
unrelated. The picture I had in mind was:
B
/ \
A C
In other words, B is the most derived class, C contains the final
overrider, we're looking at the vtable for A. That's the general case
-- all other cases are special instances of this one. But, I guess
for case 1, we're assuming C is derived from A?
Christophe> - If you call through an A*, you call through the A
Christophe> vtable, which points to a virtual-base-adjustment
Christophe> thunk. That thunk reads the C-to-A virtual base
Christophe> offset, and adds that to get a C*, and then jumps to
Where is this offset? There's a vbase offset in C for converting to
A, if A is a virtual base of C. But, there's no virtual base offset
in A, for converting to a C. Where's V in all this?
Christophe> the non-adjusting C::f. Of course, the C-to-A virtual
Christophe> base offset is not necessarily constant along the
Christophe> class hierarchy, so for instance the C-to-A virtual
Christophe> base offset in a C is not the same as the C-to-A
Christophe> virtual base offset in a B.
I don't understand this.
Christophe> - If you call through a C*, you call through the C
Christophe> vtable. In that case, you don't care if A is a virtual
Christophe> base, since C::f expects a C*. Virtual-base adjustment
Christophe> to A, if necessary, would be done inside C::f. So the
Christophe> C vtable points to the non-adjusting entry point.
That one makes sense.
Christophe> - If you call through a B*, you call through the C
Christophe> vtable (the final overrider for f), and you adjust to
Christophe> a C* statically.
That too.
Christophe> Case 2: V is between C and B:
Christophe> - If you call through an A*, you call through the A
Christophe> vtable. The offset from A to C is constant in the
Christophe> class hierarchy, but there may be multiple bases A1,
Christophe> A2, A3, with different offsets. The "classical"
Are all these bases of type A? Or are these just different bases, all
of which declare the same `f' overridden by `C::f'?
Christophe> approach is to use multiple thunks that convert from
Christophe> A1 to C, from A2 to C, from A3 to C. Because of the
Christophe> high cache muss and branch misprediction penalty, I
Christophe> proposed that we rather have all vtables A1-in-C,
Christophe> A2-in-C, ... contain an offset, call it
Christophe> 'Convert_to_C', that adjusts from A1 to C, A2 to C,
Christophe> etc. This way, there can be a single adjusting entry
Christophe> point that does this adjustment (although you can
Christophe> still emit thunks)
Right -- I get that. And the point is that you can store the
'convert_to_C' offset at some fixed negative index from the vptr from
each *unadjusted* `this' pointer?
Christophe> - If you call through a C*, you call through the C
Christophe> vtable, which points to the non-adjusting entry point
Christophe> C::f.
Yes.
Christophe> - If you call through a B*, you need at call-site to
Christophe> convert from B* to C*, which involves a dynamic
Christophe> adjustment using the B-to-V virtual base offsets in
Christophe> B's vtable, followed by a static adjustment from V to
Christophe> C (which is known at compile-time).
Yes. Note that in my attempted writeup, I wasn't trying to specify
how to call the function: I was trying to specify what you put in the
vtable for A.
Christophe> I'm not sure I clarified anything ;-)
Somewhat. :-)
Christophe> First, note that the scheme is believed to be
Christophe> advantageous whenever there is no virtual inheritance,
Christophe> which I think is a frequent case. Cache locality plays
Christophe> a big role here.
OK. It would really help if the ABI document contained some
non-normative notes explaining some of this, and giving some examples.
Unfortunately, I can't write these, because I don't know the answers.
Christophe> Second, in the virtual base case, there is no
Christophe> guarantee that the thunk will be right before the
Christophe> non-adjusting entry point: as Jason pointed out, you
Christophe> cannot find a single offset to use to perform the
Christophe> adjustment.
Christophe> Third, I do not understand in this statement whether
Christophe> you are calling through an A*, B* or C*.
An A*: that's the point of figuring out what to put in the A-in-B
vtable. (In general, we might be calling through something derived
from `A' but where `A::f' is the unique final overrider in that
objects static type. So, we first convert to `A*' -- then use the
A-in-B vtable to get to `C::f'.)
>> I still can't see why it is a win to use vcall offsets in the
>> case where `A' and `V' are not the same class. You already
>> have to do one static adjustment in the entry point -- why not
>> just adjust all the way to `B' directly, without bothering to
>> look up the vcall offset?
Christophe> I did not understand this question :-( If you feel a
It seems to me, from:
For each secondary vtable from a base 'D' which is a non-virtual
base of a virtual base 'E', an additional entry point is generated
in the secondary vtable for 'E' which first performs the constant
adjustment from D* to E*, then the adjustment from E* to A* by
adding the vcall offset for f stored in the secondary vtable for E.
(which I admittedly do not understand -- hence this email!) that there
are some situations where we have an entry point which *both* performs
a constant adjustment *and* adjustes via a vcall offset. In other
words, it does two adjustments. So the code for that entry point will
look like:
entry:
adjust by constant to a virtual base
jump to vcall-adjusting-entry-point
(Then, elsewhere, we have:
vcall-adjusting-entry-point:
adjust by vcall offset
f:
/* here's where user code starts */)
Note that `entry' needs a jump at the end -- it can't just
fall-through (in general) because there may be lots of different such
double-adjusting things. So, I'm asking why this is a win over just:
entry:
adjust by constant all the way to the destination
jump to f
The point is that if we're going to adjust by a constant and then
jump, we might as well adjust all the way.
Christophe> static thunk is better than a dynamic lookup, then you
Christophe> can emit that thunk and point to it through the
Christophe> vtable, as Jason pointed out. The ABI allows you to do
Christophe> that.
That's unclear. That may have been the intent, but there's no
discussion of what's normative and what's not in the ABI.
The ABI says:
For each secondary vtable from a virtual base class 'C' which
defines f, an additional entry point is generated which performs
the adjustment from A* to C* by adding the vcall offset for f
stored in the secondary vtable for A.
It doesn't say that you can adjust in some other way, for instance.
It also says that the caller adjusts to the class containing the
unique final overrider. It should just say that the caller locates
any class containing a virtual function table entry for the function,
converts `this' to that class, and calls the corresponding entry.
*Which* class is chosen isn't an ABI issue. Then, there should be a
non-normative note that says (Note: it is probably most efficient to
convert to the class containing the unique final overrider.)
>> Furthermore, the actual algorithm used to perform the
>> adjustments does not seem necessarily to be part of the ABI.
>> The layout of the vtables is certainly part of the ABI. But,
>> if one compiler wants to completely ignore the vcall offset
>> entries in the vtables, and compute the entire adjustment
>> statically, shouldn't that be permitted by the ABI, even though
>> it might require one extra branch? Surely that's just a
>> quality-of-implementation issue?
Christophe> Correct. Again, that seems clear to me from Jason's
Christophe> writeup:
Christophe> Note that the ABI only specifies the multiple entry
Christophe> points; how those entry points are provided is
Christophe> unspecified. An existing compiler which uses thunks
Christophe> could be converted to use this ABI by only adding
Christophe> support for the vcall offsets. A more efficient
That's the sentence that's unclear. It should say something more
like:
An existing compiler which uses thunks could be converted to use
this ABI by adding vcall offsets in the vtable (as required by the
ABI). (The thunks generated need not be modified.)
I'm not trying to split hairs, or polish prose. These are places
where I'm legitimately unsure as to the intent of the words written.
--
Mark Mitchell mark at codesourcery.com
CodeSourcery, LLC http://www.codesourcery.com
More information about the cxx-abi-dev
mailing list