bytecodealliance / bytecodealliance/endive

Split oversized WASM functions across multiple JVM methods instead of falling back to the interpreter

Open
#170 7 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
296
Forks
21
Avg merge
2d 12h
Merged PRs (30d)
29

Description

## Problem

When a single WASM function is too large to fit the JVM's 64KB method bytecode limit, the compiler currently offers only interpretation (`interpreterFallback` / `interpretedFunctions`):

```
WASM function size exceeds the Java method size limits and cannot be compiled to Java bytecode.
It can only be run in the interpreter. Either reduce the size of the function or enable the
interpreter fallback mode: WASM function index: 3169 (name: _PyEval_EvalFrameDefault):
Method too large: io/trino/wasm/python/PythonMachineFuncGroup_0.func_3169
```

For [trino-wasm-python](https://github.com/trinodb/trino-wasm-python) (CPython 3.13 compiled to wasm32-wasip1, AOT-compiled with endive 1.0.1) the function that hits the limit is `_PyEval_EvalFrameDefault` — CPython's bytecode interpreter loop, i.e. the hottest function in the module. Falling back to the interpreter for exactly that function is the worst case for performance, and it blocks us from updating Python at all:

- CPython 3.13.2: eval loop is 56,565 bytes of wasm body → compiles fine
- CPython 3.13.3 … 3.13.15: the eval loop grew past the limit → AOT compilation fails
- CPython 3.14: same, and its tail-call interpreter (which would split the loop into small per-opcode functions) cannot be built for wasm because LLVM's wasm backend has no `preserve_none` calling convention

We tried hard to shrink the function from the producer side; none of it closes the gap:

- binaryen v132 `wasm-opt --outlining`: 73,520 → 67,822 bytes
- `-Oz --code-folding --outlining --converge`: 67,749 bytes (plateau)
- compiling only `ceval.c` with clang `-Oz`: 66,967 bytes after wasm-opt

The remaining body is unique per-opcode handler code that no wasm-level transform can deduplicate, so we are pinned to CPython 3.13.2 indefinitely — and interpreter loops of real-world languages will only keep growing.

## Request

Support compiling oversized WASM functions by splitting them across multiple JVM methods instead of rejecting them, e.g. spilling locals into a frame object/arrays and continuing across method boundaries at control-flow seams (block/loop boundaries are natural split points). Even a slower-but-compiled path for oversized functions would be far better than full interpretation for a function like an interpreter dispatch loop.

This is the classic pain point of wasm→JVM compilation (other compilers in this space hit the same wall), so a general solution here would be a significant differentiator.

Happy to test candidate builds against the CPython module — it's a readily available ~50MB real-world stress case with one function right at the boundary.

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the 64KB failure in endive 1.0.1 with the trino-wasm-python CPython module and `_PyEval_EvalFrameDefault`, comparing the compiled path with interpreterFallback/interpretedFunctions. Done means an oversized WASM function compiles across multiple JVM methods and runs without full interpreter fallback; validate it against the CPython module.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, wasm
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.