I cannot understand the implementation of inheritance in Google's V8 JavaScript engine. It clearly (?) implements an inheritance hierarchy, but seems to completely do away with virtual functions.
This is the inheritance hierarchy as detailed in the objects.h
header file:
// Inheritance hierarchy:
// - Object
// - Smi (immediate small integer)
// - HeapObject (superclass for everything allocated in the heap)
// - JSReceiver (suitable for property access)
// - JSObject
// - JSArray
// ... and many more entries
Most object types are derived from Object
, which is declared as follows:
// Object is the abstract superclass for all classes in the
// object hierarchy.
// Object does not use any virtual functions to avoid the
// allocation of the C++ vtable.
// Since both Smi and HeapObject are subclasses of Object no
// data members can be present in Object.
class Object {
// ... bunch of method declarations and definitions
};
The relatively simple Smi
class is declared next:
class Smi: public Object {
public:
// methods declarations and static member definitions
};
and so on.
For the life of me, I cannot understand how can, say, an instance of Smi
can be used as an Object
; there are no virtual functions and I cannot find overrides in the the implementation file, objects.cc
. At 17,290 lines, though, trying to understand what is going on is proving a difficult task.
As another difficulty, I found an ObjectVisitor
class in the same header file (this one is more classical; it consists of virtual methods). But I could not find the equivalent Accept(Visitor*)
(or similar) method in the Object
base class.
What I am asking in concrete is for a minimal example that illustrates how does this inheritance pattern works.
The classes in objects.h do not actually define real C++ classes. They do not have any fields. The classes are merely facades to objects managed on the V8 JavaScript heap. Hence they cannot have any virtual functions either, because that would require putting vtable pointers into the JS heap. Instead, all dispatch is done manually, via explicit type checks and down casts.
The this
pointer inside methods isn't real either. For smis, this
is simply an integer. For everything else it is a pointer into the V8 heap, off by one for tagging. Any actual accessor method masks this pointer and adds an offset to access the appropriate address in the heap. The offsets of each field is also defined manually in the classes.