Skip to content

Lever C: stream/evict resolved-module envs (representation-minimization plan item 2) — design-first - #5886

Closed
briansrls wants to merge 7 commits into
mainfrom
session/zesty-crab-811
Closed

briansrls wants to merge 7 commits into
mainfrom
session/zesty-crab-811

Conversation

@briansrls

@briansrls briansrls commented Jun 28, 2026 •

Copy link
Copy Markdown
Contributor

Lever C — evict resolved-module envs the interpreter never reads

Representation-minimization plan (docs/plans/representation-minimization.md,
#5880) item 2. Attacks the sustained floor RSS: the resolved graph is held
resident for the whole long claim-execution phase, and most of what it holds is dead
weight to the interpreter.

Finding (by code read, then by execution)

Whole-tree resolve produces ResolvedGraph { modules: Vec<TypedModule> }. Each
TypedModule carries a heavy type_env: Rc<TypeEnv> (per-symbol bindings of
resolved type Nodes + provenance) and func_env: Rc<ResolvedFuncEnv> (signature
map). InterpContext::with_runtime_options then builds fn_nodes / service_ops /
the global item_registry up front and stores modules: graph.modules.clone().

After construction the interpreter never reads type_env or func_env — the
only eval-time reads of the stored modules (v1_interpreter.rs:2686,
coproduct_reflection.rs:69/466/767, wire_value_serialize.rs:130) touch only
module / items / per-module item_registry. Zero .type_env / .func_env
reads exist in the interpreter or its external module readers. So those payloads are
a representation the consumer never consults (DESIGN §2).

Change

evict_module_envs (new) projects each TypedModule into a fresh one that keeps
module / items / item_registry (same Rcs — cheap refcount bumps) but points
type_env / func_env at shared empty singletons (empty_intern_table() already
exists). with_runtime_options builds the stored modules through it. When the
caller drops its Rc<ResolvedGraph> (the floor path whole_tree_resolved_ctx drops
graph at return), the real env payloads become reclaimable.

  • Behaviour-identical: the only mutated fields are provably never read.
  • No public-type change; external module readers untouched.
  • Universally safe: callers that keep the graph alive (e.g. emit, which does need
    the envs) just pay ~600 small struct allocs (no win, no harm); the win materializes
    on the floor path where the graph is dropped.

Magnitude (executed, live 462-module dsl corpus)

A one-off probe serializing the resolved modules before vs after eviction:

modules=462  full_repr_bytes=5,723,421,964  retained_repr_bytes=391,710,819
evicted_bytes=5,331,711,145  evicted_pct=93.2

93.2% of the serialized resolved-module representation is the evicted
type_env/func_env.
Caveat on reading this number: serialized size overstates
the in-memory footprint because serde_json expands Rc-shared structure (the shared
InternTable from #5867, and any resolved Nodes shared with items, are counted once
per reference). It is therefore a relative representation-size metric, not an RSS
measurement — the in-memory reclamation is a fraction of 5.33 GB. RSS itself is not a
clean A/B target here: eviction does not lower the resolve peak (all envs are still
built before projection), and glibc does not return freed heap to the OS on a
short run; the benefit is steady-state heap reuse during the long claim phase.

Scope boundary (honest)

This is the evict half of Lever C — it removes the never-read envs from the
sustained interpreter working set. It does not cut the transient typecheck peak
(the deeper "resolve in dependency batches, evict each env once its dependents are
typechecked" restructures the load-bearing typecheck pipeline). Since v1 is going
away, that deeper batch/stream is documented as the v2-root follow-on rather than
built into the doomed seed. The emit path (needs all envs) is untouched and is not
the floor-OOM driver.

Design note: scratchpad/lever-c-design.md (worktree-local, not committed).

Brian Searls and others added 5 commits June 28, 2026 01:07
Project each TypedModule into a fresh one keeping module/items/item_registry
but pointing type_env/func_env at shared empty singletons, so the floor path
releases the never-read envs once the source ResolvedGraph drops. Remove probe
scaffold; untrack scratchpad.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review June 28, 2026 01:54
@gunbai-bot
gunbai-bot Bot marked this pull request as draft June 28, 2026 01:57
Brian Searls and others added 2 commits June 28, 2026 02:09
…ept, only envs evicted)

The teeth perturbation (items emptied) was auto-committed/pushed; it served its
purpose as the discriminating control (rust_tests RED with items evicted vs GREEN
with only type_env/func_env evicted), proving the reflection path consumes
items/item_registry. Restoring the intended change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Dropping per design review (manager stern-moth-225): this is bulk env-eviction at InterpContext construction = SUSTAINED-RSS only; it does not move the per-shard resolve/infer PEAK, which is the actual floor OOM axis (#5877 cgroup-bound concurrency). The verified §2 finding (interpreter never reads type_env/func_env; teeth-proven) and the root design (frontier-bounded retention over the existing v2 ResolvedTree/InferredTree split) are captured in the representation-minimization plan. Whether to implement peak-reducing frontier-evict in the doomed v1 seed (A1) vs record it as a design note until v2 infer is the live path (A3) is being decided by a per-shard-peak-vs-cap measurement after the cheaper fixes (#5884 + alloc-clamp). — sent from zesty-crab-811

@gunbai-bot gunbai-bot Bot closed this Jun 28, 2026
gunbai-bot Bot pushed a commit that referenced this pull request Jun 28, 2026
Drop the abandoned v1_interpreter.rs bulk-eviction (PR #5886 closed: sustained-RSS
only, did not move the per-shard resolve peak). Record the Lever C design outcome in
the plan .dag authority + regenerate the .md projection (main_wet):
- §2 finding (floor consumes the resolved Node tree, not type_env/func_env;
  teeth-proven) and extend-not-fork (aligns to v2 ResolvedTree/InferredTree, no new
  type);
- the v2-surviving root is a frontier-bounded RETENTION POLICY over InferredTree.facts
  (OWED at v2 00_compile, which holds it whole today);
- A3-default conditional with an explicit flip-to-A1 trigger (implement v1
  frontier-evict only if a residual per-shard peak survives over cap after the
  cheaper fixes — alloc-clamp #5877 landed + Rc-shared sigs #5884 pending).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Jun 28, 2026
…und; A3-default + flip trigger) (#5892)

* WIP: Lever C: stream/evict resolved-module envs (representation-minimization

* WIP: Lever C: stream/evict resolved-module envs (representation-minimization

* Lever C: evict resolved-module envs the interpreter never reads

Project each TypedModule into a fresh one keeping module/items/item_registry
but pointing type_env/func_env at shared empty singletons, so the floor path
releases the never-read envs once the source ResolvedGraph drops. Remove probe
scaffold; untrack scratchpad.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* WIP: Lever C: stream/evict resolved-module envs (representation-minimization

* WIP: Lever C: stream/evict resolved-module envs (representation-minimization

* Restore clean #5886 (revert teeth perturbation; items/item_registry kept, only envs evicted)

The teeth perturbation (items emptied) was auto-committed/pushed; it served its
purpose as the discriminating control (rust_tests RED with items evicted vs GREEN
with only type_env/func_env evicted), proving the reflection path consumes
items/item_registry. Restoring the intended change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* WIP: Lever C: stream/evict resolved-module envs (representation-minimization

* Lever C (item 2): record design-note disposition in the plan authority

Drop the abandoned v1_interpreter.rs bulk-eviction (PR #5886 closed: sustained-RSS
only, did not move the per-shard resolve peak). Record the Lever C design outcome in
the plan .dag authority + regenerate the .md projection (main_wet):
- §2 finding (floor consumes the resolved Node tree, not type_env/func_env;
  teeth-proven) and extend-not-fork (aligns to v2 ResolvedTree/InferredTree, no new
  type);
- the v2-surviving root is a frontier-bounded RETENTION POLICY over InferredTree.facts
  (OWED at v2 00_compile, which holds it whole today);
- A3-default conditional with an explicit flip-to-A1 trigger (implement v1
  frontier-evict only if a residual per-shard peak survives over cap after the
  cheaper fixes — alloc-clamp #5877 landed + Rc-shared sigs #5884 pending).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Lever C: record RESOLVED A3 verdict in the plan note (measured, not data-gated)

Official call (manager stern-moth-225): A3, robust. Post-#5873-cutover floor
measurement (claim_executor per-shard VmHWM × spawn_width=2, sccache excluded):
co-resident 5.98 GiB, 1.82 GiB UNDER the ~7.8 GiB safety threshold below the 8 GiB
host cap, +0.7% vs the 5.94 GiB pre-cutover baseline (noise). The cheaper fixes
already hold the floor under cap; frontier-evict is NOT written into the doomed v1
seed — the ~4 GiB prize is captured durably in v2 streaming-infer. Flip-to-A1
trigger kept documented but marked UN-tripped (re-auditable if the corpus grows).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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