Skip to content

fix: order-independent heap classification (#1311), determinism gate (#1299), README positioning (#475), dead runtime surface - #1330

Merged
nicolas-maman merged 2 commits into
mainfrom
fix/readme-475-1299-1311
Jul 29, 2026
Merged

fix: order-independent heap classification (#1311), determinism gate (#1299), README positioning (#475), dead runtime surface#1330
nicolas-maman merged 2 commits into
mainfrom
fix/readme-475-1299-1311

Conversation

@nicolas-maman

@nicolas-maman nicolas-maman commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Closes #1311
Closes #1299
Closes #475

Heap tracker: tracked-empty error string leaked when dropped outside main (#1311)

The return-heap classifiers took bare-identifier evidence from gen->heap_string_vars, the tracker table of whichever function happened to be emitting when a callee's memo was first computed. User helpers emit before merged std module functions, so oid, oerr = asn1.read_oid(r) inside a helper classified read_oid's error slot against the helper's (wrong) tracker table, memoised non-heap, and the chain minted heap empties nobody freed: 1 byte per asn1.read_* call, unreclaimable by the caller. The same three lines inline in main were clean because main emits last, after the std chain had classified itself correctly.

Identifier evidence now resolves structurally against the analysed function's own body (declaration-from-heap, or the new destructure-from-heap-position resolution), in both the tuple and single-value walkers. Classification is order-independent; the asn1 error chain settles on the non-allocating literal fixed point (no wrap, no free, no malloc at all on the success path). Verified with the issue's exact repro under macOS leaks: 1 leak before, 0 after, in both the helper and inline variants. Regression probe (RSS-bounded, 50000 nested tuple-err drops): tests/integration/heap_tracker_nested_tuple_err_no_leak/. All 8 pre-existing no-leak probes still pass.

Emitted-C determinism: gate + documented guarantee (#1299)

  • docs/architecture.md now states the guarantee (same source + same compiler build produces byte-identical C), the two invariants that make it true (source-order emission, no volatile metadata), and the scope boundary (per compiler build; downstream C compiler out of scope).
  • tests/integration/emit_c_determinism/ compiles a 7-program corpus (actors, std imports, string interpolation, collections) twice, byte-compares, and rejects __DATE__/__TIME__/__TIMESTAMP__ outright.
  • Audit re-verified on the current tree: no hash-order iteration feeds codegen, the version placeholder stays static.

README positioning (#475)

The old lede read "another compiled systems language" (the exact failure mode the issue quotes from the Wasp retrospective) and the body duplicated itself: string-ownership tracking appeared twice nearly verbatim, sandbox twice, actors twice, across ~110 template-cadence bullets.

  • New lede: Erlang-style actors, Rust-grade capability discipline, and Go-flavored ergonomics, compiled to readable C.
  • Overview leads with the capability sandbox (the differentiator most visibly absent from competitors), then actors; compiles-to-C is framed as an implementation choice, not the pitch.
  • Core Features is seven one-line pillars with links, matching the one-liner-with-link format of the Documentation section.
  • The duplicated Runtime Features tree, Optimization Tiers wall, and Runtime Configuration C section are deleted with a no-information-loss pass: every removed detail was verified present in (or added to) its doc. Tier structure moved to docs/runtime-optimizations.md, embed flags already in docs/c-embedding.md, sendfile/static files newly documented in docs/http-server.md.
  • GitHub repo description updated to match the new positioning.
  • 629 lines to 526.

Dead surface removed (found while verifying README claims)

Verifying the README's feature claims against the runtime surfaced three false claims, one of them hiding real memory bugs:

  • Actor pooling never existed. Per-core ActorPools were allocated and initialized, but actor_pool_acquire had zero call sites. Worse, the release path cast every ActorBase* to PooledActor (not layout-compatible; the read lands inside the step function pointer) and, when the overlaid bytes landed in 0..63, routed NUMA-allocated memory to plain free(): mmap-backed under libnuma, VirtualAlloc-backed on Windows NUMA. Removed both pool headers (one was a duplicate included nowhere), the dead AETHER_ACTOR_POOL_SIZE env knob and profile constants, the never-incremented actors_pooled counter, the false Actor Pooling [ON] config print, and the isolated unit tests of the unused structure. scheduler_spawn_pooled / scheduler_release_pooled renamed to scheduler_spawn_actor / scheduler_release_actor (nothing pools); all callers, benchmarks, and docs updated.
  • Actor destroy under-freed on NUMA builds. Spawn allocates the full derived-struct size but release freed with sizeof(ActorBase); numa_free unmaps exactly the given range, so every destroy leaked the derived tail under libnuma. The allocation size is now stored on the actor and used at release.
  • Message tracing did not exist. runtime/utils/aether_tracing.c compiled into every build and generated C included its header, but nothing ever called it; the README advertised it. Removed the TU, the #include emission, and the manifest rows (the snprintf argument lists rebalanced; the mismatch was caught by the MinGW -Werror=format-extra-args cross-check). Also removed six orphaned generated-C snapshots under tests/integration/ that no build referenced, plus scheduler_enable_features (zero callers; its only live effect duplicated aether_enable_opt).

Verification

The two removed concepts are legitimate features with fatally flawed sketches; their proper designs now have demand-gated issues: #1332 (actor pooling, benchmark-first) and #1333 (message tracing, zero-cost-when-off).

…positioning, dead runtime surface

Closes #1311. The return-heap classifiers took bare-identifier evidence
from gen->heap_string_vars, the tracker table of whichever function
happened to be emitting when a callee's memo was first computed. User
helpers emit before merged std module functions, so a std (value, error)
tuple destructured inside a helper classified its error slot with the
wrong function's table, memoised non-heap, and leaked one allocation per
call (1 byte per asn1 read_*). Identifier evidence now resolves
structurally against the analysed function's own body
(declaration-from-heap or destructure-from-heap-position, the new
body_tuple_destructure_binds_heap), classification is order-independent,
and the asn1 error chain settles on the non-allocating literal path.
Regression probe: tests/integration/heap_tracker_nested_tuple_err_no_leak.

Closes #1299. Emitted-C determinism is now a stated guarantee
(docs/architecture.md: invariants + scope boundary) and a CI gate
(tests/integration/emit_c_determinism compiles a 7-program corpus twice,
byte-compares, rejects timestamp macros). Audit re-verified: no
hash-order iteration feeds codegen, no volatile metadata reaches
emitted text.

Closes #475. README repositioned: leads with the capability sandbox,
config-IS-code, and polyglot hosting instead of "another compiled
language"; Core Features is seven one-line pillars with links; the
duplicated Runtime Features / Optimization Tiers walls are gone with
every removed detail homed in its doc (tiers in runtime-optimizations,
embed flags in c-embedding, sendfile + static files newly documented in
http-server.md). GitHub repo description updated to match.

Dead-surface removal found while verifying README claims:

- Actor pooling never existed: per-core ActorPools were allocated and
  initialized but actor_pool_acquire had zero call sites. The release
  path cast every ActorBase* to PooledActor (not layout-compatible) and,
  when the overlaid bytes landed in 0..63, routed NUMA-allocated memory
  to plain free(). Removed both pool headers (one a never-included
  duplicate), the dead AETHER_ACTOR_POOL_SIZE knob, profile constants,
  actors_pooled counter, false "Actor Pooling [ON]" print, and the unit
  tests of the unused structure. scheduler_spawn_pooled /
  scheduler_release_pooled renamed scheduler_spawn_actor /
  scheduler_release_actor (nothing pools); callers and docs updated.
- Actor destroy freed with sizeof(ActorBase) while spawn allocated the
  full derived-struct size; numa_free unmaps exactly the given range, so
  every destroy leaked the derived tail under libnuma. The allocation
  size is now stored on ActorBase and used at release.
- aether_tracing.c compiled into every build, its header was emitted
  into all generated C, and the README advertised message tracing, but
  nothing ever called it. Removed the TU, the include emission, manifest
  rows (the snprintf arg lists rebalanced; caught by the MinGW
  -Werror=format-extra-args gate), and six orphaned generated-C
  snapshots under tests/integration that nothing compiled.
- scheduler_enable_features: zero callers, only live effect duplicated
  aether_enable_opt(AETHER_OPT_LOCKFREE_MAILBOX).

Verified: make test 229/229, make examples 88/88, test-ae (sandbox-hung
abort()/network tests excluded, plain abort() proven to hang in this
sandbox), emit_c_determinism, all 8 no-leak regression probes, -Werror
spot compiles, MinGW cross-compile of tools/ae.c.
Both Windows jobs failed solely on the new emit_c_determinism gate:
cmp/diff live in diffutils, which the MSYS2 runner does not install,
and the missing binary's exit 127 read as "files differ". cksum is
coreutils (CRC + byte count), present on every CI shell.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant