fix: order-independent heap classification (#1311), determinism gate (#1299), README positioning (#475), dead runtime surface - #1330
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, sooid, oerr = asn1.read_oid(r)inside a helper classifiedread_oid's error slot against the helper's (wrong) tracker table, memoised non-heap, and the chain minted heap empties nobody freed: 1 byte perasn1.read_*call, unreclaimable by the caller. The same three lines inline inmainwere clean becausemainemits 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.mdnow 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.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.
Erlang-style actors, Rust-grade capability discipline, and Go-flavored ergonomics, compiled to readable C.docs/runtime-optimizations.md, embed flags already indocs/c-embedding.md,sendfile/static files newly documented indocs/http-server.md.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:
ActorPools were allocated and initialized, butactor_pool_acquirehad zero call sites. Worse, the release path cast everyActorBase*toPooledActor(not layout-compatible; the read lands inside thestepfunction pointer) and, when the overlaid bytes landed in 0..63, routed NUMA-allocated memory to plainfree(): mmap-backed under libnuma, VirtualAlloc-backed on Windows NUMA. Removed both pool headers (one was a duplicate included nowhere), the deadAETHER_ACTOR_POOL_SIZEenv knob and profile constants, the never-incrementedactors_pooledcounter, the falseActor Pooling [ON]config print, and the isolated unit tests of the unused structure.scheduler_spawn_pooled/scheduler_release_pooledrenamed toscheduler_spawn_actor/scheduler_release_actor(nothing pools); all callers, benchmarks, and docs updated.sizeof(ActorBase);numa_freeunmaps 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.runtime/utils/aether_tracing.ccompiled into every build and generated C included its header, but nothing ever called it; the README advertised it. Removed the TU, the#includeemission, and the manifest rows (thesnprintfargument lists rebalanced; the mismatch was caught by the MinGW-Werror=format-extra-argscross-check). Also removed six orphaned generated-C snapshots undertests/integration/that no build referenced, plusscheduler_enable_features(zero callers; its only live effect duplicatedaether_enable_opt).Verification
make test: 229/229make examples: 88/88make test-ae: passing locally except sandbox-environmental cases (network bind blocked, host interpreters killed, andabort()-based tests: a plain two-line Cabort()program hangs in this sandbox, proven independently)emit_c_determinismgate: 7/7 byte-identical-Werrorspot compiles of every changed TU; MinGW cross-compile oftools/ae.ccleanThe 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).