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.
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.
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:
# Fedora, RHEL, CentOS podman run --rm -ti -v $(pwd):/python-wasm/cpython:Z -w /python-wasm/cpython quay.io/tiran/cpythonbuild:emsdk3 # other docker run --rm -ti -v $(pwd):/python-wasm/cpython -w /python-wasm/cpython quay.io/tiran/cpythonbuild:emsdk3
NOTE: Follow the on-screen instructions how to add the SDK to PATH.
git clone https://github.com/emscripten-core/emsdk.git /opt/emsdk /opt/emsdk/emsdk install latest /opt/emsdk/emsdk activate latest
The EM_COMPILER_WRAPPER must be set after the EMSDK environment is sourced. Otherwise the source script removes the environment variable.
. /opt/emsdk/emsdk_env.sh EM_COMPILER_WRAPPER=ccache
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:
. /opt/emsdk/emsdk_env.sh embuilder build zlib bzip2 MINIMAL_PIC embuilder build --pic zlib bzip2 MINIMAL_PIC
From within the container, run the following command:
./Tools/wasm/wasm_build.py build
The command is roughly equivalent to:
mkdir -p builddir/build pushd builddir/build ../../configure -C make -j$(nproc) popd
./Tools/wasm/wasm_build.py emscripten-browser
The command is roughly equivalent to:
mkdir -p builddir/emscripten-browser pushd builddir/emscripten-browser CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \ emconfigure ../../configure -C \ --host=wasm32-unknown-emscripten \ --build=$(../../config.guess) \ --with-emscripten-target=browser \ --with-build-python=$(pwd)/../build/python emmake make -j$(nproc) popd
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.
./Tools/wasm/wasm_webserver.py
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.
./Tools/wasm/wasm_build.py emscripten-browser-dl
The command is roughly equivalent to:
mkdir -p builddir/emscripten-node-dl pushd builddir/emscripten-node-dl CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \ emconfigure ../../configure -C \ --host=wasm32-unknown-emscripten \ --build=$(../../config.guess) \ --with-emscripten-target=node \ --enable-wasm-dynamic-linking \ --with-build-python=$(pwd)/../build/python emmake make -j$(nproc) popd
node --experimental-wasm-threads --experimental-wasm-bulk-memory --experimental-wasm-bigint builddir/emscripten-node-dl/python.js
(--experimental-wasm-bigint is not needed with recent NodeJS versions)
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.
asyncio, urllib, selectors, etc. are not available.AF_INET and AF_INET6 with SOCK_STREAM (TCP) or SOCK_DGRAM (UDP) are available. AF_UNIX is not supported.socketpair does not work.socket.accept crashes the runtime. gethostbyname does not resolve to a real IP address. IPv6 is not available.select module is limited. select.select() crashes the runtime due to lack of exectfd support.ENOSYS or ENOSUP.signal.alarm, itimer, sigaction are not available or do not work correctly. SIGTERM exits the runtime.os.nice and most functions of the resource module are not available.configure option --enable-wasm-pthreads adds compiler flag -pthread and linker flags -sUSE_PTHREADS -sPROXY_TO_PTHREAD.pwd module, grp module, os.setgroups, os.chown, and so on. lchown and lchmod are not available.umask is a no-op.os.link) are not supported.os.pread, os.preadv) are not available.os.mknod and os.mkfifo don't work and are disabled.mmap module is unstable. flush (msync) can crash the runtime.ctypes, readline, ssl, and more.--enable-wasm-dynamic-linking enables dynamic extensions supports. It's currently known to crash in combination with threading.locales module is affected by musl libc issues, gh-90548.obmalloc is disabled by default.ensurepip is not available.ctypes features like c_longlong and c_longdouble may need NodeJS option --experimental-wasm-bigint.pyc files.--enable-test-modules build test modules like _testcapi.Node builds use NODERAWFS.
FS.mount() call.--experimental-wasm-memory64.EM_JS functions must return BigInt().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.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.
Place a .htaccess file in the same directory as python.wasm.
# .htaccess
Header set Cross-Origin-Opener-Policy same-origin
Header set Cross-Origin-Embedder-Policy require-corp
AddType application/javascript js
AddType application/wasm wasm
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html application/javascript application/wasm
</IfModule>
WASI builds require the WASI SDK 16.0+. See .devcontainer/Dockerfile for an example of how to download and install the WASI SDK.
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:
./Tools/wasm/wasm_build.py wasi build
or:
./Tools/wasm/build_wasi.sh
The commands are equivalent to the following steps:
Modules/Setup.local existsclang)makepkg-config (on Linux)mkdir -p builddir/buildpushd builddir/buildsysconfig.get_config_var("BUILD_GNU_TYPE")../../config.guess../../configure -Cmake allPYTHON_VERSION=`./python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")'` popdmkdir 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 --"/ in the WASI runtime/Lib_sysconfigdata__wasi_wasm32-wasi.py on to sys.path via PYTHONPATHwasi-envWASI_SDK_PATHWASI_SYSROOTCCCPPCXXLDSHAREDARRANLIBCFLAGSLDFLAGSPKG_CONFIG_PATHPKG_CONFIG_LIBDIRPKG_CONFIG_SYSROOT_DIRPATHmake allIf 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):
wasmtime run --mapdir /::../.. --env PYTHONPATH=/builddir/wasi/build/lib.wasi-wasm32-$PYTHON_VERSION python.wasm -- <args>
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).
./Tools/wasm/wasm_build.py wasi repl
./Tools/wasm/wasm_build.py wasi test
wasmtime run -g generates debugging symbols for gdb and lldb. The feature is currently broken, see https://github.com/bytecodealliance/wasmtime/issues/4669 .RUST_LOG=wasi_common enables debug and trace logging.import os, sys if sys.platform == "emscripten": # Python on Emscripten if sys.platform == "wasi": # Python on WASI if os.name == "posix": # WASM platforms identify as POSIX-like. # Windows does not provide os.uname(). machine = os.uname().machine if machine.startswith("wasm"): # WebAssembly (wasm32, wasm64 in the future)
>>> import os, sys >>> os.uname() posix.uname_result( sysname='Emscripten', nodename='emscripten', release='3.1.19', version='#1', machine='wasm32' ) >>> os.name 'posix' >>> sys.platform 'emscripten' >>> sys._emscripten_info sys._emscripten_info( emscripten_version=(3, 1, 10), runtime='Mozilla/5.0 (X11; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0', pthreads=False, shared_memory=False )
>>> sys._emscripten_info sys._emscripten_info( emscripten_version=(3, 1, 19), runtime='Node.js v14.18.2', pthreads=True, shared_memory=True )
>>> import os, sys >>> os.uname() posix.uname_result( sysname='wasi', nodename='(none)', release='0.0.0', version='0.0.0', machine='wasm32' ) >>> os.name 'posix' >>> sys.platform 'wasi'
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.
__wasm__ (also __wasm)__wasm32__ (also __wasm32)__wasm64____EMSCRIPTEN__ (also EMSCRIPTEN)__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny____wasi__