escape (fix): the arena took ludic.ui's pooled nodes - a generic's call and an unknown callee now keep what they are handed

memory_final's gate crashed in all 13 scenarios at the first frame of play, R3D_ARENA_CHECK=1 reading
0xDD in ludic.ui's nd_take: a node the pool keeps had come from the frame's scratch. Two holes:

- a call to a generic (ui_kept(list, n)) names the generic, and the analysis knows only its instances
  (ui_kept$UiNode), so the callee looked unknown - and an unknown callee was taken to keep nothing.
  A generic's call now reaches every instance, and an unknown callee keeps everything it is handed,
  but for a short list of intrinsics known to keep nothing; view() shares its list's storage.
- a push's growth into a parameter's list was LOCAL whenever the list was not seen kept, though a
  parameter may be a state's list. A site is LOCAL now only when its class is neither ESC nor HEAP.

examples/lang/arena_pool.ludic is the shape (a pool keeping records across frames through a generic
push): built with --arena it prints '7 3498' poisoned on every reset and with the arena off, in
ludic-dev test. On the valley every ludic.ui pool site is kept; 5480 sites local, 6431 kept.

Also, from ECS: a record's field defaults are stored into it when it is made, a global's initializer
is kept, and a component's own functions are frame roots (they run while its page is open).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 17:13:05 +03:00
parent a6364199da
commit c8a588b2de
6 changed files with 62210 additions and 59807 deletions

View file

@ -0,0 +1,40 @@
# arena_pool.ludic - a pool that keeps its records across frames through a generic push (the shape of
# ludic.ui's nd_take and ui_kept). Built with --arena, a record the pool keeps must not come from the
# frame's scratch: run with R3D_ARENA_CHECK=1 (each reset filled with 0xDD) it prints what it prints
# with the arena off (R3D_ARENA=0) - `7 3498`.
program ArenaPool {
property Cell { v: int = 0, tag: string = "" }
property Pool { cells: []Cell = new []Cell, used: int = 0 }
function kept<T>(xs: []T, v: T) -> void { push(xs, v) }
function take(p: Pool, f: int) -> Cell {
if p.used < len(p.cells) {
let c = p.cells[p.used]
p.used += 1
return c
}
let c = new Cell { v: f }
kept(p.cells, c)
p.used += 1
return c
}
function step(p: Pool, f: int) -> int {
p.used = 0
var s = 0
for k in 0 .. (f % 8) {
let c = take(p, f)
c.v = f + k
s += c.v
}
return s
}
entry {
let p = new Pool
for f in 0 .. 500 {
step(p, f)
Mem.frame()
}
var sum = 0
for i in 0 .. len(p.cells) { sum += p.cells[i].v }
print(`{len(p.cells)} {sum}`)
}
}