An implementation issue: Polymorphic types and separate compilation

Pages: 12
Hi there,

I have a problem that is not related to how C++ is seen from the ISO standard point of view (external) but this is an implementation issue (internal). However this might be related to other standards, maybe those related to object file formats that are inputs to linkers, or maybe to some binary standards related to polymorphism and multiple modules.

First I will make some clarifications just to make sure we understand each other:

DEFINITION: A polymorphic type is a type that has at least one virtual member function, either defined by the type itself or by one or many of its parent types if any.

AN IMPLICATION FROM ISO C++: It must be possible to determine the actual type of a polymorphic type when pointed by a pointer of any of its parent polymorphic types at run time.

RATIONALE: When accessed by pointers (or references) polymorphic types bind virtual functions to objects they point to considering the actual type of the object the pointer points to. The actual type of the object might override a virtual function inherited from a parent type and accessed with the parent type pointer (or reference) and the function binding has to be delayed since the actual type of the object can only be determined at run time.

IMPLEMENTATION: Typically polymorphic types are implemented in three parts:
1) Virtual function table (VFT): The table is not part of a polymorphic object but is common to all objects of a polymorphic type, that is to say there is one VFT per polymorphic type. VFT of a type T contains function pointers to all virtual functions accessible to T with a pointer (or reference) of any polymorphic parent type. The order of virtual function pointers in a VFT is determined by the order of the virtual function declarations in the type that declares these functions. For example if a type D has polymorphic types B1 and B2 as parents and B1 and B2 do not have any parents then virtual function table of D has three parts: a part that is specific to D and which contains pointers to virtual functions declared in D, a part that contains pointers to virtual functions declared in B1 and a part that contains pointers to virtual functions declared in B2. The actual addresses of functions might differ in different VFTs. For example the pointer to a virtual function F declared in B2 might be different in VFT of D and VFT of B2 since D might redefine F but the relative order is the same. All types that inherit from B2 (either directly or indirectly) have part of their VFT identical in order to VFT of B2. VFTs are created/defined and filled with data during compiling/linking.

2) Pointer to the virtual function table. One per object. The pointer has to be at the top of object data.

3) Type specific instance data. One per object. This part is contained in non-polymorphic types as well.

Having this implementation and a pointer to a polymorphic type it is easy to generate code that can bind a virtual function to an object at run time. All that is needed is to determine the type of the object and that can be done by looking at the first object data - the pointer to the virtual function table.

In expression P->F() static analysis done by the compiler goes as follows:
A) compiler first checks that P is a pointer type and that F is accessible by objects of any type that P can point to, and that means that F has to be member function of either P's type or any of its parent types.
B) compiler then checks if P is a pointer to a polymorphic type:
1. If it is not then F is certainly non-virtual and is bound to object pointed with P statically.
2. If P is a pointer to a polymorphic type the compiler then checks if F is virtual:
i. If it is not then F is bound to the object statically.
ii. If F is virtual the compiler knows that F has to be bound to the object dynamically (at run time).

In order to enable dynamic (late) binding compiler inserts the code into the compiled program that has to find F's entry in the VFT of the actual polymorphic type that P points to. In order to do that the code first has to check the actual type of the object. The type is determined by the object's first data - the pointer to the virtual function table. The address of the VFT might be the determining factor or maybe a number at the very top of VFT might uniquely identify the polymorphic type to make the decision process independent of the VFT's address. Having identified type of the object at run time the code has to "parse" the VFT - it has to find part of VFT that corresponds to the VFT of the type where F is declared and that can be type of P or any of its parent types. The code has to "translate" the pointer to the VFT to the part of VFT that has F. The translation function depends on the actual type of the object pointed with P and the type where F is declared. When part of the VFT is found the code indexes that part of VFT with the number that corresponds to the ordinal number of F in its type definition.
Last edited on
PROBLEM: When all the polymorphic types and all the functions are defined in one source file there is no problem. The compiler generates VFT's for each polymorphic type and fills them with numbers. However what in the case of separate compilation? I see a problem with VFT's in case of separate compilation.

Let's assume the following scenario: Non-member function F is defined is source file Source1. It returns a pointer P to a polymorphic type T. The function is called from source file Source2. This situation implies that the function has to be declared in both source files, and that implies that the type T and all its parent types have also to be defined in both source files. But what about children types of T? When F returns an object it must be possible to point to it with pointer to T. That implies that the actual type of the object is either T or any of its children types. In order to create (instantiate) such an object the code in Source1 must define the type of the actual object (T or any of its children types). The code in Source1 cannot instantiate an object if it does not know about its type, so all the possible child types have to be defined in Source1. But what about Source2? Let's assume that the code in Source2 does not instantiate any of children types of T. It accesses the object returned by F only through pointer to T or pointer to any of T's parents. This implies that there is no variable in Source2 that has any of children types of T. At first look it looks like that there is no need for Source2 to know anything about any of children of T. But that conclusion is wrong! The code in Source2 that is generated by the compiler cannot parse VFT's of any of the possible actual types of object returned by F (and that is used in the process of late binding described above) if it is not aware of the T's children types. The code in Source2 cannot understand a VFT (in order to parse it) if it is not aware of its type. Let's assume that polymorphic type T has three child types C1, C2, C3 as defined in Source3. Let's also assume that member functions of T and all its parent and child types are defined in source file Source3. Let's assume that the code in Source1 generates only two possible return types T, C2 and C3. In that case there is no need to define C1 in Source1. But what would happen if we defined only T, its parent types and C3 in Source2? The code in Source2 would then be ready to accept objects of type T or C3. But since F as defined in Source1 can return also C2 what would happen in that case? That would be an error but that error cannot be detected by the compiler but instead would have to be detected by the compiled program. The code in Source2 would attempt late binding and read the object's first data (pointer to VFT) and then it would realize that pointer to VFT points to a structure (C2) that is not recognized. The compiled program would have to report error at run time. So the conclusion is that not only T and all of its parent types but also all of the possible child types of T have also to be defined, the whole family tree of T. This fact has an important implication: if the family tree changes, for example a new child type C4 of T is added to the tree, then the Source2 would still have to be recompiled with C4 defined even though the source code of the member functions in Source2 might not change (all the polymorphic algorithms remain the same as specified in the relevant functions).

QUESTION: How are VFT's filled with data in case of separate compilation? Are there any extra requirements on linkers and their input file formats (COFF, OMF…) in order to support separate compilation with polymorphic types?

The essence of the problem is that unlike other types polymorphic types rely on data (VFT's) that has to be defined globally (i.e. once for the whole program) and filled with data by the compiler/linker. In case of non-polymorphic types there are no interoperability problems involved when the program is assembled from several object modules that are complied with different compilers or even different languages. However polymorphic types as their first data have pointers to tables that have to be defined globally.

If we think about the question further the original question multiplies:
1. What are the problems involved when we compile different source files with different C++ compilers?
2. What are the problems involved when we compile different source files with C++ and non-C++ compilers? Is it theoretically possible to have interoperability with object modules compiled with different languages and polymorphic types involved? For example a polymorphic algorithm is implemented in a function F that has a polymorphic type as input parameter and is specified in Source_file_A that is compiled with C++ compiler, functions that generate the objects are implemented in another language in another Source_file_B, and finally member functions of polymorphic types are implemented in a Source_file_C and written in a third language.

Let's assume that in these examples all the compilers produce object files in any of the standard object file formats (COFF, OMF…).
It looks like to me that in order for these examples to work there has to be a binary standard involved and the compilers would have to comply to that standard. That standard should not be part of any language specification since the idea is to support interoperability with modules implemented in different languages. I am aware that there is a binary standard published by Microsoft called COM but I think it cannot cover the case in 2 since it mandates that COM modules (components) implement object generation and member function in one module.

RATIONALE FOR THE QUESTION: The reason I ask the question is not because I am interested in implementing C++ compilers but because I want to fully understand all the dependencies involved when having separate compilation and polymorphic types.
POSSIBLE SOLUTIONS:

1. I do not know much about linkers but I do know that they should be as programing language independent as possible. Ideally a linker should not know anything about C++ or Pascal or ADA. The input file formats (object files) for linkers should also be as much language independent as possible. These formats should only support those features that are common to all languages. They should not be extended in order to support very particular feature (polymorphism) of very particular language (C++). However this solution puts some demands on linkers. In this solution the complier would generate VFT's independently in each translation unit. In our case there would be two VFT's for type T and all its child types once in Source1 and once in Source2. When F defined in Source1 returns pointer to T to the caller in Source2 the returned object would point to the VFT of T or any of its child types in Source1. But the address of VFT of T in Source1 cannot be determining factor for type ID for code in Source2. That implies that a number has to be added at the top of each VFT that would have to be used as type ID in the late binding process described above. However the problem with that is how to ensure that these numbers have unique ID's globally? A translation unit would "export" polymorphic type names in a special section of the object file generated by the compiler. The linker would assemble all the object files generated by the complier and assign a unique number for each "exported" type and fill the VFT's with data. I consider this solution extremely "dirty" due to the violation of basic principles I outlined at the beginning of this paragraph but in theory it could work. It would be an ad-hoc solution. But nevertheless please note that regardless of the solution the linker has to fill VFT's with data in case of separate compilation anyway since only the linker knows the addresses of member functions for T and all its parent and child types.

2. In this solution the compiler would automatically generate hidden global variables that would represent VFT's, one per polymorphic type, e.g. VFT_T for type T, VFT_P1 for parent P1, VFT_P2…, VFT_C1 for child C1, VFT_C2… As is the case with "normal" global variables (those defined by the programmer) these automatically generated hidden variables would also have to be defined once (in one source file) but declared (with extern keyword) in all the others. There are two questions:
a) In which source files (that is plural) should the compiler generate hidden declarations (not definition!) for VFT of a type T?
b) In which source file (that is singular) should the compiler generate hidden definition (not declaration!) for VFT of a type T?
The answer to question a) is the following: in all source files where T is defined.
The answer to question b) is the following: in the source file where all the member functions of T are defined.
The automatically generated hidden variables would not be visible in the source files however they must be visible to the linker in the import/export sections of the object files generated by the compiler. However this is not an extra requirement on the linkers since the linkers must be able to resolve addresses of global variables accessible to different object modules anyway.
This solution is much more clear and acceptable than the first solution since it does not put any extra requirements on the linker or any of its input file formats. However the complier would have to impose discipline of putting all the definitions of member-functions of a type T in one source file. Does ISO C++ specification mandate such rule?

These are just my possible answers to the question I asked. However there might be other possible answers. Does anyone know how this actually work?

Thank you for your cooperation.

Best regards
Ideally a linker should not know anything about C++

A good C++ linker needs to know a great deal about C++ to perform link-time optimizations, ODR violation detection, etc. A crappy one can get by with just the weak symbols to handle template instantiations, inline functions, and their statics. But there is nothing for the linker to do to support virtual function calls.

In order to enable dynamic (late) binding compiler inserts the code into the compiled program that has to find F's entry in the VFT of the actual polymorphic type that P points to. In order to do that the code first has to check the actual type of the object

What? Why? It just dereferences the pointer, adds f's number in the list of virtual functions of the static type of *P and calls what's there, look at the actual code produced by the compiler if you don't have a reference handy.
Last edited on
PROBLEM: When all the polymorphic types and all the functions are defined in one source file there is no problem. The compiler generates VFT's for each polymorphic type and fills them with numbers. However what in the case of separate compilation? I see a problem with VFT's in case of separate compilation.


There is no difference. The compiler generates VFT's for each polymorphic type, and pointers to the VFT's are assigned by the compiler to an object when its (most derived) constructor is invoked. The only thing client code needs to know is where the pointer to the VFT is. The type can be deduced from the VFT pointed to.

There is no special handling required when linking given the typical implementation.
What? Why? It just dereferences the pointer, adds f's number in the list of virtual functions of the static type of *P and calls what's there, look at the actual code produced by the compiler if you don't have a reference handy.


Different compilers generate different code. This is an implementation choice of the compiler how to actually translate late binding. My description is externally identical to what you have described. One way or another the generated code has to consult VFT for the late binding since there are pointers to all the virtual functions for the actual type of the object. There is no performance penalty for the late binding in the way I described it. Anyway this is not relevant for the question I asked.


A good C++ linker needs to know a great deal about C++ to perform link-time optimizations, ODR violation detection, etc. A crappy one can get by with just the weak symbols to handle template instantiations, inline functions, and their statics. But there is nothing for the linker to do to support virtual function calls.


As I said in my first post I am not expert on the liking but your comment implies that in a situation of linking 3 object modules (lets assume there are no polymorphic types involved) generated by C++, Pascal and COBOL compilers a (good) linker is supposed to know all three languages. That would mean that not only there are "good C++ linkers" but also "good C++/Pascal linkers" or "good Pascal/COBOL/FORTRAN linkers". The idea behind ODR is language independent. Anyway your comment seems to eliminate "Possible Solution 1" as a real solution to the problem I described. Even I said it is "extremely dirty" so I am glad we eliminated it.

Thank you for your comments.
There is no difference. The compiler generates VFT's for each polymorphic type, and pointers to the VFT's are assigned by the compiler to an object when its (most derived) constructor is invoked. The only thing client code needs to know is where the pointer to the VFT is. The type can be deduced from the VFT pointed to.

There is no special handling required when linking given the typical implementation.


Having all the code in one source file and having the code distributed over several source files is certainly not the same as regards filling VFT's with data since in the first case compiler does all the work but in the second case assistance from the linker is needed. I think you didn't understand my question - I didn't say that separate compilation using the same compiler with polymorphic types involved does not work. I asked how it works.

Thank you for your comment.
Last edited on
Having all the code in one source file and having the code distributed over several source files is certainly not the same as regards filling VFT's with data

It is exactly the same.


since in the first case compiler does all the work but in the second case assistance from the linker is needed.

No special assistance from the linker is required.


I think you didn't understand my question - I didn't say that separate compilation using the same compiler with polymorphic types involved does not work. I asked how it works.

I understand your question fine.
It is exactly the same.


Can you then answer my question please - how does it work? Please elaborate. As an example please use scenario I described in my second post under section: PROBLEM
Last edited on
Quark wrote:
At first look it looks like that there is no need for Source2 to know anything about any of children of T. But that conclusion is wrong!

That conclusion is right. That is exactly the point of polymorphism: the user code does not need to know anything about the dynamic type of the object it accesses using a reference or pointer to base, OOP purists would even say "is forbidden to know".

It's very hard to make sense of the rest of that "problem". In any case, it has nothing whatsoever to do with linking.
Last edited on
That conclusion is right. That is exactly the point of polymorphism: the user code does not need to know anything about the dynamic type of the object it accesses using a reference or pointer to base, OOP purists would even say "is forbidden to know".


This is exactly what I had in mind at first when I wrote that. It didn't smell good to me but I was satisfied with the fact that it looked to me that the source code of the functions that use the polymorphic objects wouldn't have to be changed but only recompiled with child types defined. However this is a mistake, I admit.

Can you explain to me how the code in Source2 can "parse" VFT of a dynamic child type that it is not aware of and that is defined only in Source1? The code in Source2 has to find a virtual function's position in the table. The generated code in Source2 has to somehow extract part of the dynamic child type's VFT that corresponds to type T of the function's F return type. But how can it extract that part if it does not know anything about the structure it is extracting from?

Thank you for your comment.
The generated code in Source2 has to somehow extract part of the dynamic child type's VFT that corresponds to type T of the function's F return type. But how can it extract that part if it does not know anything about the structure it is extracting from?

It knows T. It knows how many virtual functions are defined or inherited into T. To call T's 3rd virtual function with arguments args, for example, it will emit code (*vptr+3)(this, args), as you can see in the compiler output.
It knows T. It knows how many virtual functions are defined or inherited into T. To call T's 3rd virtual function with arguments args, for example, it will emit code (*vptr+3)(this, args), as you can see in the compiler output.


I understand that part, that is rather trivial. My problem was that I expected that code in Source2 would receive pointer to a child type since I thought that Source2 must be aware of the dynamic type of the object. So I expected that returned object's first data - pointer to VFT - would point to the beginning of the child's VFT. However the answer to my question I asked in my last post is that the translation (extraction I was talking about) is actually done in Source1 where all the child types are defined - the pointer to child is translated to pointer to T in Source1. The returned pointer would point to part of child's VFT that corresponds to VFT of T. Then comes what you wrote.

But now I have another problem:

I assume that all polymorphic objects when instantiated are implemented as I described in my first post under IMPLEMENTATION:

1) Pointer to the object's VFT
2) Immediately followed with object's instance data

So a pointer to a polymorphic object actually points to the object's pointer to VFT.

So I expected that code in Source2 would receive pointer to instance of child object. I disregarded the essential translation from child's pointer to pointer to T in Source1.

But now we have two "pointers": a pointer to part of VFT (vptr) and a pointer to instance data (this). Now pointer to a polymorphic type T does not point to a structure where pointer to VFT is immediately followed with instance data.
The only way to explain dilemma I had in mu last post is the following:

I made a wrong assumption in the IMPLEMENTATION part of my first post:

1) Virtual function table (VFT): The table is not part of a polymorphic object but is common to all objects of a polymorphic type, that is to say there is one VFT per polymorphic type.


OK

2) Pointer to the virtual function table. One per object. The pointer has to be at the top of object data.


WRONG

The pointer to VFT is not part of object data! It is part of a polymorphic pointer data. If a pointer is not polymorphic there is no such data.

NEW ASSUMPTION:

A polymorphic pointer has two parts:

1) a pointer to a VFT or to a part of the VFT that corresponds to the static type of the pointer
2) pointer to instance data

A non-polymorphic pointer has only one component:

1) pointer to instance data


Is my new assumption correct?
No. The virtual table pointer is part of the object. If an object didn't point to the virtual table of its final type, then it would not be possible to rebind a pointer:
1
2
3
4
5
6
Mammal *p = get_mammal("dog");
p->speak();
//Output: Woof!
p = get_mammal("cat");
p->speak();
//Output: Meow! 
If the virtual table pointer is part of p itself and not part of the objects, how does p update itself at run time?
Now I am even more confused than I was before.

I will concretize the scenario I described in my first post in section PROBLEM:

There are 3 source files:

Source1:
1
2
3
4
struct T { virtual void fT(); };
struct C : T { virtual void fC(); void fT(); };

T * F(){ return new C; }


Source2:
1
2
3
4
5
6
7
8
9
struct T { virtual void fT(); };

T * F(), * pT;

void main()
{
	pT = F();
	pT->fT();
}


Source3:
1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;

struct T { virtual void fT(); };
struct C : T { virtual void fC(); void fT(); };

void T::fT() { cout << "Inside T::fT" << endl; }
void C::fC() { cout << "Inside C::fC" << endl; }
void C::fT() { cout << "Inside C::fT" << endl; }


Question1: How many VFT's are there for type T in the linked program? Is there one global VFT for T for the whole program or are there 3 independent VFT's (at three different addresses), one in each translation unit?

Question2: When F returns T * in main what kind of data structure does the pointer point to? Does the data structure has a pointer to VFT as the first data? If so where does that VFT pointer point to?
Last edited on
The pointer to VFT is not part of object data! It is part of a polymorphic pointer data. If a pointer is not polymorphic there is no such data.


That's kin to saying some member x is not a member of class Y because x is not a member of class Z.
How many VFT's are there for type T in the linked program? Is there one global VFT for T for the whole program or are there 3 independent VFT's (at three different addresses), one in each translation unit?
There's two. You need one for every different type with one or more virtual functions.

When F returns T * in main what kind of data structure does the pointer point to?
A C.

Does the data structure has a pointer to VFT as the first data?
The virtual table pointer isn't required to be placed at offset 0, but yes, the object contains a virtual table pointer, as I said.

If so where does that VFT pointer point to?
To the virtual table of C, since the object is a C, and C is the last class in C's inheritance tree that contains virtual functions.
Question1: How many VFT's are there for type T in the linked program? Is there one global VFT for T for the whole program or are there 3 independent VFT's (at three different addresses), one in each translation unit?

Both statements are true: There is one vtable for T in each translation unit where T is odr-used. Since they are emitted as defaulted weak symbols (you can see that with nm on Linux), there is one vtable for T in the linked program. Translation units do not exist after linking. (there's also a vtable for C in every TU where C is used)

Question2: When F returns T * in main what kind of data structure does the pointer point to?

It's pointing to the C object. Within the C object there is a pointer to C's VFT. The main function doesn't know any of that, it treats it exactly as it would treat any T object.
Last edited on
Pages: 12