Python WebAssembly (WASM) build
WARNING: WASM support is work-in-progress! Lots of features are not working yet.
This directory contains configuration and helpers to facilitate cross compilation of CPython to WebAssembly (WASM). Python supports Emscripten (wasm32-emscripten) and WASI (wasm32-wasi) targets. Emscripten builds run in modern browsers and JavaScript runtimes like Node.js. WASI builds use WASM runtimes such as wasmtime.
Users and developers are encouraged to use the script Tools/wasm/wasm_build.py. The tool automates the build process and provides assistance with installation of SDKs, running tests, etc.
NOTE: If you are looking for information that is not directly related to building CPython for WebAssembly (or the resulting build), please see https://github.com/psf/webassembly for more information.
wasm32-emscripten
Build
For now the build system has two target flavors. The Emscripten/browser target (--with-emscripten-target=browser) is optimized for browsers. It comes with a reduced and preloaded stdlib without tests and threading support. The Emscripten/node target has threading enabled and can access the file system directly.
Cross compiling to the wasm32-emscripten platform needs the Emscripten SDK and a build Python interpreter. Emscripten 3.1.19 or newer are recommended. All commands below are relative to a repository checkout.
Toolchain
Container image
Christian Heimes maintains a container image with Emscripten SDK, Python build dependencies, WASI-SDK, wasmtime, and several additional tools.
From within your local CPython repo clone, run one of the following commands:
Manually
Install Emscripten SDK
NOTE: Follow the on-screen instructions how to add the SDK to PATH.
Optionally: enable ccache for EMSDK
The EM_COMPILER_WRAPPER must be set after the EMSDK environment is sourced. Otherwise the source script removes the environment variable.
Optionally: pre-build and cache static libraries
Emscripten SDK provides static builds of core libraries without PIC (position-independent code). Python builds with dlopen support require PIC. To populate the build cache, run:
Compile a build Python interpreter
From within the container, run the following command:
The command is roughly equivalent to:
Cross-compile to wasm32-emscripten for browser
The command is roughly equivalent to:
Serve python.html with a local webserver and open the file in a browser. Python comes with a minimal web server script that sets necessary HTTP headers like COOP, COEP, and mimetypes. Run the script outside the container and from the root of the CPython checkout.
and open http://localhost:8000/builddir/emscripten-browser/python.html . This directory structure enables the C/C++ DevTools Support (DWARF) to load C and header files with debug builds.
Cross compile to wasm32-emscripten for node
The command is roughly equivalent to:
(--experimental-wasm-bigint is not needed with recent NodeJS versions)
Limitations and issues
Emscripten before 3.1.8 has known bugs that can cause memory corruption and resource leaks. 3.1.8 contains several fixes for bugs in date and time functions.
Network stack
Python's socket module does not work with Emscripten's emulated POSIX sockets yet. Network modules like
asyncio,urllib,selectors, etc. are not available.Only
AF_INETandAF_INET6withSOCK_STREAM(TCP) orSOCK_DGRAM(UDP) are available.AF_UNIXis not supported.socketpairdoes not work.Blocking sockets are not available and non-blocking sockets don't work correctly, e.g.
socket.acceptcrashes the runtime.gethostbynamedoes not resolve to a real IP address. IPv6 is not available.The
selectmodule is limited.select.select()crashes the runtime due to lack of exectfd support.
processes, signals
Processes are not supported. System calls like fork, popen, and subprocess fail with
ENOSYSorENOSUP.Signal support is limited.
signal.alarm,itimer,sigactionare not available or do not work correctly.SIGTERMexits the runtime.Keyboard interrupt (CTRL+C) handling is not implemented yet.
Resource-related functions like
os.niceand most functions of theresourcemodule are not available.
threading
Threading is disabled by default. The
configureoption--enable-wasm-pthreadsadds compiler flag-pthreadand linker flags-sUSE_PTHREADS -sPROXY_TO_PTHREAD.pthread support requires WASM threads and SharedArrayBuffer (bulk memory). The Node.JS runtime keeps a pool of web workers around. Each web worker uses several file descriptors (eventfd, epoll, pipe).
It's not advised to enable threading when building for browsers or with dynamic linking support; there are performance and stability issues.
file system
Most user, group, and permission related function and modules are not supported or don't work as expected, e.g.
pwdmodule,grpmodule,os.setgroups,os.chown, and so on.lchownandlchmodare not available.umaskis a no-op.hard links (
os.link) are not supported.Offset and iovec I/O functions (e.g.
os.pread,os.preadv) are not available.os.mknodandos.mkfifodon't work and are disabled.Large file support crashes the runtime and is disabled.
mmapmodule is unstable. flush (msync) can crash the runtime.
Misc
Heap memory and stack size are limited. Recursion or extensive memory consumption can crash Python.
Most stdlib modules with a dependency on external libraries are missing, e.g.
ctypes,readline,ssl, and more.Shared extension modules are not implemented yet. All extension modules are statically linked into the main binary. The experimental configure option
--enable-wasm-dynamic-linkingenables dynamic extensions supports. It's currently known to crash in combination with threading.glibc extensions for date and time formatting are not available.
localesmodule is affected by musl libc issues, gh-90548.Python's object allocator
obmallocis disabled by default.ensurepipis not available.Some
ctypesfeatures likec_longlongandc_longdoublemay need NodeJS option--experimental-wasm-bigint.
In the browser
The interactive shell does not handle copy 'n paste and unicode support well.
The bundled stdlib is limited. Network-related modules, multiprocessing, dbm, tests and similar modules are not shipped. All other modules are bundled as pre-compiled
pycfiles.In-memory file system (MEMFS) is not persistent and limited.
Test modules are disabled by default. Use
--enable-test-modulesbuild test modules like_testcapi.
wasm32-emscripten in node
Node builds use NODERAWFS.
Node RawFS allows direct access to the host file system without need to perform
FS.mount()call.
wasm64-emscripten
wasm64 requires recent NodeJS and
--experimental-wasm-memory64.EM_JSfunctions must returnBigInt().Py_BuildValue()format strings must match size of types. Confusing 32 and 64 bits types leads to memory corruption, see gh-95876 and gh-95878.
Hosting Python WASM builds
The simple REPL terminal uses SharedArrayBuffer. For security reasons browsers only provide the feature in secure environents with cross-origin isolation. The webserver must send cross-origin headers and correct MIME types for the JavaScript and WASM files. Otherwise the terminal will fail to load with an error message like Browsers disable shared array buffer.
Apache HTTP .htaccess
Place a .htaccess file in the same directory as python.wasm.
WASI (wasm32-wasi)
WASI builds require the WASI SDK 16.0+. See .devcontainer/Dockerfile for an example of how to download and install the WASI SDK.
Build
The script wasi-env sets necessary compiler and linker flags as well as pkg-config overrides. The script assumes that WASI-SDK is installed in /opt/wasi-sdk or $WASI_SDK_PATH.
There are two scripts you can use to do a WASI build from a source checkout. You can either use:
or:
The commands are equivalent to the following steps:
Make sure
Modules/Setup.localexistsMake sure the necessary build tools are installed:
WASI SDK (which ships with
clang)makepkg-config(on Linux)
Create the build Python
mkdir -p builddir/buildpushd builddir/buildGet the build platform
Python:
sysconfig.get_config_var("BUILD_GNU_TYPE")Shell:
../../config.guess
../../configure -Cmake allPYTHON_VERSION=`./python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'`popd
Create the host/WASI Python
mkdir builddir/wasipushd builddir/wasi../../Tools/wasm/wasi-env ../../configure -C --host=wasm32-unknown-wasi --build=$(../../config.guess) --with-build-python=../build/pythonCONFIG_SITE=../../Tools/wasm/config.site-wasm32-wasiHOSTRUNNER="wasmtime run --mapdir /::$(dirname $(dirname $(pwd))) --env PYTHONPATH=/builddir/wasi/build/lib.wasi-wasm32-$PYTHON_VERSION $(pwd)/python.wasm --"Maps the source checkout to
/in the WASI runtimeStdlib gets loaded from
/LibGets
_sysconfigdata__wasi_wasm32-wasi.pyon tosys.pathviaPYTHONPATH
Set by
wasi-envWASI_SDK_PATHWASI_SYSROOTCCCPPCXXLDSHAREDARRANLIBCFLAGSLDFLAGSPKG_CONFIG_PATHPKG_CONFIG_LIBDIRPKG_CONFIG_SYSROOT_DIRPATH
make all
Running
If you followed the instructions above, you can run the interpreter via e.g., wasmtime from within the Tools/wasi directory (make sure to set/change $PYTHON_VERSION and do note the paths are relative to running inbuilddir/wasi for simplicity only):
There are also helpers provided by Tools/wasm/wasm_build.py as listed below. Also, if you used Tools/wasm/build_wasi.sh, a run_wasi.sh file will be created in builddir/wasi which will run the above command for you (it also uses absolute paths, so it can be executed from anywhere).
REPL
Tests
Debugging
wasmtime run -ggenerates debugging symbols for gdb and lldb. The feature is currently broken, see https://github.com/bytecodealliance/wasmtime/issues/4669 .The environment variable
RUST_LOG=wasi_commonenables debug and trace logging.
Detect WebAssembly builds
Python code
C code
Emscripten SDK and WASI SDK define several built-in macros. You can dump a full list of built-ins with emcc -dM -E - < /dev/null and /path/to/wasi-sdk/bin/clang -dM -E - < /dev/null.
WebAssembly
__wasm__(also__wasm)wasm32
__wasm32__(also__wasm32)wasm64
__wasm64__Emscripten
__EMSCRIPTEN__(alsoEMSCRIPTEN)Emscripten version
__EMSCRIPTEN_major__,__EMSCRIPTEN_minor__,__EMSCRIPTEN_tiny__WASI
__wasi__