Skip to content

Phase 1 / Etch / Non-POD resource fields (string, enum) exposed to the interpreter#29

Merged
guysenpai merged 13 commits into
mainfrom
phase-1/etch/resource-nonpod-fields
Jun 24, 2026
Merged

Phase 1 / Etch / Non-POD resource fields (string, enum) exposed to the interpreter#29
guysenpai merged 13 commits into
mainfrom
phase-1/etch/resource-nonpod-fields

Conversation

@guysenpai

Copy link
Copy Markdown
Contributor

Milestone M1.0.3 — Non-POD resource fields (string, enum) exposed to the Etch interpreter. The Option A alignment (formerly the deferred "tranche 7"): resource fields reach spec parity for the two scalar non-POD cases.

Brief (source of truth, Status: CLOSED): briefs/M1.0.3-resource-nonpod-fields.md

Scope delivered (staged E1 → E2 → E3, each STOP-reviewed via Claude.ai)

  • E1 — Phase-1 persistent heap (src/etch/persistent.zig, Tier-0-agnostic): refcounted system-allocator heap, 8-byte {refcount, type_id} header, type_id→drop dispatch, immortal sentinel. The keystone M1.0.4 collections reuse unchanged.
  • E2 — string resource fields: FieldKind.string_ (resource-only), borrowed-view reads (no incref), write = rule-arena→persistent promotion (read-old → alloc+copy → write-slot → decref-old), immortal-interned defaults (zero alloc at addResource), teardown decref discipline in the Etch runtime (Tier-0 stays string-agnostic).
  • E3 — enum resource fields: FieldKind.enum_ (POD u32 discriminant = declaration-order index), FieldDesc.enum_type_name_id (interned StringId) so the bridge rebuilds a typed enum_value, .variant default / first-variant-when-absent, validator unlock.

The flagship GameState program (string + enum + int) type-checks with zero diagnostics and mutates end-to-end through the interpreter harness, leak-free.

Validation (Étape 4 — all green)

  • All "Scope" deliverables present (E1 heap, E2 string, E3 enum)
  • No drift into "Out-of-scope" (collections → M1.0.4; codegen unchanged; components POD-strict)
  • All "Acceptance criteria › Tests" pass in debug AND ReleaseSafe (7 M1.0.3 named tests + full suite 791/808, 17 skipped, 0 failed)
  • Benchmarks: none (per brief — founds the heap + wires the dev/interpreter path)
  • Observable behaviour demonstrable (flagship GameState end-to-end)
  • zig build, zig build test, zig fmt --check, zig build lint green; commit-msg hook green on every commit
  • Brief "Closing notes" filled (4 rubriques + measures + residual debt)
  • Status: CLOSED, close date set
  • Final commit docs(brief): close M1.0.3

registry.zig (FROZEN, C0.5/M0.9) edited additively per the brief's "Files to create or modify"; resources.zig (FROZEN) kept string-agnostic.

Closing notes

What worked

  • E1 persistent heap as a clean keystonestd-only, Tier-0-agnostic; E2 reused it unchanged (StringSlot + literal registry slotted straight in). The drop dispatch / open TypeId set are what M1.0.4 collections register against.
  • Resource-only by construction, validator as the guaranteestring_/enum_ live in the shared registry path but are emitted only for .resource and rejected on component; the guard test pins all four cases.
  • Write-promote vs POD-write split — strings route around the byte-encoder (need allocator + old slot); enums are self-contained discriminants through the generic path.
  • Staged E1→E2→E3 with STOP-reviews settled the two subtle design points (header bookkeeping; enum type-name representation) without re-scope.

What deviated (tracked in § Accepted deviations)

  • E3FieldDesc carries the enum type name as the interned StringId (enum_type_name_id: u32), not a string slice (refines review guard 2, confirmed): enum_value.type_name IS a StringId, and a borrowed AST slice would dangle post-AST-free while the registry persists.
  • Impl detail (no contract change) — the persistent block carries a hidden leading size: usize before the spec's 8-byte header so Allocator.free recovers the length; the spec-visible header is preserved exactly (comptime offset asserts).

What to flag in review

  • registry.zig/resources.zig are FROZEN; the registry edit is additive + brief-listed, resources.zig untouched.
  • tests/etch_interp/diff_runner.zig (out-of-list) got .string_/.enum_ => unreachable arms — mechanical exhaustiveness, POD-only corpus, no behaviour change.
  • No new diagnostic codes (removes rejections / flips guard assertions — the intended resolution).
  • Codegen unchanged (non-POD resource fields are interpreter-only per § Out-of-scope).

Final measures

  • No benchmarks (per brief). Tests: 3 E1 + 4 E2 + 3 E3 (incl. the combined flagship). zig build test exit 0 debug + ReleaseSafe (791/808, 17 skipped, 0 failed); leak-freedom under std.testing.allocator (+ CountingAllocator); no new watchdog site.

Residual debt (intentional)

  • Borrowed-read aliasing (E2) — borrowed reads with no incref (Phase-1 choice; scope-bound incref = Phase 2). Hazardous pattern not triggered by any test/flagship; closed additively by Phase-2 incref-on-read.
  • Persistent-string + enum-id ownership tied to the Interpreter (E2/E3)persistent_literals + slot-decref in deinit; a StringId from one AST in the world's registry. Correct for the single-program path; a future script hot-reload (world reused across an interpreter swap, stub src/runtime/main.zig:297, out of scope) must move ownership to world/resource lifetime — additive at reload wire-up (the enum-id case is a logic bug under multi-AST reload, not a UAF — strictly safer than a slice).
  • M1.0.4 — dynamic collections on resource fields (string[], [K: V], Set<T>) — deferred; reuse E1's persistent heap + E2's FieldKind/slot/teardown machinery unchanged.

Merge + tag (v0.10.3-resource-nonpod-fields) are left to Guy after PR review.

@guysenpai guysenpai merged commit 088a4aa into main Jun 24, 2026
8 checks passed
@guysenpai guysenpai deleted the phase-1/etch/resource-nonpod-fields branch June 24, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant