writeonce/docs/stories/language-runtime-database/07b-inferred-gc-mark-sweep.md
shoney.arickathil 79605cbb4f feat: milestone 1 complete — .wob emitter, conformance corpus, single binary; GC redesign specced
- `woc` now emits `.wob` that `wovm` runs: emit.ml lowers the typed,
  owner-annotated AST (scope-stack registers with a >64 WO-E401 diagnostic,
  Lua-style call windows, ICALL by slot, dedup const pool, drop maps, line
  tables, implicit terminators); disasm.ml backs `--dump-bc` goldens.
- Ownership lowering consumes the four owner tables verbatim; RESIDUAL is the
  only source of borrow ops, coalesced per operand. Review caught the emitter
  consuming only 2 of owner.ml's 4 residual producers — an assignment-anchored
  aliasing violation ran to exit 0 instead of trapping; fixed, plus a backstop
  raising WO-E404 for any residual region left unconsumed.
- Conformance harness `scripts/oop-e2e.sh` (`just oop-e2e`): four fixture
  kinds with exact outcomes — byte-exact stdout, one WO-E### anchored on
  `error CODE:`, numeric trap code, gc trace. 25 fixtures incl. pricing-demo
  logic, the ownership suite, and DB_STUB's parse-but-trap. `tests/` un-ignored
  so the corpus is actually tracked.
- `woc build` produces a self-contained binary: wovm copy + appended image +
  20-byte trailer, self-exec via /proc/self/exe. Verified relocated outside
  the repo, argless, and against adversarial trailer corruption.
- Milestone 1's five spec criteria all MET (`just oop-accept`). Criterion 3
  closed by WO-E405 — the entry must return `Int`, since program mode already
  says its return value is the exit code — which deletes the leak class
  without adding return-type metadata to the format. `gc/held-cycle` retired:
  an externally-held cycle is not expressible in a post-exit pump.
- New spec: inferred GC + incremental per-shard tri-color mark-sweep, retiring
  `@gc` and reference counting. Story gains iterations 7b (that work) and 9b
  (`@table`, relations, compiler-checked query); `.dev/reference` gains a
  sparse System.Linq checkout. Priority: 5→6→7 (log-watcher) then 7b, 8, 9, 9b.
2026-08-11 19:31:26 +02:00

108 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Iteration 7b — inferred GC + incremental mark-sweep
> Format: `product/story-iteration-template`. Part of
> [Story — one language, one runtime, one database, one binary](00-story.md).
>
> **Inserted 2026-08-11**, after the plan was first drawn — hence `7b` rather
> than a renumber. It sits here because the log-watcher critical path
> (iterations 3–7) must not be delayed, and because the collector should be
> settled before iteration 8 multiplies shards.
## Goals
- **The developer stops deciding which types are garbage collected.** `@gc`
disappears from the language; the compiler infers GC-ness and reports every
decision with its reason.
- Reference counting is replaced by an incremental per-shard tri-color
mark-sweep collector, so the compiler no longer has to emit a balanced
acquire/release at every alias site — the source of every recorded `@gc`
defect.
- Milestone 1's acceptance criterion 3 (ASan-clean across the corpus) closes,
because the leak blocking it is one of the defects this deletes.
## Acceptance Criteria
- What to achieve?
- **Given** a class whose declaration can form a reference cycle, and a
separate class that is only ever shared through a long-lived alias,
- **when** the program is compiled,
- **then** both are classified GC-managed without any annotation, and
`--dump-gc` names the reason for each — a cycle path for the first, the
escaping alias site for the second.
- What to achieve?
- **Given** any `.wo` source containing `@gc`,
- **when** it is compiled,
- **then** it is a diagnostic pointing at inference and `--dump-gc`, not a
silently accepted no-op.
- What to achieve?
- **Given** a program that hides a traced object from the collector —
storing it into an already-blackened object between marking slices and
dropping the original reference,
- **when** the collector completes,
- **then** the object is still alive; and the same fixture fails loudly if
the write barrier is compiled out.
- What to achieve?
- **Given** an abandoned cycle and a cycle still rooted from a live frame,
- **when** collection runs,
- **then** the abandoned one is freed within budgeted slices with no slice
exceeding the configured budget, and the rooted one survives.
- What to achieve?
- **Given** the whole conformance corpus, run repeatedly so several
collection cycles occur,
- **when** it runs under ASan,
- **then** zero leaks and zero errors — the clause that currently fails.
- What to achieve?
- **Given** any emitted `.wob` image,
- **when** it is disassembled,
- **then** no `RC_INC` or `RC_DEC` appears, and the format doc records
opcodes 27–28 as reserved behind a version bump.
## Out Of Scope
- Cross-shard tracing — ownership moves mean no traced object spans shards.
- Generational collection and compaction. Non-moving is load-bearing: no
forwarding pointers, no read barrier. Go's collector is not generational
either.
- Scheduler-integrated pacing beyond the heap-goal trigger; that stays
iteration 8's concern, which is part of why this lands first.
- `ref T` semantics, unchanged — it is an id, not a pointer, and creates no
edge in the inference graph.
## Info
- Governing spec: [`docs/superpowers/specs/2026-08-11-inferred-gc-mark-sweep-design.md`](../../superpowers/specs/2026-08-11-inferred-gc-mark-sweep-design.md).
- **Why the annotation was insufficient, not merely inconvenient:** the OOP
spec's own example, `@gc class PriceCache { entries: map<SKU, Money> }`, is
acyclic. It needs GC because it is shared, and second-class borrows cannot
be stored or returned. So the developer was being asked to reason about type
shape *and* whole-program aliasing at once — hence the hybrid rule
(structural SCC plus reported demand promotion).
- **Why tracing rather than better reference counting:** all four recorded
`@gc` defects are RC bookkeeping failures — `push` missing an increment,
`set` still missing one, the `mut`-`@gc` clobber, and the held-cycle leak.
Inferring GC-ness would widen that population and so widen that bug class.
Tracing emits no per-alias bookkeeping at all.
- **Most of what tracing needs already exists.** The emitter already produces
precise per-pc pointer masks (the drop table's gc mask) and the class table
already carries per-field kinds — the two pieces Go gets from stack maps and
type maps. Go's dependence on OS threads is incidental; the algorithm needs
only per-frame PC→map lookup and the ability to suspend one stack.
- **The one real runtime addition:** the arena cannot enumerate objects — bump
allocation plus size-class free lists, with large objects on bare `malloc`
and no size headers anywhere. Sweep needs its own list. Retiring `rc`, plus
the `borrow` word that traced objects never use, frees exactly eight
contiguous bytes for an intrusive link, so the 16-byte header survives.
- This iteration **supersedes part of iteration 2's memory model** (§4 of the
OOP spec) and closes iteration 4's open gate clause. Neither is renumbered;
both carry pointers here.
## Proposed Solution
- Write the implementation plan from the approved spec, then execute it: the
`gcinfer.ml` pass (Tarjan SCC over the class-reference graph, then demand
promotion to a fixpoint, with a note per decision), the `@gc` removal and
its diagnostic, retiring `RC_INC`/`RC_DEC` and the rc machinery from
`owner.ml`/`emit.ml`, the per-shard traced list and sweep, incremental
tri-color marking with roots read from the existing pc masks, the Yuasa
deletion barrier inside the VM's store paths, and the doc/golden migration
the spec's §8 table enumerates.