summary_class-lowering.md 3.3 KB

Step: Clarion CLASS lowering — design (not yet implemented)

Status: not started (scoped here). Suite 137/137; fixpoint OK.

Current state

Classes parse, scope and check — fields, methods, = consts, single/multiple parent lists, VIRTUAL, and CLASS IMPLEMENTATION blocks with nested procedures — but every class/method ends in error 230 and method bodies are suppressed (QbeGen.SetNoEmit(TRUE) + AbortFunc in MethodImpl). See docs/OOP.txt for the grammar.

Goal

Make a class a usable value with callable methods:

TYPE
  CLASS Point;
    x, y : INTEGER;
    PROCEDURE Set(a, b : INTEGER);
    PROCEDURE Get() : INTEGER;
  END Point;
CLASS IMPLEMENTATION Point;
  PROCEDURE Set(a, b : INTEGER);
  BEGIN x := a; y := b END Set;
  PROCEDURE Get() : INTEGER;
  BEGIN RETURN x + y END Get;
END Point;
VAR p : Point;
BEGIN p.Set(3, 4); ExitCode := p.Get() END

Design sketch

  1. Layout — a class is a record: fields get declaration-order offsets via the existing ComputeOffsets/FieldOffset machinery (already shared with RECORD; PushRecord already accepts FClass). obj.field then works through the record field path. Single inheritance prepends the parent's fields (offset by the parent's size); multiple parents stay 230.

  2. Receiver — each method gets a hidden first parameter THIS of type POINTER TO <class>. Inside a method body, a bare field name f is an implicit THIS^.f; the class scope already holds the fields as KindField, so Design needs a "current receiver" addressing mode (like WITH): push the receiver register at method entry, add FieldOffset to it, load/store.

  3. Calls — obj.M(args) binds statically to the method's mangled symbol and passes ADR obj as the hidden first argument, then the user arguments. QbeGen.CallBegin/ArgList gain a flag for the implicit receiver. Non-virtual first; VIRTUAL accepts but dispatches statically with a 230-free path.

  4. Dispatch (later) — a per-class vtable (one static array of code pointers, parent entries first) stored as a hidden first field; VIRTUAL calls load the slot and call indirectly (procedure types

    • indirect calls already exist from step 8.5). This can be a follow-on increment.
  5. WITH-style field binding — reuse PushRecord/PushWith; the receiver is the WITH base, so WITH p DO x := 1 END works for free once (2) lands.

Acceptance (planned)

  • The Point program above compiles and exits 7.
  • obj.field read/write, method calls with value and VAR params, methods returning values.
  • Single inheritance: child methods see parent fields.
  • Suite green; fixpoint still byte-identical.

Risks

  • Design's addressing is threaded through several productions; adding a receiver mode must not change existing RECORD/WITH codegen (fixpoint is the guard).
  • VIRTUAL/vtable must not perturb layout for non-virtual classes.

Files (planned)

compiler/src/M2.atg (ClassRest/ClassImplRest/MethodImpl lowering, Design receiver mode, call-site receiver), compiler/src/SymTab.def/.mod (receiver scope, parent field offsets, vtable), compiler/src/QbeGen.def/.mod (receiver param, vtable emission, indirect VIRTUAL calls), compiler/tests/t_class*.mod, docs/OOP.txt.