1# Python WebAssembly (WASM) build 2 3**WASI support is [tier 2](https://peps.python.org/pep-0011/#tier-2).** 4**Emscripten is NOT officially supported as of Python 3.13.** 5 6This directory contains configuration and helpers to facilitate cross 7compilation of CPython to WebAssembly (WASM). Python supports Emscripten 8(*wasm32-emscripten*) and WASI (*wasm32-wasi*) targets. Emscripten builds 9run in modern browsers and JavaScript runtimes like *Node.js*. WASI builds 10use WASM runtimes such as *wasmtime*. 11 12Users and developers are encouraged to use the script 13`Tools/wasm/wasm_build.py`. The tool automates the build process and provides 14assistance with installation of SDKs, running tests, etc. 15 16**NOTE**: If you are looking for information that is not directly related to 17building CPython for WebAssembly (or the resulting build), please see 18https://github.com/psf/webassembly for more information. 19 20## wasm32-emscripten 21 22### Build 23 24For now the build system has two target flavors. The ``Emscripten/browser`` 25target (``--with-emscripten-target=browser``) is optimized for browsers. 26It comes with a reduced and preloaded stdlib without tests and threading 27support. The ``Emscripten/node`` target has threading enabled and can 28access the file system directly. 29 30Cross compiling to the wasm32-emscripten platform needs the 31[Emscripten](https://emscripten.org/) SDK and a build Python interpreter. 32Emscripten 3.1.19 or newer are recommended. All commands below are relative 33to a repository checkout. 34 35#### Toolchain 36 37##### Container image 38 39Christian Heimes maintains a container image with Emscripten SDK, Python 40build dependencies, WASI-SDK, wasmtime, and several additional tools. 41 42From within your local CPython repo clone, run one of the following commands: 43 44``` 45# Fedora, RHEL, CentOS 46podman run --rm -ti -v $(pwd):/python-wasm/cpython:Z -w /python-wasm/cpython quay.io/tiran/cpythonbuild:emsdk3 47 48# other 49docker run --rm -ti -v $(pwd):/python-wasm/cpython -w /python-wasm/cpython quay.io/tiran/cpythonbuild:emsdk3 50``` 51 52##### Manually 53 54###### Install [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) 55 56**NOTE**: Follow the on-screen instructions how to add the SDK to ``PATH``. 57 58```shell 59git clone https://github.com/emscripten-core/emsdk.git /opt/emsdk 60/opt/emsdk/emsdk install latest 61/opt/emsdk/emsdk activate latest 62``` 63 64###### Optionally: enable ccache for EMSDK 65 66The ``EM_COMPILER_WRAPPER`` must be set after the EMSDK environment is 67sourced. Otherwise the source script removes the environment variable. 68 69``` 70. /opt/emsdk/emsdk_env.sh 71EM_COMPILER_WRAPPER=ccache 72``` 73 74###### Optionally: pre-build and cache static libraries 75 76Emscripten SDK provides static builds of core libraries without PIC 77(position-independent code). Python builds with ``dlopen`` support require 78PIC. To populate the build cache, run: 79 80```shell 81. /opt/emsdk/emsdk_env.sh 82embuilder build zlib bzip2 MINIMAL_PIC 83embuilder --pic build zlib bzip2 MINIMAL_PIC 84``` 85 86 87### Compile and build Python interpreter 88 89From within the container, run the following command: 90 91```shell 92./Tools/wasm/wasm_build.py build 93``` 94 95The command is roughly equivalent to: 96 97```shell 98mkdir -p builddir/build 99pushd builddir/build 100../../configure -C 101make -j$(nproc) 102popd 103``` 104 105#### Cross-compile to wasm32-emscripten for browser 106 107```shell 108./Tools/wasm/wasm_build.py emscripten-browser 109``` 110 111The command is roughly equivalent to: 112 113```shell 114mkdir -p builddir/emscripten-browser 115pushd builddir/emscripten-browser 116 117CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \ 118 emconfigure ../../configure -C \ 119 --host=wasm32-unknown-emscripten \ 120 --build=$(../../config.guess) \ 121 --with-emscripten-target=browser \ 122 --with-build-python=$(pwd)/../build/python 123 124emmake make -j$(nproc) 125popd 126``` 127 128Serve `python.html` with a local webserver and open the file in a browser. 129Python comes with a minimal web server script that sets necessary HTTP 130headers like COOP, COEP, and mimetypes. Run the script outside the container 131and from the root of the CPython checkout. 132 133```shell 134./Tools/wasm/wasm_webserver.py 135``` 136 137and open http://localhost:8000/builddir/emscripten-browser/python.html . This 138directory structure enables the *C/C++ DevTools Support (DWARF)* to load C 139and header files with debug builds. 140 141 142#### Cross compile to wasm32-emscripten for node 143 144```shell 145./Tools/wasm/wasm_build.py emscripten-node-dl 146``` 147 148The command is roughly equivalent to: 149 150```shell 151mkdir -p builddir/emscripten-node-dl 152pushd builddir/emscripten-node-dl 153 154CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \ 155 emconfigure ../../configure -C \ 156 --host=wasm32-unknown-emscripten \ 157 --build=$(../../config.guess) \ 158 --with-emscripten-target=node \ 159 --enable-wasm-dynamic-linking \ 160 --with-build-python=$(pwd)/../build/python 161 162emmake make -j$(nproc) 163popd 164``` 165 166```shell 167node --experimental-wasm-threads --experimental-wasm-bulk-memory --experimental-wasm-bigint builddir/emscripten-node-dl/python.js 168``` 169 170(``--experimental-wasm-bigint`` is not needed with recent NodeJS versions) 171 172### Limitations and issues 173 174Emscripten before 3.1.8 has known bugs that can cause memory corruption and 175resource leaks. 3.1.8 contains several fixes for bugs in date and time 176functions. 177 178#### Network stack 179 180- Python's socket module does not work with Emscripten's emulated POSIX 181 sockets yet. Network modules like ``asyncio``, ``urllib``, ``selectors``, 182 etc. are not available. 183- Only ``AF_INET`` and ``AF_INET6`` with ``SOCK_STREAM`` (TCP) or 184 ``SOCK_DGRAM`` (UDP) are available. ``AF_UNIX`` is not supported. 185- ``socketpair`` does not work. 186- Blocking sockets are not available and non-blocking sockets don't work 187 correctly, e.g. ``socket.accept`` crashes the runtime. ``gethostbyname`` 188 does not resolve to a real IP address. IPv6 is not available. 189- The ``select`` module is limited. ``select.select()`` crashes the runtime 190 due to lack of exectfd support. 191 192#### processes, signals 193 194- Processes are not supported. System calls like fork, popen, and subprocess 195 fail with ``ENOSYS`` or ``ENOSUP``. 196- Signal support is limited. ``signal.alarm``, ``itimer``, ``sigaction`` 197 are not available or do not work correctly. ``SIGTERM`` exits the runtime. 198- Keyboard interrupt (CTRL+C) handling is not implemented yet. 199- Resource-related functions like ``os.nice`` and most functions of the 200 ``resource`` module are not available. 201 202#### threading 203 204- Threading is disabled by default. The ``configure`` option 205 ``--enable-wasm-pthreads`` adds compiler flag ``-pthread`` and 206 linker flags ``-sUSE_PTHREADS -sPROXY_TO_PTHREAD``. 207- pthread support requires WASM threads and SharedArrayBuffer (bulk memory). 208 The Node.JS runtime keeps a pool of web workers around. Each web worker 209 uses several file descriptors (eventfd, epoll, pipe). 210- It's not advised to enable threading when building for browsers or with 211 dynamic linking support; there are performance and stability issues. 212 213#### file system 214 215- Most user, group, and permission related function and modules are not 216 supported or don't work as expected, e.g.``pwd`` module, ``grp`` module, 217 ``os.setgroups``, ``os.chown``, and so on. ``lchown`` and ``lchmod`` are 218 not available. 219- ``umask`` is a no-op. 220- hard links (``os.link``) are not supported. 221- Offset and iovec I/O functions (e.g. ``os.pread``, ``os.preadv``) are not 222 available. 223- ``os.mknod`` and ``os.mkfifo`` 224 [don't work](https://github.com/emscripten-core/emscripten/issues/16158) 225 and are disabled. 226- Large file support crashes the runtime and is disabled. 227- ``mmap`` module is unstable. flush (``msync``) can crash the runtime. 228 229#### Misc 230 231- Heap memory and stack size are limited. Recursion or extensive memory 232 consumption can crash Python. 233- Most stdlib modules with a dependency on external libraries are missing, 234 e.g. ``ctypes``, ``readline``, ``ssl``, and more. 235- Shared extension modules are not implemented yet. All extension modules 236 are statically linked into the main binary. The experimental configure 237 option ``--enable-wasm-dynamic-linking`` enables dynamic extensions 238 supports. It's currently known to crash in combination with threading. 239- glibc extensions for date and time formatting are not available. 240- ``locales`` module is affected by musl libc issues, 241 [gh-90548](https://github.com/python/cpython/issues/90548). 242- Python's object allocator ``obmalloc`` is disabled by default. 243- ``ensurepip`` is not available. 244- Some ``ctypes`` features like ``c_longlong`` and ``c_longdouble`` may need 245 NodeJS option ``--experimental-wasm-bigint``. 246 247#### In the browser 248 249- The interactive shell does not handle copy 'n paste and unicode support 250 well. 251- The bundled stdlib is limited. Network-related modules, 252 multiprocessing, dbm, tests and similar modules 253 are not shipped. All other modules are bundled as pre-compiled 254 ``pyc`` files. 255- In-memory file system (MEMFS) is not persistent and limited. 256- Test modules are disabled by default. Use ``--enable-test-modules`` build 257 test modules like ``_testcapi``. 258 259### wasm32-emscripten in node 260 261Node builds use ``NODERAWFS``. 262 263- Node RawFS allows direct access to the host file system without need to 264 perform ``FS.mount()`` call. 265 266### wasm64-emscripten 267 268- wasm64 requires recent NodeJS and ``--experimental-wasm-memory64``. 269- ``EM_JS`` functions must return ``BigInt()``. 270- ``Py_BuildValue()`` format strings must match size of types. Confusing 32 271 and 64 bits types leads to memory corruption, see 272 [gh-95876](https://github.com/python/cpython/issues/95876) and 273 [gh-95878](https://github.com/python/cpython/issues/95878). 274 275### Hosting Python WASM builds 276 277The simple REPL terminal uses SharedArrayBuffer. For security reasons 278browsers only provide the feature in secure environments with cross-origin 279isolation. The webserver must send cross-origin headers and correct MIME types 280for the JavaScript and WASM files. Otherwise the terminal will fail to load 281with an error message like ``Browsers disable shared array buffer``. 282 283#### Apache HTTP .htaccess 284 285Place a ``.htaccess`` file in the same directory as ``python.wasm``. 286 287``` 288# .htaccess 289Header set Cross-Origin-Opener-Policy same-origin 290Header set Cross-Origin-Embedder-Policy require-corp 291 292AddType application/javascript js 293AddType application/wasm wasm 294 295<IfModule mod_deflate.c> 296 AddOutputFilterByType DEFLATE text/html application/javascript application/wasm 297</IfModule> 298``` 299 300## WASI (wasm32-wasi) 301 302See [the devguide on how to build and run for WASI](https://devguide.python.org/getting-started/setup-building/#wasi). 303 304## Detecting WebAssembly builds 305 306### Python code 307 308```python 309import os, sys 310 311if sys.platform == "emscripten": 312 # Python on Emscripten 313 ... 314if sys.platform == "wasi": 315 # Python on WASI 316 ... 317 318if os.name == "posix": 319 # WASM platforms identify as POSIX-like. 320 # Windows does not provide os.uname(). 321 machine = os.uname().machine 322 if machine.startswith("wasm"): 323 # WebAssembly (wasm32, wasm64 potentially in the future) 324``` 325 326```python 327>>> import os, sys 328>>> os.uname() 329posix.uname_result( 330 sysname='Emscripten', 331 nodename='emscripten', 332 release='3.1.19', 333 version='#1', 334 machine='wasm32' 335) 336>>> os.name 337'posix' 338>>> sys.platform 339'emscripten' 340>>> sys._emscripten_info 341sys._emscripten_info( 342 emscripten_version=(3, 1, 10), 343 runtime='Mozilla/5.0 (X11; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0', 344 pthreads=False, 345 shared_memory=False 346) 347``` 348 349```python 350>>> sys._emscripten_info 351sys._emscripten_info( 352 emscripten_version=(3, 1, 19), 353 runtime='Node.js v14.18.2', 354 pthreads=True, 355 shared_memory=True 356) 357``` 358 359```python 360>>> import os, sys 361>>> os.uname() 362posix.uname_result( 363 sysname='wasi', 364 nodename='(none)', 365 release='0.0.0', 366 version='0.0.0', 367 machine='wasm32' 368) 369>>> os.name 370'posix' 371>>> sys.platform 372'wasi' 373``` 374 375### C code 376 377Emscripten SDK and WASI SDK define several built-in macros. You can dump a 378full list of built-ins with ``emcc -dM -E - < /dev/null`` and 379``/path/to/wasi-sdk/bin/clang -dM -E - < /dev/null``. 380 381* WebAssembly ``__wasm__`` (also ``__wasm``) 382* wasm32 ``__wasm32__`` (also ``__wasm32``) 383* wasm64 ``__wasm64__`` 384* Emscripten ``__EMSCRIPTEN__`` (also ``EMSCRIPTEN``) 385* Emscripten version ``__EMSCRIPTEN_major__``, ``__EMSCRIPTEN_minor__``, ``__EMSCRIPTEN_tiny__`` 386* WASI ``__wasi__`` 387