CINXE.COM
PEP 776 – Emscripten Support | peps.python.org
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 776 – Emscripten Support | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0776/"> <link rel="stylesheet" href="../_static/style.css" type="text/css"> <link rel="stylesheet" href="../_static/mq.css" type="text/css"> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light"> <link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark"> <link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss"> <meta property="og:title" content='PEP 776 – Emscripten Support | peps.python.org'> <meta property="og:description" content="Emscripten is a complete open source compiler toolchain. It compiles C/C++ code into WebAssembly/JavaScript executables, for use in JavaScript runtimes, including browsers and Node.js. The Rust language also maintains an Emscripten target."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0776/"> <meta property="og:site_name" content="Python Enhancement Proposals (PEPs)"> <meta property="og:image" content="https://peps.python.org/_static/og-image.png"> <meta property="og:image:alt" content="Python PEPs"> <meta property="og:image:width" content="200"> <meta property="og:image:height" content="200"> <meta name="description" content="Emscripten is a complete open source compiler toolchain. It compiles C/C++ code into WebAssembly/JavaScript executables, for use in JavaScript runtimes, including browsers and Node.js. The Rust language also maintains an Emscripten target."> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li> <li><a href="../pep-0000/">PEP Index</a> » </li> <li>PEP 776</li> </ul> <button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())"> <svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg> <span class="visually-hidden">Toggle light / dark / auto colour theme</span> </button> </header> <article> <section id="pep-content"> <h1 class="page-title">PEP 776 – Emscripten Support</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Hood Chatham <roberthoodchatham at gmail.com></dd> <dt class="field-even">Sponsor<span class="colon">:</span></dt> <dd class="field-even">Łukasz Langa <lukasz at python.org></dd> <dt class="field-odd">Discussions-To<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/84996">Discourse thread</a></dd> <dt class="field-even">Status<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Proposal under active discussion and revision">Draft</abbr></dd> <dt class="field-odd">Type<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-even">Topic<span class="colon">:</span></dt> <dd class="field-even"><a class="reference external" href="../topic/packaging/">Packaging</a></dd> <dt class="field-odd">Created<span class="colon">:</span></dt> <dd class="field-odd">18-Mar-2025</dd> <dt class="field-even">Python-Version<span class="colon">:</span></dt> <dd class="field-even">3.14</dd> <dt class="field-odd">Post-History<span class="colon">:</span></dt> <dd class="field-odd"><a class="reference external" href="https://discuss.python.org/t/84996" title="Discourse thread">18-Mar-2025</a></dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#goals">Goals</a><ul> <li><a class="reference internal" href="#runtime-goals">Runtime Goals</a></li> <li><a class="reference internal" href="#packaging-goals">Packaging Goals</a></li> </ul> </li> <li><a class="reference internal" href="#emscripten-platform-information">Emscripten Platform Information</a><ul> <li><a class="reference internal" href="#background-on-emscripten">Background on Emscripten</a></li> <li><a class="reference internal" href="#posix-compliance">POSIX Compliance</a></li> <li><a class="reference internal" href="#emscripten-abi-compatibility">Emscripten ABI Compatibility</a></li> <li><a class="reference internal" href="#development-tools">Development Tools</a></li> <li><a class="reference internal" href="#emscripten-application-lifecycle">Emscripten Application Lifecycle</a></li> <li><a class="reference internal" href="#file-system-setup">File System Setup</a><ul> <li><a class="reference internal" href="#the-standard-library">The Standard Library</a></li> <li><a class="reference internal" href="#third-party-packages">Third-party packages</a></li> </ul> </li> <li><a class="reference internal" href="#console-and-interactive-usage">Console and Interactive Usage</a></li> <li><a class="reference internal" href="#dynamic-libraries">Dynamic Libraries</a><ul> <li><a class="reference internal" href="#main-thread-synchronous-loading-limit">Main Thread Synchronous Loading Limit</a></li> <li><a class="reference internal" href="#missing-rpath-support">Missing RPATH Support</a></li> </ul> </li> <li><a class="reference internal" href="#traps-and-uncaught-exceptions">Traps and Uncaught Exceptions</a></li> </ul> </li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#scope-of-work">Scope of Work</a></li> <li><a class="reference internal" href="#linkage">Linkage</a></li> <li><a class="reference internal" href="#standard-library">Standard Library</a><ul> <li><a class="reference internal" href="#unsupported-modules">Unsupported Modules</a><ul> <li><a class="reference internal" href="#removed-modules">Removed Modules</a></li> <li><a class="reference internal" href="#included-but-not-working-modules">Included but not Working Modules</a></li> </ul> </li> <li><a class="reference internal" href="#platform-identification">Platform Identification</a></li> </ul> </li> <li><a class="reference internal" href="#signals-support">Signals Support</a></li> <li><a class="reference internal" href="#function-pointer-casts">Function Pointer Casts</a></li> <li><a class="reference internal" href="#ci-resources">CI Resources</a></li> <li><a class="reference internal" href="#packaging">Packaging</a><ul> <li><a class="reference internal" href="#existing-package-support">Existing Package Support</a></li> <li><a class="reference internal" href="#emscripten-wheel-format">Emscripten Wheel Format</a></li> </ul> </li> <li><a class="reference internal" href="#pep-11">PEP 11</a></li> </ul> </li> <li><a class="reference internal" href="#future-work">Future Work</a><ul> <li><a class="reference internal" href="#improving-cross-builds-in-the-packaging-ecosystem">Improving Cross Builds in the Packaging Ecosystem</a></li> <li><a class="reference internal" href="#pyodide-runtime-features-to-be-upstreamed">Pyodide Runtime Features to be Upstreamed</a><ul> <li><a class="reference internal" href="#javascript-api-for-bootstrapping">JavaScript API for Bootstrapping</a></li> <li><a class="reference internal" href="#javascript-foreign-function-interface-ffi">JavaScript foreign function interface (FFI)</a></li> <li><a class="reference internal" href="#asyncio">Asyncio</a></li> </ul> </li> </ul> </li> <li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li> <li><a class="reference internal" href="#security-implications">Security Implications</a></li> <li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li> <li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p><a class="reference external" href="https://emscripten.org/">Emscripten</a> is a complete open source compiler toolchain. It compiles C/C++ code into WebAssembly/JavaScript executables, for use in JavaScript runtimes, including browsers and Node.js. The Rust language also maintains an Emscripten target.</p> <p>This PEP formalizes the addition of Tier 3 for Emscripten support in Python 3.14 which <a class="reference external" href="https://github.com/python/steering-council/issues/256">was approved by the Steering Council on October 25, 2024</a>. The goal is to allow Pyodide wheels to be uploaded to PyPI.</p> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>A web browser is a universal computing platform, available on Windows, macOS, Linux, and every smartphone.</p> <p><a class="reference external" href="https://pyodide.org/">The Pyodide project</a> has supported Emscripten Python since 2018. Hundreds of thousands of students have learned Python through Pyodide via projects like <a class="reference external" href="https://web.archive.org/web/20241211090946/https://cfp.jupytercon.com/2023/talk/TJ9YEV/">Capytale</a> and <a class="reference external" href="https://stanford.edu/~cpiech/bio/papers/pyodideU.pdf">PyodideU</a>. Pyodide is also increasingly being used by Python packages to provide interactive documentation. This demonstrates both the importance and the maturity of the Emscripten platform.</p> <p>Emscripten and WASI are also the only supported platforms that offer any meaningful sandboxing.</p> </section> <section id="goals"> <h2><a class="toc-backref" href="#goals" role="doc-backlink">Goals</a></h2> <p>It is our long term goal to upstream the entire Pyodide runtime into CPython, but this is out of scope for the present PEP. This PEP only attempts to establish the foundation for future work.</p> <section id="runtime-goals"> <h3><a class="toc-backref" href="#runtime-goals" role="doc-backlink">Runtime Goals</a></h3> <ol class="arabic simple"> <li>To describe the current state of the CPython Emscripten runtime</li> <li>To describe the current state of the Pyodide runtime</li> <li>To identify minor features to be upstreamed from the Pyodide runtime into the CPython Emscripten runtime</li> </ol> <p>The minor features identified here are all features that could be implemented without a PEP. We discuss more significant runtime features that we would like to implement but we defer decisions on those features to subsequent PEPs.</p> </section> <section id="packaging-goals"> <h3><a class="toc-backref" href="#packaging-goals" role="doc-backlink">Packaging Goals</a></h3> <ol class="arabic simple"> <li>To describe Pyodide’s packaging tooling</li> <li>To describe Pyodide’s wheel abi to a similar level of detail as the manylinux PEPs</li> <li>For Pyodide wheels to be allowed for upload to PyPI</li> </ol> </section> </section> <section id="emscripten-platform-information"> <h2><a class="toc-backref" href="#emscripten-platform-information" role="doc-backlink">Emscripten Platform Information</a></h2> <section id="background-on-emscripten"> <h3><a class="toc-backref" href="#background-on-emscripten" role="doc-backlink">Background on Emscripten</a></h3> <p><a class="reference external" href="https://emscripten.org/docs/introducing_emscripten/about_emscripten.html">Emscripten</a> consists of a C and C++ compiler and linker based on <a class="reference external" href="https://llvm.org/">LLVM</a>, together with a runtime based on a mildly patched musl libc.</p> <p>Emscripten is a POSIX-based platform. It uses the <a class="reference external" href="https://webassembly.github.io/spec/core/binary/index.html">WebAssembly binary format</a>, and the <a class="reference external" href="https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md">WebAssembly dynamic linking section</a>.</p> <p>The <code class="docutils literal notranslate"><span class="pre">emcc</span></code> compiler is a wrapper around <code class="docutils literal notranslate"><span class="pre">clang</span></code>. The <code class="docutils literal notranslate"><span class="pre">emcc</span></code> linker is a wrapper around <code class="docutils literal notranslate"><span class="pre">wasm-ld</span></code> (<a class="reference external" href="https://lld.llvm.org/WebAssembly.html">also part of the LLVM toolchain</a>).</p> <p>Emscripten support for portable C/C++ code source compatibility with Linux is fairly comprehensive, with certain expected exceptions to be spelled out. CPython already supports compilation to Emscripten, and it only requires a very modest number of modifications to the normal Linux target.</p> </section> <section id="posix-compliance"> <h3><a class="toc-backref" href="#posix-compliance" role="doc-backlink">POSIX Compliance</a></h3> <p>Emscripten is a POSIX platform. However, there are POSIX APIs that exist but always fail when called and POSIX APIs that don’t exist at all. In particular, there are problems with networking APIs and blocking I/O, and there is no support for <code class="docutils literal notranslate"><span class="pre">fork()</span></code>. See <a class="reference external" href="https://emscripten.org/docs/porting/guidelines/portability_guidelines.html">Emscripten Portability Guidelines</a>.</p> <p>Emscripten executables can be linked with threading support, but it comes with several limitations:</p> <ul class="simple"> <li>Enabling threading requires websites to be served with special security headers that indicate acceptance of the possibility of <a class="reference external" href="https://en.wikipedia.org/wiki/Spectre_(security_vulnerability)">Spectre</a>-style information leakage. These headers are a usability hazard for users who are not intimately familiar with the web platform.</li> <li>If an executable is linked with both threading and a dynamic loader, Emscripten prints a warning that using dynamic loading and pthreads together is experimental. It may cause performance problems or crashes. These problems may require WebAssembly standards work to resolve.</li> </ul> <p>Because of these limitations, Pyodide standardizes a no-pthreads build of Python. If there is sufficient demand, a pthreads build with no dynamic loader could be added later.</p> </section> <section id="emscripten-abi-compatibility"> <h3><a class="toc-backref" href="#emscripten-abi-compatibility" role="doc-backlink">Emscripten ABI Compatibility</a></h3> <p>The Emscripten compiler has no ABI stability guarantees between versions. Many Emscripten updates are ABI compatible by chance, and the Rust Emscripten target behaves as if the ABI were stable with only <a class="reference external" href="https://github.com/rust-lang/rust/issues/131467">occasional negative consequences</a>.</p> <p>There are several linker flags that adjust the Emscripten ABI, so Python packages built to run with Emscripten must make sure to match the ABI-sensitive linker flags used to compile the interpreter to avoid load-time or run-time errors. The Emscripten compiler continuously fixes bugs and adds support for new web platform features. Thus, there is significant benefit to being able to update the ABI.</p> <p>In order to balance the ABI stability needs of package maintainers with the ABI flexibility to allow the platform to move forward, Pyodide plans to adopt a new ABI for each feature release of Python.</p> <p>The Pyodide team also coordinates the ABI flags that Pyodide uses with the Emscripten ABI that Rust supports in order to ensure that we have support for the many popular Rust packages. Historically, most of the work for this has been related to unwinding ABIs. See for instance <a class="reference external" href="https://github.com/rust-lang/compiler-team/issues/801">this Rust Major Change Proposal</a>.</p> </section> <section id="development-tools"> <h3><a class="toc-backref" href="#development-tools" role="doc-backlink">Development Tools</a></h3> <p>Emscripten development tools are equally well supported on Linux, Windows, and macOS. The upstream tools include:</p> <ul class="simple"> <li>The Emscripten Software Developer Kit (<strong class="program">emsdk</strong>) which can be used to install the Emscripten compiler toolchain (<strong class="program">emcc</strong>).</li> <li><strong class="program">emcc</strong> is a C and C++ compiler, linker, and a sysroot with headers for the system libraries. The system libraries themselves are generated on the fly based on the ABI requested.</li> <li>Node.js can be used as an “emulator” to run Emscripten programs from the command line. This emulation behaves best on Linux with macOS as a runner up. Node.js is the most convenient way to test Emscripten programs.</li> <li>It is possible to run Emscripten programs inside of any web browser. Browser automation tools like Selenium, Playwright, or Puppeteer can be used to test features that are browser-only.</li> </ul> <p>Pyodide’s tools:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">pyodide</span> <span class="pre">build</span></code> can be used to cross compile Python packages to run on Emscripten. Cross compilation works best on Linux, there is experimental support on macOS, and it is entirely unsupported on Windows.</li> <li><code class="docutils literal notranslate"><span class="pre">pyodide</span> <span class="pre">venv</span></code> can make a virtual environment that runs in Pyodide.</li> <li><code class="docutils literal notranslate"><span class="pre">pytest-pyodide</span></code> can test Python code against various JavaScript runtimes.</li> </ul> <p><a class="reference external" href="https://cibuildwheel.pypa.io/">cibuildwheel</a> supports building wheels to target Emscripten using <code class="docutils literal notranslate"><span class="pre">pyodide</span> <span class="pre">build</span></code>.</p> <p>In the short term, Pyodide’s packaging tooling will stay in the Pyodide repository. It is an open question where Pyodide’s packaging tooling should live in the long term. Two sensible options would be for it to remain under the <code class="docutils literal notranslate"><span class="pre">pyodide</span></code> organization or be moved into the <code class="docutils literal notranslate"><span class="pre">pypa</span></code> organization on GitHub.</p> </section> <section id="emscripten-application-lifecycle"> <h3><a class="toc-backref" href="#emscripten-application-lifecycle" role="doc-backlink">Emscripten Application Lifecycle</a></h3> <p>An Emscripten “binary” consists of a pair of files, an <code class="docutils literal notranslate"><span class="pre">.mjs</span></code> file and a <code class="docutils literal notranslate"><span class="pre">.wasm</span></code> file. The <code class="docutils literal notranslate"><span class="pre">.wasm</span></code> file contains all of the compiled C/C++/Rust code. The <code class="docutils literal notranslate"><span class="pre">.mjs</span></code> file contains the lifecycle code to set up the runtime, locate the <code class="docutils literal notranslate"><span class="pre">.wasm</span></code> file, compile it, instantiate it, call the <code class="docutils literal notranslate"><span class="pre">main()</span></code> function, and to shut down the runtime on exit. It also includes an implementation for all of the system calls, including the file system, the dynamic loader, and any logic to expose additional functionality from the JavaScript runtime to C code.</p> <p>The <code class="docutils literal notranslate"><span class="pre">.mjs</span></code> file exports a single <code class="docutils literal notranslate"><span class="pre">bootstrapEmscriptenExecutable()</span></code> JavaScript function that bootstraps the runtime, calls the <code class="docutils literal notranslate"><span class="pre">main()</span></code> function, and returns an API object that can be used to call C functions. Each time it is called produces a complete and independent copy of the runtime with its own separate address space.</p> <p>The <code class="docutils literal notranslate"><span class="pre">bootstrapEmscriptenExecutable()</span></code> takes a large number of runtime settings. <a class="reference external" href="https://emscripten.org/docs/api_reference/module.html#id3">The full list is described in the Emscripten documentation here.</a> The most important of these are as follows:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">thisProgram</span></code>: The value of <code class="docutils literal notranslate"><span class="pre">argv[0]</span></code>. In Python, this makes its way into <code class="docutils literal notranslate"><span class="pre">sys.executable</span></code>.</li> <li><code class="docutils literal notranslate"><span class="pre">arguments</span></code>: The list of string arguments to be passed to <code class="docutils literal notranslate"><span class="pre">main()</span></code>.</li> <li><code class="docutils literal notranslate"><span class="pre">preRun</span></code>: A list of callbacks which are invoked after the JavaScript runtime and file system have been bootstrapped but before calling <code class="docutils literal notranslate"><span class="pre">main()</span></code>. Useful to set up the file system, environment variables, and standard streams.</li> <li><code class="docutils literal notranslate"><span class="pre">print</span></code> / <code class="docutils literal notranslate"><span class="pre">printErr</span></code> : Initial handlers for stdout and stderr. They are line buffered and performing a <code class="docutils literal notranslate"><span class="pre">flush()</span></code> of a partial line forces an extra new line. If tty-like behavior is desired, the standard stream devices should be replaced in a <code class="docutils literal notranslate"><span class="pre">preRun()</span></code> hook.</li> <li><code class="docutils literal notranslate"><span class="pre">onExit</span></code>: A handler that is called when the runtime exits.</li> <li><code class="docutils literal notranslate"><span class="pre">instantiateWasm</span></code>: A callback that is called to instantiate the WebAssembly module. Overriding the WebAssembly instantiation procedure via this function is useful when you have other custom asynchronous startup actions or downloads that can be performed in parallel to WebAssembly compilation. Implementing this callback allows performing all of these in parallel.</li> </ul> </section> <section id="file-system-setup"> <h3><a class="toc-backref" href="#file-system-setup" role="doc-backlink">File System Setup</a></h3> <section id="the-standard-library"> <h4><a class="toc-backref" href="#the-standard-library" role="doc-backlink">The Standard Library</a></h4> <p>In order for Python to run, it needs access to the standard library in the Emscripten file system. There are several possible approaches to this:</p> <ul class="simple"> <li>The Emscripten linker has a <code class="docutils literal notranslate"><span class="pre">--preload-file</span></code> flag that will automatically handle loading files. <a class="reference external" href="https://emscripten.org/docs/porting/files/packaging_files.html#packaging-using-emcc">Information about how it works is available here.</a> This is the simplest approach, but Pyodide has moved away from it because it embeds the files into a custom archive format that cannot be processed with standard tooling.</li> <li>For Node.js, use the NODEFS to mount a native directory with the files into the Emscripten file system. This is the most efficient option but is Node only. It is closely analogous to what <a class="reference external" href="https://wasi.dev/">WASI</a> does.</li> <li>Put the standard library into a zip archive and use <code class="docutils literal notranslate"><span class="pre">ZipImporter</span></code>. Using an uncompressed zip file allows the web server and client to apply better compression to the standard library itself. It also uses the more efficient native decompression algorithms of the browser rather than less efficient WebAssembly decompression. The disadvantages of this are a higher memory footprint and breaking <a class="reference external" href="https://docs.python.org/3/library/inspect.html#module-inspect" title="(in Python v3.13)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">inspect</span></code></a> & various tests that do not expect the standard library to be packaged in this way.</li> <li>Put the standard library into an uncompressed tar archive and mount it into a TARFS read only file system backed by the tar file. This has the best memory usage, runtime performance, and transfer size of the options that can be used in the browser. The disadvantage is that Emscripten does not itself include a TARFS so it requires a downstream implementation.</li> </ul> <p>Pyodide uses the <code class="docutils literal notranslate"><span class="pre">ZipImporter</span></code> approach in every runtime. Python uses the NODEFS approach when run with node and the <code class="docutils literal notranslate"><span class="pre">ZipImporter</span></code> approach for the web example. We will continue with this approach.</p> <p>The <code class="docutils literal notranslate"><span class="pre">ZipImporter</span></code> provides a clean resolution for a bootstrapping problem: the Python runtime is capable of unpacking a wide variety of archive formats, but the Python runtime is not ready to use until the standard library is already available. Since <code class="docutils literal notranslate"><span class="pre">zipimport.py</span></code> is a frozen module, it avoids these problems. All of the other approaches solve the bootstrapping problem by setting up the standard library using JavaScript.</p> </section> <section id="third-party-packages"> <h4><a class="toc-backref" href="#third-party-packages" role="doc-backlink">Third-party packages</a></h4> <p>It is also necessary to make any needed packages available in the Emscripten file system. Currently Emscripten CPython has no support for packages. Pyodide uses two different approaches for packages:</p> <ul class="simple"> <li>In the browser, Pyodide downloads and unpacks wheels into the MEMFS site-packages directory. It then preloads all dynamic libraries in the wheel. The work of downloading and installing all the packages is redone every time the runtime starts.</li> <li>The Pyodide <code class="docutils literal notranslate"><span class="pre">python</span></code> CLI entrypoint mounts all of the host file system as NODEFS directories before it bootstraps Python. This allows the normal virtual environment mechanism to work. Pyodide virtual environments contain a patched copy of pip and a custom <code class="docutils literal notranslate"><span class="pre">pip.conf</span></code> so that pip will install Pyodide wheels. On startup the Pyodide <code class="docutils literal notranslate"><span class="pre">python</span></code> CLI will preload all Emscripten dynamic libraries that are in the site-packages directory.</li> </ul> </section> </section> <section id="console-and-interactive-usage"> <h3><a class="toc-backref" href="#console-and-interactive-usage" role="doc-backlink">Console and Interactive Usage</a></h3> <p><code class="docutils literal notranslate"><span class="pre">stdin</span></code> defaults to always returning <code class="docutils literal notranslate"><span class="pre">EOF</span></code>, while <code class="docutils literal notranslate"><span class="pre">stdout</span></code> and <code class="docutils literal notranslate"><span class="pre">stderr</span></code> default to calling <code class="docutils literal notranslate"><span class="pre">console.log</span></code> and <code class="docutils literal notranslate"><span class="pre">console.error</span></code> respectively. It is possible to pass handlers to <code class="docutils literal notranslate"><span class="pre">bootstrapEmscriptenExecutable()</span></code> to configure the standard streams, but no matter what the I/O devices have undesirable line buffering behavior that forces a new line when flushed. To implement a well behaved TTY in-browser, it is necessary to remove the default I/O devices and replace them in a <code class="docutils literal notranslate"><span class="pre">preRun</span></code> hook.</p> <p>Making <code class="docutils literal notranslate"><span class="pre">stdin</span></code> work correctly in the browser poses an additional challenge because it is not allowed to block for user input in the main thread of the browser. If Emscripten is run in a web worker and served with the shared memory headers, it is possible to receive input using shared memory and atomics. It is also possible for a <code class="docutils literal notranslate"><span class="pre">stdin</span></code> device to block in a simpler and more efficient manner using stack switching using the experimental JavaScript Promise Integration API.</p> <p>Pyodide replaces the standard I/O devices in order to fix the line buffering behavior. When Pyodide is run in Node.js, <code class="docutils literal notranslate"><span class="pre">stdin</span></code>, <code class="docutils literal notranslate"><span class="pre">stdout</span></code>, and <code class="docutils literal notranslate"><span class="pre">stderr</span></code> are by default connected to <code class="docutils literal notranslate"><span class="pre">process.stdin</span></code>, <code class="docutils literal notranslate"><span class="pre">process.stdout</span></code>, and <code class="docutils literal notranslate"><span class="pre">process.stderr</span></code> and so the standard streams work as a tty out of the box. Pyodide also ensures that <code class="docutils literal notranslate"><span class="pre">shutil.get_terminal_size</span></code> returns results consistent with <code class="docutils literal notranslate"><span class="pre">process.stdout.rows</span></code> and <code class="docutils literal notranslate"><span class="pre">process.stdout.columns</span></code>. Pyodide currently has no support for stack switching <code class="docutils literal notranslate"><span class="pre">stdin</span></code>.</p> <p>Currently, the Emscripten Python Node.js runner uses the default I/O that Emscripten provides. The web example uses <code class="docutils literal notranslate"><span class="pre">Atomics</span></code> for <code class="docutils literal notranslate"><span class="pre">stdin</span></code> and has custom <code class="docutils literal notranslate"><span class="pre">stdout</span></code> and <code class="docutils literal notranslate"><span class="pre">stderr</span></code> handlers, but they exhibit the undesirable line buffering behavior. We will upstream the standard streams behaviors from Pyodide.</p> <p>In the long term, we hope to implement stack switching <code class="docutils literal notranslate"><span class="pre">stdin</span></code> devices, but that is out of scope for this PEP.</p> </section> <section id="dynamic-libraries"> <h3><a class="toc-backref" href="#dynamic-libraries" role="doc-backlink">Dynamic Libraries</a></h3> <section id="main-thread-synchronous-loading-limit"> <h4><a class="toc-backref" href="#main-thread-synchronous-loading-limit" role="doc-backlink">Main Thread Synchronous Loading Limit</a></h4> <p>In the main browser thread, a dynamic library can only be loaded synchronously if it is at most 4 kilobytes. This excludes most nontrivial dynamic libraries. This limit is not present in Node.js and can be avoided by using a web worker. If stack switching is available, then it is possible to make <code class="docutils literal notranslate"><span class="pre">dlopen()</span></code> stack switch in order to instantiate a dynamic library synchronously.</p> <p>To avoid the synchronous loading limit, Pyodide currently preloads all dynamic libraries present in a wheel when installing the wheel (or on startup). This is a significant disadvantage with packages like SciPy that include a very large number of shared libraries that are expected to be only loaded on demand. Pyodide will implement a solution based on stack switching as it becomes more widely available in runtimes.</p> <p>Emscripten Python only loads extension module dynamic libraries when they are imported. This approach is simpler and more efficient when it works. The web example runs in a web worker and the cli runner runs in Node so neither of these have the synchronous loading limit. We will continue with this approach in Emscripten Python.</p> <p>In the long run, we hope to implement a stack switching <code class="docutils literal notranslate"><span class="pre">dlopen</span></code>, but that is out of scope for this PEP.</p> </section> <section id="missing-rpath-support"> <h4><a class="toc-backref" href="#missing-rpath-support" role="doc-backlink">Missing RPATH Support</a></h4> <p>Another important limitation of the Emscripten dynamic loader is that it does not currently have RPATH support. Pyodide’s present workaround is as follows: <code class="docutils literal notranslate"><span class="pre">auditwheel-emscripten</span></code> places shared library dependencies that are vendored into a package in a <code class="docutils literal notranslate"><span class="pre">${package}.libs</span></code> folder, following auditwheel’s convention. Pyodide patches the dynamic loader to treat this <code class="docutils literal notranslate"><span class="pre">${package}.libs</span></code> folder as if it were on the RPATH of all of the dynamic libraries in the wheel.</p> <p>In Emscripten 4.0.5, we have updated the shared object file format, <code class="docutils literal notranslate"><span class="pre">wasm-ld</span></code> and <code class="docutils literal notranslate"><span class="pre">emcc</span></code> to accept an <code class="docutils literal notranslate"><span class="pre">-rpath</span></code> flag. We are still working on updating the dynamic loader to respect the rpath, but we expect this will be finished in the next Emscripten release. Pyodide will then switch to using the RPATH and drop the patch on the dynamic loader.</p> <p>Emscripten Python currently uses the unpatched dynamic loader and so cannot load extension modules that depend on vendored dynamic libraries via DT_NEEDED. Extension modules can load dynamic libraries via DT_NEEDED if they are in the system <code class="docutils literal notranslate"><span class="pre">lib</span></code> directory. We will wait to resolve this until we have fixed the Emscripten dynamic loader upstream. When Emscripten Python is built with a compatible version of Emscripten, it will automatically pick up support for wheels with vendored dynamic libraries.</p> </section> </section> <section id="traps-and-uncaught-exceptions"> <h3><a class="toc-backref" href="#traps-and-uncaught-exceptions" role="doc-backlink">Traps and Uncaught Exceptions</a></h3> <p>We consider the C runtime state to be corrupted if there is a WebAssembly trap, an unhandled JavaScript exception, or an uncaught WebAssembly throw instruction.</p> <p>Unlike in other platforms, there is no operating system to shut down the executable when there is a trap or other unrecoverable corruption of the libc runtime. We need to provide our own code to print tracebacks, dump the memory, or do whatever else is helpful for debugging a crash. If we expose a JavaScript API, we also must ensure that it is disabled after an unrecoverable crash to prevent downstream users from observing the Python runtime in an inconsistent state.</p> <p>In order to detect fatal errors, Pyodide uses the following approach: all fallable calls from WebAssembly into JavaScript are wrapped with a JavaScript try/catch block. Any caught JavaScript exceptions are translated into Python exceptions. This ensures that any recoverable JavaScript error is caught before it unwinds through any WebAssembly frames. All entrypoints to WebAssembly are also wrapped with JavaScript try/catch blocks. Any exceptions caught there have unwound WebAssembly frames and are thus considered to be fatal errors (though there is a special case to handle <a class="reference external" href="https://docs.python.org/3/library/sys.html#sys.exit" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">exit()</span></code></a>). This requires foundational integration with the Python/JavaScript foreign function interface.</p> <p>When the Pyodide runtime catches a fatal exception, it introspects the error to determine whether it came from a trap, a logic error in a system call, a <code class="docutils literal notranslate"><span class="pre">setjmp()</span></code> without a <code class="docutils literal notranslate"><span class="pre">longjmp()</span></code>, or a libcxxabi call to <code class="docutils literal notranslate"><span class="pre">__cxa_throw()</span></code> (an uncaught C++ exception or Rust panic). We render as informative an error message as we can. We also call <code class="docutils literal notranslate"><span class="pre">_Py_DumpTraceback()</span></code> so we can display a Python traceback in addition to the JS/WebAssembly traceback. It also disables the JavaScript API so that further attempts to call into Python result in an error saying that the runtime has fatally failed.</p> <p>Normally, WebAssembly symbols are stripped so the WebAssembly frames are not very useful. Compiling and linking with <code class="docutils literal notranslate"><span class="pre">-g2</span></code> (or a higher debug setting) ensures that WebAssembly symbols are included and they will appear in the traceback.</p> <p>Because Emscripten Python currently has no JavaScript API and no foreign function interface, the situation is much simpler. The Python Node.js runner wraps the call to <code class="docutils literal notranslate"><span class="pre">bootstrapEmscriptenExecutable()</span></code> in a try/catch block. If an exception is caught, it displays the JavaScript exception and calls <code class="docutils literal notranslate"><span class="pre">_Py_DumpTraceback()</span></code>. It then exits with code 1. We will stick with this approach until we add either a JavaScript API or foreign function interface, which is out of scope for this PEP.</p> </section> </section> <section id="specification"> <h2><a class="toc-backref" href="#specification" role="doc-backlink">Specification</a></h2> <section id="scope-of-work"> <h3><a class="toc-backref" href="#scope-of-work" role="doc-backlink">Scope of Work</a></h3> <p>Adding Emscripten as a Tier 3 platform only requires adding support for compiling an Emscripten-compatible build from the unpatched CPython source code. It does not necessarily require there to be any officially distributed Emscripten artifacts on python.org, although these could be added in the future. In the short term, they will continue to be distributed downstream with Pyodide.</p> <p>Emscripten will be built using the same configure and Makefile system as other POSIX platforms, and must therefore be built on a POSIX platform. Both Linux and macOS will be supported.</p> <p>A Python CLI entrypoint will be provided, which among other things can be used to execute the test suite.</p> </section> <section id="linkage"> <h3><a class="toc-backref" href="#linkage" role="doc-backlink">Linkage</a></h3> <p>It is only supported to statically link the Python interpreter. We use <a class="reference external" href="https://emscripten.org/docs/api_reference/emscripten.h.html#c.EM_JS">EM_JS</a> functions in the interpreter for various purposes. It is possible to dynamically link object files that include <code class="docutils literal notranslate"><span class="pre">EM_JS</span></code> functions, but their behavior deviates significantly from their behavior in static builds. For this reason, it would require special work to support. If a use case for dynamically linking the interpreter in Emscripten emerges, we can evaluate how much effort would be required to support it.</p> </section> <section id="standard-library"> <h3><a class="toc-backref" href="#standard-library" role="doc-backlink">Standard Library</a></h3> <section id="unsupported-modules"> <h4><a class="toc-backref" href="#unsupported-modules" role="doc-backlink">Unsupported Modules</a></h4> <p>See <a class="reference external" href="https://pyodide.org/en/stable/usage/wasm-constraints.html#removed-modules">https://pyodide.org/en/stable/usage/wasm-constraints.html#removed-modules</a>.</p> <section id="removed-modules"> <h5><a class="toc-backref" href="#removed-modules" role="doc-backlink">Removed Modules</a></h5> <p>The following modules are removed from the standard library to reduce download size and since they currently wouldn’t work in the WebAssembly VM.</p> <ul class="simple"> <li>curses</li> <li>dbm</li> <li>ensurepip</li> <li>fcntl</li> <li>grp</li> <li>idlelib</li> <li>msvcrt</li> <li>pwd</li> <li>resource</li> <li>syslog</li> <li>termios</li> <li>tkinter</li> <li>turtle</li> <li>turtledemo</li> <li>venv</li> <li>winreg</li> <li>winsound</li> </ul> </section> <section id="included-but-not-working-modules"> <h5><a class="toc-backref" href="#included-but-not-working-modules" role="doc-backlink">Included but not Working Modules</a></h5> <p>The following modules can be imported, but are not functional:</p> <ul class="simple"> <li>multiprocessing</li> <li>threading</li> <li>sockets</li> </ul> <p>as well as any functionality that requires these.</p> <p>The following are present but cannot be imported due to a dependency on the termios package which has been removed:</p> <ul class="simple"> <li>pty</li> <li>tty</li> </ul> </section> </section> <section id="platform-identification"> <h4><a class="toc-backref" href="#platform-identification" role="doc-backlink">Platform Identification</a></h4> <p><code class="docutils literal notranslate"><span class="pre">sys.platform</span></code> will return <code class="docutils literal notranslate"><span class="pre">"emscripten"</span></code>. Although Emscripten attempts to be compatible with Linux, the differences are significant enough that a distinct name is justified. This is consistent with the return value from <code class="docutils literal notranslate"><span class="pre">os.uname()</span></code>.</p> <p>There is also <code class="docutils literal notranslate"><span class="pre">sys._emscripten_info</span></code> which includes the Emscripten version and the runtime (either <code class="docutils literal notranslate"><span class="pre">navigator.userAgent</span></code> in a browser or <code class="docutils literal notranslate"><span class="pre">"Node</span> <span class="pre">js"</span> <span class="pre">+</span> <span class="pre">process.version</span></code> in Node.js).</p> </section> </section> <section id="signals-support"> <h3><a class="toc-backref" href="#signals-support" role="doc-backlink">Signals Support</a></h3> <p>WebAssembly does not have native support for signals. Furthermore, on a non-pthreads build, the address space of the WebAssembly module is not shared, so it is impossible for any thread capable of seeing an interrupt to write to the eval breaker while the Python interpreter is running code. To work around this, there are two possible solutions:</p> <ul class="simple"> <li>If Emscripten is run in a web worker and served with the shared memory headers, it is possible to use shared memory outside of the WebAssembly address space as a signal buffer. A signal handling UI thread can write the desired signal into the signal buffer. The interpreter can periodically check the state of this signal buffer in the eval breaker code. Checking the signal buffer is slow compared to checking the eval breaker in native platforms, so we do only do it once every 50 times through the eval breaker. See <a class="reference external" href="https://github.com/python/cpython/blob/2bef8ea8ea045d20394f0daec7a5c5b1046a4e22/Python/emscripten_signal.c">Python/emscripten_signal.c</a></li> <li>Using stack switching, we can occasionally switch the stack and allow the JavaScript event loop to go around, then check the state of a signal buffer. This requires the experimental JavaScript Promise Integration API, and would be best used with the techniques for optimizing long tasks described <a class="reference external" href="https://web.dev/articles/optimize-long-tasks">in this article</a></li> </ul> <p>Emscripten Python has already implemented the solution based on shared memory, and it is in use in Pyodide.</p> <p>Eventually, we hope to implement stack-switching-based signals so that it is possible to use signals in the main thread of node and the browser, as well as in in web pages that are not served with the shared memory headers. We will need to keep the shared memory based approach as well, both for backwards compatibility and because it is more efficient when it is possible. However, this is out of scope for this PEP.</p> </section> <section id="function-pointer-casts"> <h3><a class="toc-backref" href="#function-pointer-casts" role="doc-backlink">Function Pointer Casts</a></h3> <p><a class="reference external" href="https://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf#page=60">Section 6.3.2.3, paragraph 8</a> of the C standard reads:</p> <blockquote> <div>A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.</div></blockquote> <p>However, most platforms have the same behavior: if a function is called with too many arguments, the extra arguments are ignored; if a function is called with too few arguments, the extra arguments are filled in with garbage.</p> <p>On the other hand, the WebAssembly spec defines calling a function with the wrong signature to trap (<a class="reference external" href="https://webassembly.github.io/spec/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-call-indirect-x-y)">see step 18 in the execution of call_indirect</a>.</p> <p>It is common for Python extension modules to cast a function to a different signature and call it with the different signature. For instance, many C extensions define a <code class="docutils literal notranslate"><span class="pre">METH_NOARGS</span></code> function to take 0 or 1 argument. The interpreter calls it with two arguments, the first of which is the Python module object and the second of which is always <code class="docutils literal notranslate"><span class="pre">NULL</span></code>. In order to make these extension modules work without changing their source code, we need special handling.</p> <p>Initially, we resolved this problem by calling out to JavaScript and having JavaScript call the function pointer. When calling a WebAssembly function from JavaScript, missing arguments are treated as zero and extra arguments are ignored (<a class="reference external" href="https://webassembly.github.io/spec/js-api/index.html#call-an-exported-function)">see step 7 here</a>. This works, but has the disadvantage of being slow and breaking stack switching – it is not possible to stack switch through JavaScript frames.</p> <p>Using the wasm-gc <a class="reference external" href="https://webassembly.github.io/gc/core/exec/instructions.html#xref-syntax-instructions-syntax-instr-ref-mathsf-ref-test-mathit-rt">ref.test</a> instruction, we can query the type of the function pointer and manually fix up the argument list.</p> <p>wasm-gc is a relatively new feature for WebAssembly runtimes, so we attempt to use a wasm-gc based function pointer cast trampoline if possible and fall back to a JS trampoline if not. Every JavaScript runtime that supports stack switching also supports wasm-gc, so this ensures that stack switching works on every platform runtime that supports it. The one wrinkle is that iOS 18 ships a broken implementation of wasm-gc so we have to special case it.</p> <p><a class="reference external" href="https://github.com/python/cpython/blob/98fa4a49fecbac3c990a25ce5d300592dad31be0/Python/emscripten_trampoline.c">See here for the full implementation details.</a></p> <p>The function pointer cast handling is fully implemented in cpython. Pyodide uses exactly the same code as upstream.</p> </section> <section id="ci-resources"> <h3><a class="toc-backref" href="#ci-resources" role="doc-backlink">CI Resources</a></h3> <p>Pyodide can be built and tested on any Linux with a reasonably recent version of Node.js. Anaconda has offered to provide physical hardware to run Emscripten buildbots, maintained by Russell Keith-Magee.</p> <p>CPython does not currently test Tier 3 platforms on GitHub Actions, but if this ever changes, their Linux runners are able to build and test Emscripten Python.</p> </section> <section id="packaging"> <h3><a class="toc-backref" href="#packaging" role="doc-backlink">Packaging</a></h3> <section id="existing-package-support"> <h4><a class="toc-backref" href="#existing-package-support" role="doc-backlink">Existing Package Support</a></h4> <p>Pyodide currently maintains ports of 255 different packages at the time of this writing, including major scientific Python packages like NumPy, SciPy, pandas, Polars, scikit-learn, OpenCV, PyArrow, and Pillow as well as general purpose packages like aiohttp, Requests, Pydantic, cryptography, and orjson.</p> <p>About 60 packages are also testing against Pyodide in their CI, including NumPy, pandas, awkward-cpp, scikit-image, statsmodels, PyArrow, Hypothesis, and PyO3.</p> </section> <section id="emscripten-wheel-format"> <h4><a class="toc-backref" href="#emscripten-wheel-format" role="doc-backlink">Emscripten Wheel Format</a></h4> <p>Emscripten wheels will use either the format <code class="docutils literal notranslate"><span class="pre">emscripten_<version>_wasm32</span></code> or <code class="docutils literal notranslate"><span class="pre">pyodide_<abi>_wasm32</span></code>. For example:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">emscripten_3_1_58_wasm32</span></code></li> <li><code class="docutils literal notranslate"><span class="pre">pyodide_2025_0_wasm32</span></code></li> </ul> <p>The first triple is ambiguous, since even with Emscripten 3.1.58 it is possible to link dynamic libraries that require a large number of distinct ABIs, depending on linker and compiler options. It is our intent that the <code class="docutils literal notranslate"><span class="pre">pyodide_2025_0</span></code> specifies the particular ABI. Thus, the relationship between <code class="docutils literal notranslate"><span class="pre">pyodide_<abi></span></code> and <code class="docutils literal notranslate"><span class="pre">emscripten_<version></span></code> is intended to be the same as the relationship between <code class="docutils literal notranslate"><span class="pre">manylinux<version></span></code> and <code class="docutils literal notranslate"><span class="pre">linux</span></code>.</p> <p>The specification of the <code class="docutils literal notranslate"><span class="pre">pyodide_<abi></span></code> ABI includes:</p> <ul class="simple"> <li>Which version of the Emscripten compiler is used</li> <li>What libraries are statically linked with the interpreter</li> <li>What stack unwinding ABI is to be used</li> <li>Which runtime platform features are required to be present</li> </ul> <p>and a handful of other similar details that affect the ABI.</p> <p>The ABI is selected by choosing the appropriate version of the Emscripten compiler and passing appropriate compiler and linker flags. It is possible for other people to build their own Python interpreter that is compatible with the Pyodide ABI, it is not necessary to use the Pyodide distribution itself.</p> <p>The <code class="docutils literal notranslate"><span class="pre">pyodide</span> <span class="pre">build</span></code> tool knows how to create wheels that match our ABI. As an alternative, <a class="reference external" href="https://github.com/ryanking13/auditwheel-emscripten">the auditwheel-emscripten tool</a> is capable of performing basic compatibility checks, vendoring shared libraries, and retagging the wheel from <code class="docutils literal notranslate"><span class="pre">emscripten_<version></span></code> to <code class="docutils literal notranslate"><span class="pre">pyodide_<abi></span></code>. Unlike with manylinux, there is no need for a Docker container to build the <code class="docutils literal notranslate"><span class="pre">pyodide_<abi></span></code> wheels. All that is needed is a Linux machine and appropriate versions of Python, Node.js, and Emscripten.</p> </section> </section> <section id="pep-11"> <h3><a class="toc-backref" href="#pep-11" role="doc-backlink">PEP 11</a></h3> <p><a class="pep reference internal" href="../pep-0011/" title="PEP 11 – CPython platform support">PEP 11</a> will be updated to indicate that Emscripten is supported, specifically the triples <code class="docutils literal notranslate"><span class="pre">wasm32-unknown-emscripten_xx_xx_xx</span></code>.</p> <p>Russell Keith-Magee will serve as the initial core team contact for these ABIs.</p> </section> </section> <section id="future-work"> <h2><a class="toc-backref" href="#future-work" role="doc-backlink">Future Work</a></h2> <section id="improving-cross-builds-in-the-packaging-ecosystem"> <h3><a class="toc-backref" href="#improving-cross-builds-in-the-packaging-ecosystem" role="doc-backlink">Improving Cross Builds in the Packaging Ecosystem</a></h3> <p>Python now supports four non-self-hosting platforms: iOS, Android, WASI, and Emscripten. All of them will need to build packages via cross builds. Currently, <code class="docutils literal notranslate"><span class="pre">pyodide-build</span></code> allows building a very large number of Python packages for Emscripten, but it is very complicated. Ideally, the Python packaging ecosystem would have standards for cross builds. This is a difficult long term project, particularly because the packaging system is complex and was designed from the ground up with the assumption that cross compilation would not happen.</p> </section> <section id="pyodide-runtime-features-to-be-upstreamed"> <h3><a class="toc-backref" href="#pyodide-runtime-features-to-be-upstreamed" role="doc-backlink">Pyodide Runtime Features to be Upstreamed</a></h3> <p>This is a collection of Pyodide runtime features that are out of scope for this PEP and for the Python 3.14 development cycle but we would like to upstream in the future.</p> <section id="javascript-api-for-bootstrapping"> <h4><a class="toc-backref" href="#javascript-api-for-bootstrapping" role="doc-backlink">JavaScript API for Bootstrapping</a></h4> <p>Currently we offer no stable API for bootstrapping Python. Instead, we use <a class="reference external" href="https://github.com/python/cpython/blob/98fa4a49fecbac3c990a25ce5d300592dad31be0/Tools/wasm/emscripten/node_entry.mjs#L33-L46">one collection of settings for the Node.js CLI entrypoint</a> and <a class="reference external" href="https://github.com/python/cpython/blob/98fa4a49fecbac3c990a25ce5d300592dad31be0/Tools/wasm/emscripten/web_example/python.worker.mjs#L64-L88">a separate collection of settings for the browser demo</a>.</p> <p>The Emscripten executable startup API is complicated and there are many possible configurations that are broken. Pyodide offers a simpler set of options than Emscripten. This gives downstream users a lot of flexibility while allowing us to maintain a small number of tested configurations. It also reduces downstream code duplication.</p> <p>Eventually, we would like to upstream Pyodide’s bootstrapping API. In the short term, to keep things simple we will support no JavaScript API.</p> </section> <section id="javascript-foreign-function-interface-ffi"> <h4><a class="toc-backref" href="#javascript-foreign-function-interface-ffi" role="doc-backlink">JavaScript foreign function interface (FFI)</a></h4> <p>Because Emscripten supports POSIX, a significant number of tasks can be achieved using the <code class="docutils literal notranslate"><span class="pre">os</span></code> module. However, many fundamental operations in JavaScript runtimes are not possible via POSIX APIs. Pyodide’s approach is to specify a mapping between the JavaScript object model and the Python object model and a calling convention that allows high level bidirectional integration.</p> </section> <section id="asyncio"> <h4><a class="toc-backref" href="#asyncio" role="doc-backlink">Asyncio</a></h4> <p>Most JavaScript primitives are asynchronous. The JavaScript thread that Python runs in already has an event loop. It it not too difficult to implement a Python event loop that defers all actual work to the JavaScript event loop, <a class="reference external" href="https://github.com/pyodide/pyodide/blob/b3721fd5e9c7981216c4604025e2617e53f9726a/src/py/pyodide/webloop.py">implemented in Pyodide here</a>.</p> <p>This is logically dependent on having at least some limited JavaScript FFI because the only way to schedule tasks on the JavaScript event loop is via a call out to JavaScript.</p> <p>One cause of incompatibility is that it is not possible to control the life cycle of the event loop from within a JavaScript isolate. This makes <code class="docutils literal notranslate"><span class="pre">asyncio.run()</span></code> and similar things not work.</p> <p>Using stack switching it is also possible to make a coroutine out of “synchronous” Python frames. These stack switching coroutines are scheduled on the same event loop as ordinary Python coroutines and are fully reentrant. This is fully implemented in Pyodide.</p> </section> </section> </section> <section id="backwards-compatibility"> <h2><a class="toc-backref" href="#backwards-compatibility" role="doc-backlink">Backwards Compatibility</a></h2> <p>Adding a new platform does not introduce any backwards compatibility concerns to CPython itself. However, there may be some backwards compatibility implications on Pyodide users. There are a large number of existing users of Pyodide, so it is important when upstreaming features from Pyodide into Python that we take care to minimize backwards incompatibility. We will also need a way to disable partially-upstreamed features so that Pyodide can replace them with more complete versions downstream.</p> <p>These backwards compatibility concerns impact not just the runtime but also the packaging system.</p> </section> <section id="security-implications"> <h2><a class="toc-backref" href="#security-implications" role="doc-backlink">Security Implications</a></h2> <p>Adding a new platform does not add any new security implications.</p> <p>Emscripten and WASI are also the only supported platforms that offer sandboxing. If users wish to execute untrusted Python code or untrusted Python extension modules, Emscripten provides a secure way for them to do that.</p> </section> <section id="how-to-teach-this"> <h2><a class="toc-backref" href="#how-to-teach-this" role="doc-backlink">How to Teach This</a></h2> <p>The education needs related to this PEP relate to two groups of developers.</p> <p>First, web developers will need to know how to build Python and use it in a website, along with their own Python code and any supporting packages, and how to use them all at runtime. The documentation will cover this in a similar form to the existing Windows embeddable package. In the short term, we will encourage developers to use Pyodide if at all possible.</p> <p>Second, developers of packages with binary components need to know how to build and release them for Emscripten (see Packaging).</p> </section> <section id="reference-implementation"> <h2><a class="toc-backref" href="#reference-implementation" role="doc-backlink">Reference Implementation</a></h2> <p>Pyodide.</p> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.</p> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0776.rst">https://github.com/python/peps/blob/main/peps/pep-0776.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0776.rst">2025-03-19 11:10:40 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#goals">Goals</a><ul> <li><a class="reference internal" href="#runtime-goals">Runtime Goals</a></li> <li><a class="reference internal" href="#packaging-goals">Packaging Goals</a></li> </ul> </li> <li><a class="reference internal" href="#emscripten-platform-information">Emscripten Platform Information</a><ul> <li><a class="reference internal" href="#background-on-emscripten">Background on Emscripten</a></li> <li><a class="reference internal" href="#posix-compliance">POSIX Compliance</a></li> <li><a class="reference internal" href="#emscripten-abi-compatibility">Emscripten ABI Compatibility</a></li> <li><a class="reference internal" href="#development-tools">Development Tools</a></li> <li><a class="reference internal" href="#emscripten-application-lifecycle">Emscripten Application Lifecycle</a></li> <li><a class="reference internal" href="#file-system-setup">File System Setup</a><ul> <li><a class="reference internal" href="#the-standard-library">The Standard Library</a></li> <li><a class="reference internal" href="#third-party-packages">Third-party packages</a></li> </ul> </li> <li><a class="reference internal" href="#console-and-interactive-usage">Console and Interactive Usage</a></li> <li><a class="reference internal" href="#dynamic-libraries">Dynamic Libraries</a><ul> <li><a class="reference internal" href="#main-thread-synchronous-loading-limit">Main Thread Synchronous Loading Limit</a></li> <li><a class="reference internal" href="#missing-rpath-support">Missing RPATH Support</a></li> </ul> </li> <li><a class="reference internal" href="#traps-and-uncaught-exceptions">Traps and Uncaught Exceptions</a></li> </ul> </li> <li><a class="reference internal" href="#specification">Specification</a><ul> <li><a class="reference internal" href="#scope-of-work">Scope of Work</a></li> <li><a class="reference internal" href="#linkage">Linkage</a></li> <li><a class="reference internal" href="#standard-library">Standard Library</a><ul> <li><a class="reference internal" href="#unsupported-modules">Unsupported Modules</a><ul> <li><a class="reference internal" href="#removed-modules">Removed Modules</a></li> <li><a class="reference internal" href="#included-but-not-working-modules">Included but not Working Modules</a></li> </ul> </li> <li><a class="reference internal" href="#platform-identification">Platform Identification</a></li> </ul> </li> <li><a class="reference internal" href="#signals-support">Signals Support</a></li> <li><a class="reference internal" href="#function-pointer-casts">Function Pointer Casts</a></li> <li><a class="reference internal" href="#ci-resources">CI Resources</a></li> <li><a class="reference internal" href="#packaging">Packaging</a><ul> <li><a class="reference internal" href="#existing-package-support">Existing Package Support</a></li> <li><a class="reference internal" href="#emscripten-wheel-format">Emscripten Wheel Format</a></li> </ul> </li> <li><a class="reference internal" href="#pep-11">PEP 11</a></li> </ul> </li> <li><a class="reference internal" href="#future-work">Future Work</a><ul> <li><a class="reference internal" href="#improving-cross-builds-in-the-packaging-ecosystem">Improving Cross Builds in the Packaging Ecosystem</a></li> <li><a class="reference internal" href="#pyodide-runtime-features-to-be-upstreamed">Pyodide Runtime Features to be Upstreamed</a><ul> <li><a class="reference internal" href="#javascript-api-for-bootstrapping">JavaScript API for Bootstrapping</a></li> <li><a class="reference internal" href="#javascript-foreign-function-interface-ffi">JavaScript foreign function interface (FFI)</a></li> <li><a class="reference internal" href="#asyncio">Asyncio</a></li> </ul> </li> </ul> </li> <li><a class="reference internal" href="#backwards-compatibility">Backwards Compatibility</a></li> <li><a class="reference internal" href="#security-implications">Security Implications</a></li> <li><a class="reference internal" href="#how-to-teach-this">How to Teach This</a></li> <li><a class="reference internal" href="#reference-implementation">Reference Implementation</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0776.rst">Page Source (GitHub)</a> </nav> </section> <script src="../_static/colour_scheme.js"></script> <script src="../_static/wrap_tables.js"></script> <script src="../_static/sticky_banner.js"></script> </body> </html>