ADR-004 — AOT compilation over JIT/interpreter
Status: Accepted Date: 2026-04-27
Context
A JS-to-Wasm system can run code in three ways: an interpreter (a bytecode VM compiled to Wasm); a JIT engine (a compiler compiled to Wasm, generating new code at runtime); or AOT (the source is compiled to static Wasm at build time, and the runtime executes that Wasm directly).
An interpreter inside Wasm typically runs hot code 25–50× slower than natively-compiled equivalents. A JIT compiler shipped inside Wasm adds 2MB+ of binary overhead and depends on speculative optimization to amortize its own dispatch cost — but most serverless and edge Wasm hosts disable runtime code generation for security isolation, which forces JIT engines back onto their interpreter or baseline tier. AOT, by contrast, performs all compilation work at build time: the deployed module is plain Wasm that the host can execute (and, where available, JIT) directly.
Decision
AOT only. The js2wasm pipeline performs all type analysis, lowering, and Wasm emission at build time. There is no runtime code generation inside the compiled module.
Consequences
Positive: maximum performance for static code; minimal binary size (no embedded compiler or interpreter); cold start is proportional to Wasm instantiation; the output is a plain Wasm module the host runtime can JIT itself when permitted.
Negative: features that depend on runtime code generation cannot be served
by the compiled module alone. Specifically, eval() and dynamic import()
of arbitrary source strings are not supported in the compiled module without
an external code-generation path. For statically known strings, eval()
can be folded at compile time. For dynamic strings, a host import is used
that compiles a fresh Wasm module via the host's compiler (see ADR-010).
There is no in-Wasm interpreter fallback.
Alternatives rejected
- Interpreter inside Wasm: rejected. Performance penalty (25–50× for hot code) is unacceptable for the target workloads.
- JIT engine inside Wasm: rejected. JIT is disabled in serverless and edge hosts; the binary overhead and security constraints make this the worst tradeoff for the target environments.
- Hybrid AOT + embedded interpreter for
eval: rejected as the default. Adds an interpreter to every binary for a feature most modules do not use. The host-import path (ADR-010) covers this when needed without bundling.