Status of synchronous WASM compilation limitation in main thread (2026/07)
@hoodmane told me in EuroPython that the synchronous WASM compilation limit in Chrome was removed or at least relaxed a lot, so I tested it. Here is the result and related links.
Test script
It tests syncronously compiling WASM modules of size 8MiB, 8MiB +1, 16 MiB, and 64 MiB in the main thread.
function uleb(n) {
const out = [];
do {
let b = n & 0x7f;
n = Math.floor(n / 128);
if (n) b |= 0x80;
out.push(b);
} while (n);
return out;
}
function paddedModule(size) {
const bytes = new Uint8Array(size);
bytes.set([0, 97, 115, 109, 1, 0, 0, 0]); // wasm header
bytes[8] = 0; // custom section
let payload = size - 10;
while (9 + uleb(payload).length + payload !== size) {
payload += size - (9 + uleb(payload).length + payload);
}
const length = uleb(payload);
bytes.set(length, 9);
bytes[9 + length.length] = 0; // empty custom-section name
return bytes;
}
for (const size of [
8 * 1024 * 1024,
8 * 1024 * 1024 + 1,
16 * 1024 * 1024,
64 * 1024 * 1024,
]) {
try {
const module = new WebAssembly.Module(paddedModule(size));
new WebAssembly.Instance(module);
console.log(size, "OK");
} catch (error) {
console.log(size, error.toString());
}
}Test result
| Browser | Result |
|---|---|
| Chrome (Windows, v150) | 8MiB passes, 8MiB+ fails |
| Chrome (MacOS, v150) | 8MiB passes, 8MiB+ fails |
| Chrome (Android [Pixel 9]) | 8MiB passes, 8MiB+ fails |
| Chrome (iOS [iPhone 15]) | Pass |
| Chrome (iPadOS [iPad 9]) | Pass |
| Firefox (Windows, v153) | Pass |
| Safari (MacOS, v26.5 ) | Pass |
| Edge (Windows, v150) | 8MiB passes, 8MiB+ fails |
| Node.js (v24) | Pass |
| Deno (2.9.4) | Pass |
| Bun (v1.3.14) | Pass |
So obviously chromium-based browsers has 8MiB limitation (which is increased from 4KiB before), while all other JS runtimes does not have it. Note that Chrome on iPhone and iPad does not have this limitation since they are based on WebKit.
Related changes
Chrome has updated the limitation in Chrome v115 which was landed Jul 2023 (3 years ago).
There is a --enable-features=WebAssemblyUnlimitedSyncCompilation flag which removes this limitation but it is disabled by default.
How large are shared libraries in Pyodide packages?
I scanned all packages in latest pyodide-recipes release and only the following packages have shared libraries with 8MiB or more.
Note that all libraries in the numpy and scipy are under 8MiB
Sharedlib size in numpy and scipyNumPy 2.4.3
2,154,517 numpy/_core/_multiarray_umath.so
1,056,155 numpy/linalg/_umath_linalg.so
490,960 numpy/random/_generator.so
389,101 numpy/random/mtrand.so
260,256 numpy/fft/_pocketfft_umath.so
207,529 numpy/linalg/lapack_lite.so
138,648 numpy/random/_bounded_integers.so
134,521 numpy/random/_common.so
114,584 numpy/random/bit_generator.so
58,123 numpy/random/_pcg64.so
56,655 numpy/random/_mt19937.so
53,558 numpy/random/_philox.so
38,026 numpy/random/_sfc64.so
SciPy 1.18.0
6,456,081 scipy.libs/libopenblas.so
2,758,624 scipy/sparse/_sparsetools.so
2,377,843 scipy/optimize/_highspy/_core.so
1,419,071 scipy/special/_ufuncs_cxx.so
1,386,725 scipy/linalg/_flapack.so
947,359 scipy/special/cython_special.so
913,909 scipy/special/_special_ufuncs.so
805,670 scipy/fft/_duccfft/pyduccfft.so
705,946 scipy/io/_fast_matrix_market/_fmm_core.so
561,884 scipy/spatial/_qhull.so
545,850 scipy/special/_ufuncs.so
480,765 scipy/special/_gufuncs.so
405,566 scipy/linalg/_fblas.so
367,781 scipy/linalg/_decomp_interpolative.so
365,615 scipy/stats/_unuran/unuran_wrapper.so
260,121 scipy/spatial/_ckdtree.so
242,687 scipy/spatial/transform/_rotation_cy.so
231,406 scipy/stats/_stats.so
223,296 scipy/sparse/_csparsetools.so
216,584 scipy/spatial/_distance_pybind.so
203,624 scipy/sparse/csgraph/_shortest_path.so
186,861 scipy/sparse/linalg/_dsolve/_superlu.so
173,155 scipy/sparse/csgraph/_traversal.so
162,387 scipy/optimize/_highspy/_highs_options.so
150,585 scipy/linalg/cython_lapack.so
147,497 scipy/linalg/_batched_linalg.so
147,226 scipy/linalg/_decomp_update.so
131,947 scipy/odr/__odrpack.so
123,622 scipy/spatial/transform/_rigid_transform_cy.so
122,553 scipy/sparse/linalg/_eigen/arpack/_arpacklib.so
121,661 scipy/cluster/hierarchy/_hierarchy.so
119,757 scipy/interpolate/_interpnd.so
111,485 scipy/interpolate/_fitpack.so
110,042 scipy/io/matlab/_mio5_utils.so
103,251 scipy/linalg/_cythonized_array_utils.so
99,335 scipy/special/_specfun.so
97,671 scipy/interpolate/_ppoly.so
96,634 scipy/interpolate/_rbfinterp_pythran.so
95,854 scipy/sparse/csgraph/_tools.so
94,811 scipy/optimize/_trlib/_trlib.so
93,021 scipy/optimize/_bglu_dense.so
93,010 scipy/signal/_upfirdn_apply.so
86,301 scipy/sparse/csgraph/_matching.so
85,685 scipy/stats/_sobol.so
85,072 scipy/stats/_biasedurn.so
83,937 scipy/optimize/_pava_pybind.so
80,920 scipy/ndimage/_nd_image.so
80,426 scipy/sparse/csgraph/_flow.so
79,516 scipy/ndimage/_ni_label.so
78,631 scipy/_cyutility.so
75,398 scipy/sparse/csgraph/_reordering.so
74,278 scipy/integrate/_quadpack.so
71,940 scipy/cluster/hierarchy/_optimal_leaf_ordering.so
59,781 scipy/linalg/cython_blas.so
57,701 scipy/signal/_peak_finding_utils.so
56,861 scipy/optimize/_moduleTNC.so
56,589 scipy/linalg/_internal_matfuncs.so
56,564 scipy/io/matlab/_streams.so
56,213 scipy/sparse/linalg/_propack.so
53,251 scipy/linalg/_solve_toeplitz.so
52,259 scipy/integrate/_vode.so
52,070 scipy/cluster/vq/_vq.so
51,853 scipy/fftpack/convolve.so
50,983 scipy/interpolate/_rgi_cython.so
49,628 scipy/signal/_sosfilt.so
49,497 scipy/signal/_sigtools.so
48,249 scipy/stats/_qmc_cy.so
47,473 scipy/stats/_stats_pythran.so
47,398 scipy/stats/_qmvnt_cy.so
45,816 scipy/special/_ellip_harm_2.so
45,577 scipy/interpolate/_dierckx.so
45,347 scipy/stats/_ansari_swilk_statistics.so
43,951 scipy/sparse/csgraph/_min_spanning_tree.so
43,212 scipy/linalg/_matfuncs_sqrtm_triu.so
42,146 scipy/_lib/_ccallback_c.so
41,866 scipy/stats/_rcont/rcont.so
40,553 scipy/optimize/cython_optimize/_zeros.so
38,586 scipy/spatial/_hausdorff.so
38,261 scipy/optimize/_minpack.so
38,193 scipy/linalg/_linalg_pythran.so
38,089 scipy/_lib/_uarray/_uarray.so
36,467 scipy/spatial/_voronoi.so
36,225 scipy/ndimage/_cytest.so
36,090 scipy/special/_test_internal.so
33,211 scipy/_lib/messagestream.so
32,833 scipy/integrate/_odepack.so
29,327 scipy/io/matlab/_mio_utils.so
25,605 scipy/optimize/_lsq/givens_elimination.so
24,959 scipy/stats/_levy_stable/levyst.so
23,984 scipy/optimize/_slsqplib.so
23,868 scipy/spatial/_distance_wrap.so
23,092 scipy/optimize/_group_columns.so
22,725 scipy/special/_comb.so
21,574 scipy/optimize/_lbfgsb.so
19,533 scipy/signal/_spline.so
17,656 scipy/optimize/_direct.so
16,934 scipy/_lib/_test_deprecation_call.so
15,644 scipy/integrate/_dop.so
14,658 scipy/optimize/_lsap.so
13,781 scipy/signal/_max_len_seq_inner.so
10,171 scipy/ndimage/_rank_filter_1d.so
8,500 scipy/_lib/_test_deprecation_def.so
7,539 scipy/optimize/_zeros.so
4,461 scipy/_lib/_test_ccallback.so
1,972 scipy/ndimage/_ctest.so
1,222 scipy/integrate/_test_multivariate.so
747 scipy/_lib/_fpumode.soI didn't check blis or semicolon-lapack so it would be great if @agriyakhetarpal can check them too.
Conclusion
I think we can start experimenting with synchronously loading WASM modules with 8MiB or lower, probably making a optional parameter in loadPyodide to disable / enable that feature. Since Chrome v115 is pretty old, I think we can enable it by default it things work well.
Source: pyodide/pyodide