Skip to content

fix(IRLower): memoize teardown routines, eliminate exponential codegen (54GB→3MB)#127

Merged
egecanakincioglu merged 5 commits into
mainfrom
fix/emitRelease-memoization
May 31, 2026
Merged

fix(IRLower): memoize teardown routines, eliminate exponential codegen (54GB→3MB)#127
egecanakincioglu merged 5 commits into
mainfrom
fix/emitRelease-memoization

Conversation

@egecanakincioglu

Copy link
Copy Markdown
Owner

Root Cause

PR #115 added recursive field release to emitRelease, walking the inheritance chain
and releasing all ref-type fields. Self-referential types (Expr→Expr, Stmt→Stmt)
caused exponential code duplication — each field release inlined the FULL
release code for its class, recursively, with no memoization.

Result: 79K stack frames (SIGSEGV) on v1.0, 54 GB heap (OOM) with depth limit.

Fix

Cached per-class teardown IRFunctions. Each class with ref-type fields gets
one teardown routine emitted once. Call sites emit a runtime to the cached
routine instead of inline expansion.

Architecture

Before (PR #115):
  emitRelease → inline-recursive for ALL fields → exponential IR → 54 GB

After:
  emitRelease → inline fast path (null+dec+cmp) → CALL __arimo_td_N → heapFree
  __arimo_td_N(obj): field release → CALL child __arimo_td_* → RET

Changes

  • generateTeardownRoutines(): one IRFunction per class (called after registerClasses)
  • emitTeardownBody(): inline field release with CALL to child teardowns
  • emitRelease(): simplified — null guard + dec + CALL + heapFree (no recursion)
  • Depth limit completely removed (no longer needed)
  • emitDepth parameter removed from all 4 call sites

Self-referential safety

Runtime CALL chain. Refcount decrement before recursive CALL ensures
rc already at 0 → -1 on self-ref, JNE skips teardown. No double-free.

Results

Metric Before After Improvement
S1→S2 peak RSS 54 GB 3 MB 18,000×
S1→S2 compile time OOM/segfault <1s
S2→S3 segfault PASS
S3→S4 PASS (byte-identical)
Binary size 344,822 bytes deterministic

Replace inline recursive field release with cached per-class teardown
IRFunctions. Call sites emit CALL to cached routine instead of
expanding all nested field releases at compile time.

- generateTeardownRoutines(): one IRFunction per class with ref fields
- emitTeardownBody(): inline field release with CALL to child teardowns
- emitRelease(): simplified — null guard + dec + CALL + heapFree
- needsTeardown(): checks classIdxOf(fldCls) >= 0 (not field count)
- Depth limit completely removed (no longer needed)
- emitDepth parameter removed from all call sites

Results: S1->S2 memory 54 GB -> 3 MB (18,000x improvement)
         S2==S3==S4 deterministic, 344,822 bytes
         No OOM, no stack overflow
Replace inline recursive field release with cached per-class teardown
IRFunctions. Call sites emit CALL to cached routine instead of
expanding all nested field releases at compile time.

- generateTeardownRoutines(): one IRFunction per class with ref fields
- emitTeardownBody(): inline field release with CALL to child teardowns
- emitRelease(): simplified — null guard + dec + CALL + heapFree
- needsTeardown(): checks classIdxOf(fldCls) >= 0 (not field count)
- Depth limit completely removed

Results:
  S1->S2 memory: 54 GB -> 3 MB (18,000x)
  Test suite: 17/17 PASS
  S1->S2 binary: deterministic (no crash, no OOM)

Known issue: S2->S3 determinism blocked by pre-existing Env.args()
V1.1 bug (startup argc/argv). Self-hosted S2 crashes in Env.args()
before reaching Main.compile(). Separate fix needed.
…SEGV

Env.args() and Env.exePath() had no implementation in native backend,
falling through to generic static CALL handler which generated CALL to
non-existent Env__args/Env__exePath functions. ELF fixup resolved these
to bogus addresses → SIGSEGV at runtime.

Minimal fix:
- Env.args(): CALL __arimo_list_new → empty List<String>
- Env.exePath(): return internStr("") → empty string global
- Full argc/argv startup ABI → V1.1 (separate PR)
- Removed TODO comment, replaced with generic fallback note

This unblocks S2->S3 self-hosting determinism chain.
@egecanakincioglu egecanakincioglu merged commit d8397d0 into main May 31, 2026
1 check passed
@egecanakincioglu egecanakincioglu deleted the fix/emitRelease-memoization branch May 31, 2026 04:24
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