[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