Skip to content

WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944

Description

@mmcky

Note

Updated 2026-08-21. The original body attributed the failure to comb_jit's eager signature and said the Emscripten build does not support the cache's object path, so the error fired at definition; the upstream analysis in emscripten-forge/recipes#6309 shows the real mechanism is a cache-miss cache=True caller linking a cache-hit callee in the same warm session, so the signature only decides which function trips it first. The mechanism paragraph, the Options table (now including what PR #943 does) and the Tasks are corrected below; the traceback, reproducer, upstream link and the 2026-08-21 decision are unchanged. The original text is preserved in the edit history.

Part of #925 (Phase 1). Surfaced while reviewing #942; split out of #929, which is now documentation-only.

Problem

On the emscripten-forge Numba build running in the xeus-python JupyterLite kernel, calling simplex_grid fails before any computation happens:

File /lib/python3.13/site-packages/numba/core/codegen.py:632, in CodeLibrary._get_compiled_object(self)
    630     raise ValueError("object caching not enabled in %s" % (self,))
    631 if self._compiled_object is None:
--> 632     raise RuntimeError("no compiled object yet for %s" % (self,))
    633 return self._compiled_object

RuntimeError: no compiled object yet for <Library 'comb_jit' at 0x9e12360>

Reproducer (in the browser kernel): import quantecon as qe; qe.simplex_grid(3, 4). Confirmed independently by @kp992 and @oyamad on #942. Precondition: the persistent cache must be warmcomb_jit cached by an earlier session on the same browser profile (any session that did import quantecon is enough) while num_compositions_jit is not. A cold single-session kernel does not reproduce it; the upstream control is pointing NUMBA_CACHE_DIR at an empty directory.

Mechanism (see emscripten-forge/recipes#6309). The emscripten-forge build does persist the Numba cache — patch 0006 enables object caching and writes to /drive/.cache/numba — but its save path is what fails. When a cache=True function is saved, patch 0006's _serialize_wasm_linking_libraries walks the libraries it links and calls _get_compiled_object() on each. A library that was restored from the cache no longer has that object: mainline Numba's _object_getbuffer_hook hands the buffer to the engine and sets _compiled_object = None. So the crash occurs exactly when a function compiled fresh in this session (cache miss) links a callee restored from cache in the same session (cache hit); fresh+fresh and hit+hit are both fine. The error therefore fires at the first call of the caller, not at definition — in the traceback above it is simplex_gridDispatcher.compilesave_overload, i.e. the save of the freshly compiled num_compositions_jit, whose linked comb_jit was restored at import. The failure is self-perpetuating: the save that would cache the caller is the operation that crashes, so the caller can never become a cache hit and the crash recurs in every later session.

comb_jit is the deterministic trigger only because it is the library's one function with an eager signature and cache=True together (@jit(types.intp(types.intp, types.intp), nopython=True, cache=True)): it is compiled at every import quantecon, so in every warm session it is restored from cache before any caller has a chance to be compiled fresh alongside it. The eager signature is not itself the bug — the upstream MWE crashes two lazy @njit(cache=True) functions f/g the same way. Lazy cache=True functions with no jitted callees (e.g. _cartesian_index) are unaffected.

This, not integer width, is what currently blocks simplex_grid, num_compositions_jit and k_array_rank_jit in the browser. The cache=Truecache=True chains on main are:

  • simplex_gridnum_compositions_jitcomb_jit — fails deterministically on a warm cache (this issue).
  • k_array_rank_jitcomb_jit — same trigger, same outcome.
  • _lemke_howson_tbl (game_theory/lemke_howson.py), linprog_simplex and lcp_lemke_pivoting / _lex_min_ratio_test — latently exposed: the same crash whenever one caller cached the shared helpers in an earlier session and a different, not-yet-cached caller is compiled in a later one (e.g. linprog_simplex in session 1, then a first call to lcp_lemke in session 2).

Upstream

Filed by @oyamad as emscripten-forge/recipes#6309 (2026-08-20; open, two follow-up comments as of 2026-08-21). The report gives the root cause above and a quantecon-free f/g MWE; the follow-ups add a copy-paste three-cell repro and verify the first candidate patch (retain _compiled_object after restore) by monkeypatching a live kernel: simplex_grid(3, 4) completes and writes the num_compositions_jit / simplex_grid entries, which unpatched kernels afterwards load successfully. No upstream PR yet. The report also notes a secondary issue — cache writes shortly before a page reload can be lost because /drive is backed by the asynchronous Contents API — which makes the crash intermittent to reproduce by hand.

Options

Option Effect Cost
Wait for the upstream fix No library change simplex_grid stays broken in the browser until the recipe is rebuilt
Drop cache=True from comb_jit only Works today for the three comb_jit chains: comb_jit is then compiled fresh at import, never restored, and skipped at restore time by the is_symbol_defined check; comb_jit is tiny so the recompile cost is negligible Loses disk caching for one function natively; leaves the _pivoting / _lex_min_ratio_test chains exposed
Gate cache on sys.platform != "emscripten" Native behaviour unchanged Adds a platform branch to a decorator; same coverage as the row above unless applied to every cache=True function, which would give up persistent caching in the browser altogether
Drop the eager signature only (what PR #943 for #930 does) comb_jit no longer compiles at import, so a cold session that calls simplex_grid first saves the whole chain fresh and later sessions are all hits Does not fix this issue — a lazily cached comb_jit is still restored in later sessions, so the next not-yet-cached caller (k_array_rank_jit, or simplex_grid / num_compositions_jit when their entries are missing) fails identically; it narrows the exposure to warm sessions in which comb_jit was cached by a different caller

Decision (2026-08-21): wait for the upstream fix; no library-side change for now. The candidate patch is already verified in emscripten-forge/recipes#6309, so the remaining wait is for an upstream PR and a recipe rebuild. Revisit if emscripten-forge/recipes#6309 stalls.

Tasks

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions