summary_virtual-dispatch.md 3.0 KB

Step: virtual dispatch (vtables)

Tag v3-virtual-dispatch. Suite 143/143; fixpoint OK (image 2,310,300 bytes).

What

VIRTUAL methods now dispatch dynamically: a base-class pointer holding a derived object calls the derived method.

TYPE
  CLASS Shape;  VIRTUAL PROCEDURE Draw() : INTEGER; END Shape;
  CLASS Circle (Shape); VIRTUAL PROCEDURE Draw() : INTEGER; END Circle;
...
VAR b : POINTER TO Shape; c : POINTER TO Circle;
BEGIN
  NEW(c); b := c;             (* subtype pointer assignment *)
  ExitCode := b^.Draw()       (* -> Circle.Draw, decided at run time *)
END

How it works

  • vtable — each class with virtual methods owns a slice of a shared pool (SymTab.BuildVTable): the parent's slots are inherited first, and a same-named virtual method overrides in place; new virtual methods append. Each method node records its slot (vslot).
  • vptr — a class that introduces virtuals gets an 8-byte vtable pointer field (VptrOffset): at offset 0 when the class has no parent (or the parent already has one), otherwise right after the parent's fields. Subclasses share the inherited slot, so a derived object's vptr identifies its dynamic class.
  • Emission — QbeGen.EmitVTables writes one data $vt_<typeindex> = { l $Method_<uid>, … } per class; a method declared but never implemented is l 0.
  • Initialisation — a class-typed static variable (DeclRec) and NEW (InitHeap) both store $vt_<typeindex> in the vptr field.
  • Dispatch — a virtual call loads the vptr from the receiver, fetches the slot's code pointer, and calls indirectly (QbeGen.VirtCallBegin, reusing the existing indirect-call path). The receiver is passed as the hidden first argument, as for any method.
  • Subtyping — POINTER TO Derived assigns to POINTER TO Base when Derived inherits Base (SymTab.IsSubclass), which is what makes a base pointer able to hold a derived object.

Verified

  • t_virtual.mod (3): a Shape pointer holding a Circle then a Square dispatches to each override.
  • vt_<Shape> = { Draw_0 }, vt_<Circle> = { Draw_1 } (same slot, overridden).
  • Non-virtual classes, VIRTUAL-with-static-only usage, and the whole existing suite (143) still pass; fixpoint byte-identical.

Still open

  • Bare sibling-method calls inside a method body (Add(a) rather than obj.Add(a)); a class BEGIN … END init body; multiple parents. VIRTUAL coupled with VAR/value formals is covered by the ordinary method path.

Files

compiler/src/SymTab.def/.mod (vtBase/nVirt/vtName/vtUid/ tvptr, BuildVTable, HasVTable, VirtSlot, VptrOffset, VtCount/VtName/VtUid/VtHasBody, VtClassCount/VtClassAt, IsSubclass), compiler/src/QbeGen.def/.mod (VtRef, EmitVTables, VirtCallBegin, vptr init in DeclRec/InitHeap), compiler/src/M2.atg (ArgList virtual dispatch), compiler/tests/t_virtual.mod, compiler/run_tests.sh, docs/features.md, docs/OOP.txt.