Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ RUN apt-get update \

# Full EigenScript source at the pinned tag; build the gfx variant (the
# binary hard-links to src/eigenscript, which finds lib/ next to it).
ARG EIGS_REF=v0.35.0
ARG EIGS_REF=v0.35.1
RUN git clone --depth 1 --branch "${EIGS_REF}" \
https://github.com/InauguralSystems/EigenScript.git /opt/eigenscript \
&& make -C /opt/eigenscript gfx CC=gcc
Expand Down
20 changes: 14 additions & 6 deletions FINDINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,12 +269,20 @@ non-numeric coercion of the same builtin, and its "non-findings" note that
`prev` works "provided `record_history` is never called" is now qualified —
`prev` works, and arms an unbounded table for the whole program while doing it.

**In this repo:** `orbit.eigs`'s `_run` calls the documented `record_history of 0`
for the lifetime of a window session (a lab window asks no temporal questions)
and restores the previous setting on exit. `tests/test_bif_mem.sh` carries a
planted fault that removes that call and must go red, so when #827 lands the
opt-out can come out and the gate still holds. For
`eigenscript-runtime-engineer` / `eigenscript-trace-tape-engineer`.
**FIXED upstream** by EigenScript#829, shipped in **v0.35.1** (the pin this repo
now carries): the history table is bounded by program TEXT — entries no backward
query can reach are pruned at append time — and arming is per NAME instead of
whole-program, so `physics.eigs`'s dead-code `prev of` arms nothing here.

**In this repo:** `orbit.eigs`'s `_run` used to call `record_history of 0` for
the lifetime of a window session and restore it on exit; that opt-out is gone
(dynamics#24). Removing it changed peak RSS by nothing measurable — 134.3 /
134.5 / 134.4 MB with it, 134.7 / 134.3 / 134.4 MB without, at 30 / 100 / 300
frames. The same tree with history explicitly ON measures 304 MB at 30 frames
and 674 MB at 100 on v0.35.0, and 134.5 / 134.9 / 134.8 MB flat on v0.35.1 —
which is why `tests/test_bif_mem.sh`'s planted fault 1 could no longer be
planted and was re-pointed at a per-frame retention fault instead of deleted.
For `eigenscript-runtime-engineer` / `eigenscript-trace-tape-engineer`.

## F-DYN-14 — lib/ui: `chart` allocates a list per plotted point per frame → upstream EigenScript#828

Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,19 @@ is untouched):
by two planted faults. This is not decoration: the first working build of
this view grew **3.9 MB per frame** and died at 859 MB, and every correctness
oracle stayed green the whole time. The cause was a runtime bug, not the
sweep — see FINDINGS F-DYN-13 / EigenScript#827.
sweep — see FINDINGS F-DYN-13 / EigenScript#827, fixed upstream in v0.35.1.
That fix retired one of the planted faults (the bug it recreated no longer
reproduces), so the fault was re-pointed at a per-frame retention leak rather
than dropped: a gate validated by one fewer fault is a gate that has quietly
stopped discriminating.

Forcing-function findings (runtime gaps surfaced while building) are logged in
[FINDINGS.md](FINDINGS.md) — most have graduated to upstream fixes
(#255/#256/#280/#375, and #819/#820 which closed the two lib/ui plot gaps);
a calling-convention edge remains open, and building this rung surfaced two
more: EigenScript#827 (unbounded temporal assignment history — the one that
froze a box) and #828 (chart render allocation).
froze a box, fixed by #829 and shipped in v0.35.1, the pin this repo runs) and
#828 (chart render allocation).

## Develop locally

Expand Down
28 changes: 11 additions & 17 deletions orbit.eigs
Original file line number Diff line number Diff line change
Expand Up @@ -688,23 +688,18 @@ define _build(zeta) as:
# dumps its trajectory, so it keeps every point (cap 0).
# ---- the window entry point ------------------------------------------
#
# WHY THIS TURNS OFF ASSIGNMENT HISTORY. `record_history of 0` is the
# runtime's documented control for per-assignment temporal history — the
# append-only table `prev of x` and `<kw> is x at <line>` read. The
# compiler arms that table for the WHOLE PROGRAM as soon as it sees a
# `prev of` anywhere in the source, including in a function that is
# never called; physics.eigs has exactly one, in the `frame_velocity`
# demo, which this window never invokes. The table is append-only with
# no cap and it holds a REFERENCE to every assigned value, so an
# unbounded loop over it — a 60 fps render — grows without bound: the
# bifurcation view measured 3.9 MB per frame, 859 MB by frame 300, and
# then died on the memory cap. Minimal repro and the upstream issue:
# EigenScript#827 (F-DYN-13). A lab window asks no temporal questions,
# so it opts out for the lifetime of the session and restores the
# previous setting on the way out; the observer predicates the side
# panel shows are live slot reads and are NOT affected.
# This used to call `record_history of 0` for the lifetime of a session,
# because the runtime armed its per-assignment temporal history for the
# WHOLE PROGRAM on sighting one `prev of` anywhere — physics.eigs has
# exactly one, in the never-called `frame_velocity` demo — and the table
# was append-only with no cap, so a 60 fps render grew without bound
# (3.9 MB/frame here; F-DYN-13, EigenScript#827). EigenScript#829, in
# v0.35.1, bounds that table by program TEXT and arms it per NAME, so
# neither half of the problem exists any more and the opt-out is gone.
# Removing it moved peak RSS by nothing measurable (134 MB either way at
# 30 / 100 / 300 frames); tests/test_bif_mem.sh is what keeps that
# honest, on every run.
define _run(zeta, frames, out_path, mode, ticks) as:
local hist_was is record_history of 0
_app.sim is new_sim of zeta
if frames < 0:
_app.sim.cap is HIST_CAP
Expand All @@ -726,7 +721,6 @@ define _run(zeta, frames, out_path, mode, ticks) as:
gfx_open of [WIN_W, WIN_H, "dynamics - orbit lab"]
ui.app_loop of [root, _on_key, _tick]
gfx_close of null
record_history of hist_was
return null

define run_session(zeta, frames, out_path) as:
Expand Down
55 changes: 45 additions & 10 deletions tests/test_bif_mem.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@
# frozen workstation, and it is the reason this gate could be developed
# at all.
#
# The gate is validated with TWO planted faults: one restoring the actual
# leak that was found (the temporal-history opt-out removed), one
# removing the build-once guard. Each must be caught.
# The gate is validated with TWO planted faults: one that retains the
# frame's plotted points (per-frame retention, the class the original
# leak belonged to), one that removes the build-once guard. Each must be
# caught, and each is measured — a fault nobody has watched go red is a
# gate that has stopped discriminating.
#
# Exits 2 (= failure in CI, skip locally) when the runtime has no gfx
# builtins, so it can never be silently dropped.
Expand Down Expand Up @@ -197,17 +199,50 @@ PY_END
echo "$tree"
}

echo "--- planted fault 1: the temporal-history opt-out removed (the real bug) ---"
# physics.eigs contains one `prev of`, which arms the runtime's
# append-only assignment history for the whole program; the chart's
# per-point allocations are then pinned, at ~3.9 MB per frame. The gate
# must reject this on its FIRST rung. EigenScript#827.
T1=$(plant hist " local hist_was is record_history of 0" " local hist_was is record_history of 1")
echo "--- planted fault 1: the bifurcation tick keeps its frame's points ---"
# The headline invariant of this view is that bifurcation mode does NO
# per-frame work (orbit.eigs `_tick`). This fault breaks exactly that:
# every frame rebuilds the plotted point set and KEEPS it — the shape any
# naive per-frame cache (a pick index, a frame history, an undo trail)
# would have, and the same class as the two retention bugs this repo has
# already had: the unbounded `sim.pts` trail and the pinned per-point
# allocations of F-DYN-13. Every structural counter stays CORRECT under
# it — built=1, series=1, points=4000, markers=4 — so only the shape of
# the memory curve can see it, which is the thing this fault has to
# prove. Measured on v0.35.1: 176,384 KB at 30 frames, 279,104 KB at 100
# (~1.46 MB/frame) — it fails BOTH RSS checks, the 200 MB ceiling on the
# second rung and the flatness limit by 102,720 KB against 25,600.
#
# WHAT USED TO BE HERE, and why it went. This leg used to flip
# `record_history of 0` — the opt-out `_run` carried against the
# runtime's unbounded temporal history (EigenScript#827) — back on, and
# require the resulting 3.9 MB/frame build to be rejected. EigenScript#829
# (v0.35.1) bounds that table by program TEXT and arms it per NAME, so
# turning history on grows nothing any more: measured, not assumed, the
# flipped build ran flat at ~134 MB and SAILED THROUGH this gate. The
# opt-out is gone from `_run` and a fault that can no longer be planted
# validates nothing — so it is re-pointed, not deleted (dynamics#24).
T1=$(plant framepts " if _app.mode == \"bif\":
if _app.hook != null:
_app.hook of _app.sim
return null" " if _app.mode == \"bif\":
if _app.frame_pts == null:
_app.frame_pts is []
local fs is _app.chart.series[0]
local pts is []
local i is 0
loop while i < (len of fs.data):
append of [pts, [fs.x[i], fs.data[i]]]
i is i + 1
append of [_app.frame_pts, pts]
if _app.hook != null:
_app.hook of _app.sim
return null")
if run_gate "$T1" quiet "static" "30 100"; then
echo "FAIL: the leaking build passed the memory gate — the gate can't discriminate"
exit 1
fi
echo "PASS: planted fault 'hist' is caught (restores the 3.9 MB/frame growth)"
echo "PASS: planted fault 'framepts' is caught (per-frame retention, every structural counter still correct)"

echo "--- planted fault 2: the build-once sweep guard removed ---"
T2=$(plant guard " if _app.sweep_done == 1:
Expand Down