You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perry compile of a 4-line program importing Effect (a popular TypeScript framework, ~12k weekly downloads of the effect package) consumes 34 GB of RSS and 249 GB of peak memory footprint before being SIGKILL'd by the OS after ~7 minutes of wall-clock time, during the codegen phase.
Module collection and parse succeed cleanly (152 native modules collected). The compiler reaches Generating code... and then memory grows unboundedly.
What you expected
perry compile to either (a) finish, (b) finish in proportional memory to the source size (Effect is ~150 modules and ~30k LOC for Effect.ts alone — substantial but not pathological), or (c) emit a clear "compiler limit exceeded" error at a sensible memory bound rather than letting the OS kill the process.
/usr/bin/time -l perry compile test_direct.ts -o /tmp/out
Diagnostic output
Collecting modules...
Compile package: effect
Found 152 module(s): 152 native, 0 JavaScript
Generating code...
[ ~7 min later, OS SIGKILLs the process ]
exit=137 (SIGKILL)
The compiler emits 173 Warning: unknown identifier '...' lines during HIR lowering — most of these are arguments (148×, used heavily by Effect's dual() data-first/data-last helper) and Intl (~10×, used in DateTime / Schedule modules). These are pre-existing categorical gaps (arguments is documented in docs/src/language/limitations.md); I do not believe they cause the OOM but they may be amplifying it via the per-warning lowering fallback path.
Environment
Perry version: 0.5.399
Host OS: macOS 26.4 (arm64, Apple Silicon)
Target: native (default — no --target flag)
Installed via: from source (cargo build --release against current main)
Effect version: 3.21.2 (latest as of filing)
Notes for triage
Module collection + parse phases finish promptly. Memory growth begins after Generating code.... This points the finger at HIR transforms, codegen, or a pass that runs between them. Reasonable starting suspects from CHANGELOG.md history:
widen_mutable_captures in crates/perry-hir/src/lower.rs — v0.5.336 noted three back-to-back walks; quadratic factor possible on a deeply-nested HIR like Effect's Channel/Stream/Fiber generic chains.
The async_to_generator transform (v0.5.371) — Effect uses generators heavily for Effect.gen; this pass walks every fn body.
The inliner (crates/perry-transform/src/inline.rs) — Effect's dual() helper produces functions that are tiny and called from everywhere; inlining cost may be quadratic in call-site count.
Suggested investigation: build Perry with debug symbols (RUSTFLAGS='-g' cargo build --release), run the repro under samply or Instruments → Allocations, look for the call site holding the most live bytes when RSS is at ~10-20 GB. If a single transform pass owns most of the heap, that's the wedge.
Workaround for users today: none. import { Effect } from "effect" (the bare form) instead "compiles" but silently produces result: 0 because Perry's collector doesn't traverse export * as Effect from "./Effect.js" — filed separately as #ADJACENT.
Why this matters
Effect is one of the largest and most-watched TypeScript-only frameworks. A successful "perry compile your Effect app to a native binary" story is a strong lever for adoption — but at 34 GB RSS the bar isn't just "Effect works", it's "Perry works on any large TypeScript codebase." This OOM almost certainly reproduces on other large libraries too (Angular, NestJS, RxJS-heavy apps); Effect is just the largest pure-TS surface I've tested.
What happened
perry compileof a 4-line program importing Effect (a popular TypeScript framework, ~12k weekly downloads of theeffectpackage) consumes 34 GB of RSS and 249 GB of peak memory footprint before being SIGKILL'd by the OS after ~7 minutes of wall-clock time, during the codegen phase.Module collection and parse succeed cleanly (152 native modules collected). The compiler reaches
Generating code...and then memory grows unboundedly.What you expected
perry compileto either (a) finish, (b) finish in proportional memory to the source size (Effect is ~150 modules and ~30k LOC forEffect.tsalone — substantial but not pathological), or (c) emit a clear "compiler limit exceeded" error at a sensible memory bound rather than letting the OS kill the process.Minimal reproduction
Command:
Diagnostic output
/usr/bin/time -lsummary on the run that crashed:The compiler emits 173
Warning: unknown identifier '...'lines during HIR lowering — most of these arearguments(148×, used heavily by Effect'sdual()data-first/data-last helper) andIntl(~10×, used in DateTime / Schedule modules). These are pre-existing categorical gaps (argumentsis documented indocs/src/language/limitations.md); I do not believe they cause the OOM but they may be amplifying it via the per-warning lowering fallback path.Environment
--targetflag)cargo build --releaseagainst current main)Notes for triage
Generating code.... This points the finger at HIR transforms, codegen, or a pass that runs between them. Reasonable starting suspects from CHANGELOG.md history:widen_mutable_capturesincrates/perry-hir/src/lower.rs— v0.5.336 noted three back-to-back walks; quadratic factor possible on a deeply-nested HIR like Effect's Channel/Stream/Fiber generic chains.compute_max_local_idwalks — class-scan was added for Class method body cannot capture enclosing-function locals #212 (v0.5.323) and Gap: await using (Symbol.asyncDispose / ES2024 explicit resource management) #154 (v0.5.388); similar walk over Effect's many class methods could compound.async_to_generatortransform (v0.5.371) — Effect uses generators heavily forEffect.gen; this pass walks every fn body.crates/perry-transform/src/inline.rs) — Effect'sdual()helper produces functions that are tiny and called from everywhere; inlining cost may be quadratic in call-site count.RUSTFLAGS='-g' cargo build --release), run the repro undersamplyorInstruments → Allocations, look for the call site holding the most live bytes when RSS is at ~10-20 GB. If a single transform pass owns most of the heap, that's the wedge.import { Effect } from "effect"(the bare form) instead "compiles" but silently producesresult: 0because Perry's collector doesn't traverseexport * as Effect from "./Effect.js"— filed separately as #ADJACENT.Why this matters
Effect is one of the largest and most-watched TypeScript-only frameworks. A successful "perry compile your Effect app to a native binary" story is a strong lever for adoption — but at 34 GB RSS the bar isn't just "Effect works", it's "Perry works on any large TypeScript codebase." This OOM almost certainly reproduces on other large libraries too (Angular, NestJS, RxJS-heavy apps); Effect is just the largest pure-TS surface I've tested.