# 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. ```modula-2 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_ = { l $Method_, … }` per class; a method declared but never implemented is `l 0`. - **Initialisation** — a class-typed static variable (`DeclRec`) and `NEW` (`InitHeap`) both store `$vt_` 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_ = { Draw_0 }`, `vt_ = { 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`.