lang: xx <lvalue> borrows the operand's storage instead of heap-copying
`xx <struct-typed local>` used to heap-copy the value through context.allocator.
The protocol value's `ctx` pointed at the heap copy; the original local was
left behind, untouched. Mutations through the protocol never reached the
original, and direct reads of the original never saw protocol mutations.
Two-fork bug, silent, easy to write by mistake.
New rule (Option 3 in the discussion):
- `xx <lvalue>` — identifier, field access, index expression, deref —
borrows the operand's storage. No heap copy, no `free` needed.
- `xx <rvalue>` — struct literal, function-call result, arithmetic, etc. —
heap-copies through context.allocator. Unchanged from today.
- `xx @ptr` and `xx <pointer-typed value>` — borrows the pointee. Unchanged.
Single switch in `buildProtocolErasure` ([lower.zig:10334](src/ir/lower.zig#L10334))
gated by a new `isLvalueExpr` helper ([lower.zig:10322](src/ir/lower.zig#L10322)).
Struct-typed operand: if the AST shape is identifier/field/index/deref,
emit `lowerExprAsPtr(operand_node)` and skip the heap-copy; otherwise
keep the alloca-store-heap_copy path.
specs.md §3 ownership table extended to three rows (rvalue, lvalue,
pointer) with examples and rationale per row.
Regressions:
- `examples/130-xx-value-routes-through-context-allocator.sx` — the
Phase 1.1 witness for heap-copy-via-context-allocator. Previous shape
(`xx <local-value>`) is now a borrow under Option 3 and no longer
exercises the heap-copy path. Rewritten to use a struct literal
(`xx ByValue.{...}`) which still heap-copies through context.allocator
— Tracer.count = 1 as before.
- `examples/135-xx-lvalue-borrows.sx` — new test. Dereferences a
TrackingAllocator into a stack value, does `xx tracker` inside a
push Context, and asserts alloc_count/dealloc_count on the LOCAL go
up. Under old semantics this would have stayed at 0 (heap copy got
the increments, local stayed stale).
157/157 example tests pass; chess clean on macOS / iOS sim / Android
(`tools/verify-step.sh` ran green immediately before this work).
This commit is contained in:
@@ -5,6 +5,28 @@ Tracking checkpoint for the mem.sx Zig-aligned implementation
|
||||
|
||||
## Last completed step
|
||||
|
||||
- **`xx <lvalue>` borrows the operand's storage** (Option 3 in the
|
||||
protocol-erasure design discussion). Today's behavior — `xx
|
||||
<struct-typed local>` heap-copies the value — was a silent footgun:
|
||||
the protocol value pointed at the heap copy, the original local
|
||||
stayed stale, mutations through the protocol weren't visible to the
|
||||
original (and vice versa). Under the new rule, when the operand
|
||||
names existing storage (identifier, field access, index expression,
|
||||
dereferenced pointer), `xx` takes its address and the protocol
|
||||
borrows. Heap-copy is reserved for `xx <rvalue>` — struct literals,
|
||||
function-call results, arithmetic expressions, anything without its
|
||||
own storage.
|
||||
|
||||
Single point of change at `buildProtocolErasure` in `lower.zig:10334`,
|
||||
via a new `isLvalueExpr` helper at `lower.zig:10322`. specs.md §3
|
||||
ownership table updated. The `examples/130-...` regression that
|
||||
previously tested heap-copy on `xx <local>` now tests `xx
|
||||
<struct-literal>` (still the heap-copy path); new regression
|
||||
`examples/135-xx-lvalue-borrows.sx` witnesses the borrow path via
|
||||
TrackingAllocator. 157/157 example tests + chess clean across all
|
||||
three platforms (`tools/verify-step.sh` gate ran green right
|
||||
before this work landed).
|
||||
|
||||
- **Phase 1.4 — `valueToLLVMConst` upgraded to handle every interp
|
||||
`Value` variant.** The serializer at `emit_llvm.zig:734` used to
|
||||
collapse anything past int/float/boolean into `LLVMConstNull(ty)` —
|
||||
@@ -220,7 +242,19 @@ Allocator value naturally.
|
||||
|
||||
## Log
|
||||
|
||||
- **2026-05-25 (latest)** — Phase 1.4 shipped. `valueToLLVMConst`
|
||||
- **2026-05-25 (latest)** — `xx <lvalue>` semantics changed to borrow.
|
||||
Single change at `lower.zig:10334` (`buildProtocolErasure`) gated by
|
||||
new `isLvalueExpr` helper at `lower.zig:10322`. specs.md §3
|
||||
ownership table extended (three modes: rvalue / lvalue / pointer).
|
||||
`examples/130-xx-value-routes-through-context-allocator.sx` updated
|
||||
to use a struct literal (rvalue) as the operand — the heap-copy
|
||||
routing through `context.allocator` is what Phase 1.1 actually
|
||||
proves, and that path is still active for rvalues. New regression
|
||||
at `examples/135-xx-lvalue-borrows.sx` witnesses the borrow path
|
||||
via TrackingAllocator counts on the local. 157/157 + chess green
|
||||
on all three platforms (`tools/verify-step.sh` ran green
|
||||
immediately before this).
|
||||
- **2026-05-25 (penultimate)** — Phase 1.4 shipped. `valueToLLVMConst`
|
||||
(`emit_llvm.zig:734`) replaced the primitive-only switch with a
|
||||
full serializer covering null_val, void_val, undef, func_ref,
|
||||
string, and aggregate (struct + array via
|
||||
|
||||
Reference in New Issue
Block a user