Skip to content

Add @compact primitive and free-list memory recycling#40

Open
gavlooth wants to merge 5 commits intoHigherOrderCO:mainfrom
gavlooth:compact-primitive
Open

Add @compact primitive and free-list memory recycling#40
gavlooth wants to merge 5 commits intoHigherOrderCO:mainfrom
gavlooth:compact-primitive

Conversation

@gavlooth
Copy link

Summary

  • Adds @compact(term) primitive: normalizes term to SNF, then deep-copies the result tree to fresh heap positions. Safe to call from any evaluation context because it never modifies existing heap data. Enables iterative algorithms (e.g. Bellman-Ford with early termination) to shed accumulated intermediates between rounds.
  • Adds per-thread, size-segregated free lists (sizes 1-16) with heap_free calls in all interaction handlers (APP-LAM, DUP-NOD, DUP-SUP, MAT-CTR, MAT-NUM, OP2, EQL, AND, OR, etc.). Disabled in multi-threaded mode to avoid cross-thread races.
  • Adds OP2-NUM fast path: skips frame push/pop when both operands are already NUM.
  • Splits dup_nod allocation into 3 separate allocs for better free-list reuse.

Motivation

HVM4 programs with iterative loops (e.g. @repeat_until calling Bellman-Ford rounds) accumulate heap garbage from evaluation intermediates. Without reclamation, heap exhausts after a few rounds. The free list recycles interaction-freed blocks immediately, and @compact provides a periodic deep-copy escape hatch for long-running computations.

Usage

// In a loop body — compact state between rounds to shed dead heap
@check_go = λ{
  0: λn. λf. λdist. #S{dist, 0};
  λm. λn. λf. λdist. @repeat_until_go(n - 1, f, %compact(#S{dist, 1}))
}

Test plan

  • All 196 existing HVM4 tests pass (8 FFI tests fail pre-existing on Linux due to -dynamiclib)
  • Compact works with linear use, DUP of compacted terms, sibling pairs, nested constructors
  • Bellman-Ford with compact + early termination produces correct shortest paths ([0,3,2,6,7])
  • Repeat loops with compact (5+ rounds) produce correct results
  • Free list recycling reduces heap usage across all interaction types

🤖 Generated with Claude Code

Introduces two features that enable iterative HVM4 programs (like
Bellman-Ford with early termination) to run without exhausting heap:

1. @compact(term): Normalizes term to SNF, then deep-copies the result
   tree to fresh heap positions. Safe to call from any evaluation context
   (inside WNF, nested in expressions) because it never modifies existing
   heap data. Enables iterative algorithms to shed accumulated evaluation
   intermediates between rounds.

2. Per-thread, size-segregated free lists (sizes 1-16): Freed heap blocks
   are recycled via LIFO free lists, checked first by heap_alloc before
   bump-allocating. heap_free calls added to all interaction handlers
   (APP-LAM, DUP-NOD, DUP-SUP, MAT-CTR, MAT-NUM, OP2, EQL, etc.).
   Disabled in multi-threaded mode to avoid cross-thread races.

Also includes OP2-NUM fast path (skip frame push when both operands are
already NUM) and dup_nod split allocation (3 separate allocs instead of
one block, enabling free-list reuse of smaller sizes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Analyze BOOK terms after parsing to compute program hints (node count,
SUP/DUP presence, max depth, arity stats). Use hints to right-size all
runtime buffers instead of hardcoded compile-time capacities — reducing
memory footprint from GBs to KBs for small programs while still
handling large ones via dynamic growth.

Key changes:
- analyze/hints.c: O(N) post-parse analysis producing HvmHints
- data/wsq.c: resizable Chase-Lev deque (WsqArray indirection, 2x growth)
- data/wspq.c: dynamic bracket count and per-bucket capacity
- data/uset.c: sized initialization from hints instead of HEAP_CAP
- data/elastic_ring.c: ouroboros double-map ring buffer with elastic
  growth/shrink via memfd_create (Linux) / shm_open (macOS)
- eval/normalize.c, eval/collapse.c, cnf/_.c: hint-based buffer sizing
- main.c: -v flag for hints output, --test-ring for ring self-test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Standalone data structure for future Hybrid SIV + Ring work queues.
Dense swap-compacted array with stable IDs, separate data/ID capacity
growth, and O(1) push/erase/valid/get operations. Self-test via
--test-siv flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Leverage fd data persistence across ftruncate+remap: on growth, existing
data at fd offsets [0, old_cap) survives the extend. Only the wrapped
prefix (if any) needs a memcpy within the mapping. On shrink, compact
live data to offset 0 with a single memmove before truncating.

Removes the malloc/free allocation overhead from the resize hot path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
elastic_ring.c:
- Update header comment to reflect zero-copy growth (was stale)
- Add thread-safety notes for future MT integration
- Document byte-level alignment contract
- Enforce cap/4 threshold in ring_shrink (was documented but not checked)
- Add tests: shrink with wrapped data, cap/4 threshold rejection,
  grow when empty, direct double-map mirror verification

siv.c:
- Document u32 ID space exhaustion (4B push limit)
- Add thread-safety contract for SIV+Ring thief pattern
  (release/acquire barriers, erase-during-read race)
- Add tests: garbage ID rejection (SIV_INVALID, near-max, beyond
  next_id, erased), independent id_cap vs data_cap growth across
  3 push-erase rounds, erase-last-element (no-swap path)

Co-Authored-By: Claude Opus 4.6 <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.

2 participants