[fpc-devel] Using case statement instead of VTable

Marco van de Voort fpc at pascalprogramming.org
Tue Apr 11 12:32:24 CEST 2023


On 11-4-2023 12:11, Hairy Pixels via fpc-devel wrote:
> Btw, I was curious because I haven’t done this in so many years but is this basically how a VTable looks in procedural code?

Every class has a reference to class type information stored at negative 
offsets. The class type info contains VMT, IVMT, the class name etc.  
The table is compile time (iow a structured constant in procedural 
pascal) and the layout of that table is roughly  described in objpash.inc:

  const
        vmtInstanceSize         = 0;
        vmtParent               = sizeof(SizeInt)*2;
        { These were negative value's, but are now positive, else classes
          couldn't be used with shared linking which copies only all 
data from
          the .global directive and not the data before the directive 
(PFV) }
        vmtClassName            = vmtParent+sizeof(pointer);
        vmtDynamicTable         = vmtParent+sizeof(pointer)*2;
        vmtMethodTable          = vmtParent+sizeof(pointer)*3;
        vmtFieldTable           = vmtParent+sizeof(pointer)*4;
        vmtTypeInfo             = vmtParent+sizeof(pointer)*5;
        vmtInitTable            = vmtParent+sizeof(pointer)*6;
        vmtAutoTable            = vmtParent+sizeof(pointer)*7;
        vmtIntfTable            = vmtParent+sizeof(pointer)*8;
        vmtMsgStrPtr            = vmtParent+sizeof(pointer)*9;
        { methods }
        vmtMethodStart          = vmtParent+sizeof(pointer)*10;
        vmtDestroy              = vmtMethodStart;
        vmtNewInstance          = vmtMethodStart+sizeof(codepointer);
        vmtFreeInstance         = vmtMethodStart+sizeof(codepointer)*2;
        vmtSafeCallException    = vmtMethodStart+sizeof(codepointer)*3;
        vmtDefaultHandler       = vmtMethodStart+sizeof(codepointer)*4;
        vmtAfterConstruction    = vmtMethodStart+sizeof(codepointer)*5;
        vmtBeforeDestruction    = vmtMethodStart+sizeof(codepointer)*6;
        vmtDefaultHandlerStr    = vmtMethodStart+sizeof(codepointer)*7;
        vmtDispatch             = vmtMethodStart+sizeof(codepointer)*8;
        vmtDispatchStr          = vmtMethodStart+sizeof(codepointer)*9;
        vmtEquals               = vmtMethodStart+sizeof(codepointer)*10;
        vmtGetHashCode          = vmtMethodStart+sizeof(codepointer)*11;
        vmtToString             = vmtMethodStart+sizeof(codepointer)*12;

The VMT has one pointer for each virtual method in a class. In e.g. 
TAnimal version of the table those pointers would point to the TAnimal 
copy of the virtual methods or a method that raises an "abstract" 
exception if the method is abstract.  The VMTs are linked (the TDog 
table points to TAnimal in its parent pointer, which in turn points to 
its parent) for the IS and AS functionality, but not for VMTs

The TDog version of the table they point to the TDog version.

So basically the tdog.create would only allocate memory, and initialize 
the first "field" with a pointer to the tdog class structured constants, 
and advance the pointer to the next field and return that as the 
instance pointer.

const ddogclassinfo : array [0..3]of pointer = 
(@parent, at vmtmethod1, at vmtmethod2, at vmtmethod3);

If the class (TDog) overrides the method, it points to the tdog 
reference, if not, this table still lists the TAnimal version of the method.


constructor TDog.Create;

begin

   result:=NewInstance; // allocate memory for the instance

   ppointer(result)^:=@dogclassinfo;

   inc(result,sizeof(pointer));

end;


Dispatch then becomes in simplified Pascal  (assume that "instance" is a 
initialized class)

var vmt : pointer;

vmt:=ppointer(instance)^;

tthemethod(ppointer(vmt)[n]).themethod(parameter);

... with n the number of the respective virtual method, and TTheMethod 
its signature.  Handling all those indexes, signatures and generating 
the parameter loading into registers, stack is the core work of the 
compiler.

Note that Delphi 1 had a different way of dispatch, called "dynamic" 
(the 16-bit compiler had to be much more careful with memory), at the 
expense of performance. IIRC that version kind of worked like your 
scheme, where every class only had a table for the methods it really 
had, leaving the rest up to a function in the parent. I don't recall the 
exact details (I suggest an old Delphi manual or mastering Delphi for 
that).  Free Pascal ignores this directive, and doesn't implement it.





More information about the fpc-devel mailing list