writeonce/docs/plan/oop-vm/00-wob-format.md
shoney.arickathil 2de724df11 feat(compiler): MVS ownership pass + woc driver; drop Money/SKU/Float, reject abstract
Completes plan 2 Tasks 7-8. owner.ml: mutable-value-semantics flow analysis
producing the four plan-3 emitter tables (moves, drops incl. LIVE-MASK for trap
unwinding, rc with elision, residual borrow sites) plus WO-E301-304 two-site
diagnostics. Alias questions run over canonicalized places, so a double-mut
reached through let-bound aliases lands in the residual table like the direct
form; dump.ml's contract notes the emitter must coalesce guards per operand.
main.ml: directory discovery, cross-file programs (symbols merge before bodies
check), diagnostics ordered by (file,line,col), new WO-E214 for a name declared
in two files. New docs/plan/oop-vm/01-error-catalog.md (14 emitted + 10 reserved
codes), un-ignored so both plan tracks can cite it; justfile regains woc-*.

builtin_scalars is now the five that work: Int, Bool, Text, Timestamp, Id.
Money/SKU/Float and the abstract_types allowlist are gone — `abstract` never
lexed, and Float had no literal syntax and no wob kind, so no value could exist.
Fixtures and samples retype Money->Int, SKU->Text. The abstract newtype feature
is rejected outright (verdict row adopt->reject); haxe-parity Task 7 keeps `is`.

nullable-types-implementation.md corrected: ?T is plumbed but UNENFORCED
(E211-213 declared, never emitted; probe exits 0), handed to haxe-parity Task 6
as next work item. Records all 10 dead codes incl. E205 — interface satisfaction
is unchecked. crates/rt keeps its Money/SKU fixtures (opaque strings, Stage 2).

Gate: build warning-clean, 14 + 264 checks 0 failures, pricing golden exit 0,
docs/examples histograms unchanged (13/70, zero WO-E225).
2026-08-10 21:24:50 +02:00

4.2 KiB
Raw Blame History

The .wob format v1 — normative reference

Copied verbatim from the normative section of docs/superpowers/plans/2026-08-01-wob-format-and-vm-core.md (plan 1 of the approved spec 2026-08-01-oop-compiler-vm-design.md). The machine-readable twin is runtime/src/wob.h — constants there and prose here must never disagree. The test-side assembler runtime/test/wob_build.c is a second, independent encoding; builder/loader disagreements surface as test failures.

All integers little-endian; offsets are absolute file offsets.

Header (44 bytes): magic "WOB1", version 1, then offset/count u32 pairs for the constant pool, class table, interface section, and method table, then a u32 entry-method index (all-ones = none).

Constant pool — sequential entries: one tag byte; tag 0 = i64 follows; tag 1 = text (u32 length + bytes, no NUL).

Class table — per class: name constant index, flags u32 (bit0 = instances are @gc), field count, then one kind byte per field padded to a 4-byte boundary. Field kinds: 0 SCALAR, 1 OWNED, 2 GCREF, 3 TEXT, 4 MULTI, 5 MAP. Runtime object layout: 16-byte header then one 8-byte slot per field, in declaration order.

Interface section — per interface: name constant index, method count. Global slot ids are assigned sequentially across interfaces in declaration order. Then a vtable row count and rows: class id, interface id, one method index per interface method.

Method table — per method: name constant index, class id (all-ones = free fn), arg count u8, register count u8, reserved u16, code length in bytes (multiple of 4), the u32 instructions, a line table (count + ascending pc→line pairs), and a drop table (count + ascending entries of pc, owned-register bitmask u64, gc-register bitmask u64). Drop-table lookup = last entry with pc ≤ current pc; no entry means nothing live.

Instructions — fixed 32-bit, Lua-style fields: opcode byte, A byte, then either B and C bytes or a 16-bit Bx (signed jumps encode as Bx − 32768).

op name semantics (in words)
0 NOP nothing
1 LOADK A Bx register A = constant Bx (int inline; text = pointer to interned const string)
2 MOVE A B copy register; for owned values this IS the move — compiler guarantees the source is dead
3–7 ADD/SUB/MUL/DIV/NEG i64 arithmetic, two's-complement wrapping (no signed-overflow UB); DIV traps on zero divisor and on INT64_MIN ÷ −1
8 CONCAT A B C new owned text from two texts
9–12 EQ/LT/LE/EQS i64 compares and text-content equality, result 0/1
13–14 JMP / JZ relative jump (JZ when register A is zero)
15 CALL A Bx call method Bx; callee's register window starts at caller base + A (register-window overlap, Lua-style); args sit at A, A+1, …; return value lands back in slot A
16 ICALL A Bx interface call by global slot id Bx; receiver in A; vtable lookup by the receiver's class
17–18 RET A / RET0 return value from register A (or zero), pop frame
19 NEW A Bx new zeroed instance of class Bx
20–21 GETF / SETF field read/write with runtime null/native/bounds checks (trap T_BOUNDS); overwriting a non-scalar field does NOT auto-drop the old value — the compiler emits the drop
22 DROP A recursively drop the owned value in A per its class drop plan, null the register
23–26 BORROW_S/BORROW_X/RELEASE_S/RELEASE_X borrow-word ops on the object in A; violation traps T_BORROW
27–28 RC_INC / RC_DEC refcount ops on the @gc object in A
29 BUILTIN A B C register A = builtin C applied to args starting at register B (fixed arity per builtin; multi_new/map_new carry kind immediates in B instead)
30 DB_STUB trap T_DB "engine not linked" (spec: SQL-layer statements in milestone 1)
31 TRAP Bx explicit trap with code Bx

Builtins: now (ms), print (text), print_int, words (whitespace token count), multi_new/multi_push/multi_get/count/latest, map_new/map_set/map_get/map_has.

Trap codes: DIV0, BORROW, STACK, OOM, DB, BOUNDS, KEY, EXPLICIT.