Repository navigation
Tracker: AOT-first eval/new Function strategy — evaporate runtime dynamism at build time #1677
Copy link
Copy link
Closed
Labels
enhancementNew capability or improvementNew capability or improvement
Description
Goal
Make Perry handle eval / new Function the way an AOT compiler should: never execute a code-string at runtime. Every legitimate case is resolved at or before build time, so the runtime JS engine (perry-jsruntime / embedded V8) becomes deletable from shipped binaries.
What the investigation found (verified against vendored sources)
evalis already dead in Perry-emitted code (perry-hir/src/lockdown.rs: "Perry doesn't emitevaltoday";new Function(body)already refused inperry-hir/.../expr_call/intrinsics.rs). Zero test-files use either.- Effect / Zod use no
eval/new Function. Zod grep is clean; Effect'sSchemais a combinator interpreter over an AST. The Effect blocker is purely compiler-completeness (perry-codegen: Effect — (number).slice is not a function during Schema.ts__init (#680 follow-up, ~310th init) #684 / perry-codegen: object literal with computed-key props + computed-key methods + cross-module spread drops keys & mis-resolves methods — EffectHashRing.tsblocker (post-#740) #809), not dynamism. - The real
new Functioncorpus is small and Fastify-shaped. Runtime libraries that actually JIT:fast-json-stringify(new Function('validator','serializer', contextFunctionCode)from a build-time schema),ajv(schema → validator),find-my-way(registered routes → matcher). Everything else that matched is build tooling (vite/rollup/next/eslint/vitest) that never ships in a Perry binary, or trivial feature-probes. - Every legitimate case specializes a function from build-time-knowable config — a schema, a route table — not from genuinely-runtime data.
Conclusion: a runtime interpreter ("PerryVM") has no use case here. The only place an evaluator is needed is build time, and we already own one (V8) — so the move is to demote V8 to a build-time tool, not embed it at runtime.
Phases (strictly gated)
Each phase must be merged to main and parity-green before the next is started. No parallel work across phases.
- Phase 0 — classifier + precise refusal diagnostic: feat(codegen): classify new Function/eval call sites + precise refusal diagnostic #1678
- Phase 1 — const-fold literal
new Functionbodies → native: feat(codegen): const-fold literal new Function bodies to native functions #1679 - Phase 2 — build-time codegen convention (ajv/standalone first): feat(tooling): build-time codegen convention — ajv/standalone (+Prisma/Drizzle/Vue) AOT path #1680
- Phase 3 — build-time evaluation of runtime codegen (demote V8 to build-time tool): feat(tooling): build-time evaluation of runtime codegen — demote perry-jsruntime to a build-time tool #1681
- Phase 4 — Perry-native reimplementation of imperative-runtime JIT libs (find-my-way): feat(stdlib): Perry-native reimplementation of imperative-runtime JIT libs (find-my-way router) #1682
End state
A Perry binary that links no runtime JS engine, where:
- constant code-strings became native functions (P1),
- schema/template codegen libraries ran at build time and emitted native code (P2/P3),
- the few imperative-runtime JITs have native equivalents (P4),
- anything left over is a clear compile error naming the package and call site (P0).
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew capability or improvementNew capability or improvement
Activity
feat(hir): classify new Function/eval call sites + refusal diagnostic (…
feat(hir): const-fold literal new Function bodies to native functions (…
feat(tooling): build-time codegen convention — perry.codegen + ajv/st…
feat(tooling): build-time codegen convention — perry.codegen + ajv/st…
feat(compiler): self-hosted build-time precompile() — Perry runs its …
proggeramlug commentedon May 26, 2026
Wrap-up — core strategy delivered
The AOT-first eval/
new Functionstrategy is implemented and merged through Phase 3. The headline result: Perry evaporates build-time-knowable dynamism at build time, with no runtime JS engine and no external build dependency — by compiling and running its own output.new Function/eval+ precise refusalperry.codegenbuild-time convention + ajv/standaloneprecompile()(Perry runs its own codegen, no node/V8)Boundary (by design): literal bodies → fold (P1); build-time-knowable codegen → self-hosted precompile (P3); genuinely-runtime
new Function(runtimeData)/eval(userInput)→ refused (P0). An AOT binary can't honestly fake runtime dynamism — the refusal is the correctness guarantee.Remaining, tracked separately (closing this tracker):
type-guard miscompile.These become a focused GC/codegen-correctness workstream rather than open tracker items.