WARNING: WASM support is highly experimental! Lots of features are not working yet.
This directory contains configuration and helpers to facilitate cross compilation of CPython to WebAssembly (WASM). For now we support wasm32-emscripten builds for modern browser and for Node.js. WASI (wasm32-wasi) is work-in-progress
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.8 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.
# Fedora, RHEL, CentOS podman run --rm -ti -v $(pwd):/python-wasm/cpython:Z quay.io/tiran/cpythonbuild:emsdk3 # other docker run --rm -ti -v $(pwd):/python-wasm/cpython quay.io/tiran/cpythonbuild:emsdk3
mkdir -p builddir/build pushd builddir/build ../../configure -C make -j$(nproc) popd
embuilder build zlib bzip2
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.
emrun builddir/emscripten-browser/python.html
or
./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.
mkdir -p builddir/emscripten-node pushd builddir/emscripten-node CONFIG_SITE=../../Tools/wasm/config.site-wasm32-emscripten \ emconfigure ../../configure -C \ --host=wasm32-unknown-emscripten \ --build=$(../../config.guess) \ --with-emscripten-target=node \ --with-build-python=$(pwd)/../build/python emmake make -j$(nproc) popd
node --experimental-wasm-threads --experimental-wasm-bulk-memory builddir/emscripten-node/python.js
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, sqlite3, 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, bpo-46390.obmalloc is disabled by default.ensurepip is not available.pyc files.--enable-test-modules build test modules like _testcapi.Node builds use NODERAWFS.
FS.mount() call.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 WASI SDK 15.0+ and currently wasix for POSIX compatibility stubs.
A lot of Emscripten limitations also apply to WASI. Noticable restrictions are:
socket(2) cannot create new socket file descriptors. WASI programs can call read/write/accept on a file descriptor that is passed into the process.socket.gethostname() and host name resolution APIs like socket.gethostbyname() are not implemented and always fail.open(2) checks flags more strictly. Caller must pass either O_RDONLY, O_RDWR, or O_WDONLY to os.open. Directory file descriptors must be created with flags O_RDONLY | O_DIRECTORY.chmod(2) is not available. It's not possible to modify file permissions, yet. A future version of WASI may provide a limited set_permissions API.os.chown(), os.getuid, etc. are stubs or fail with ENOTSUP.fcntl) is not available.os.pipe(), os.mkfifo(), and os.mknod() are not supported.process_time does not work as expected because it's implemented using wall clock.os.umask() is a stub.sys.executable is empty./dev/null / os.devnull may not be available.os.utime*() is buggy in WASM SDK 15.0, see utimensat() with timespec=NULL sets wrong timeos.symlink() fails with PermissionError when attempting to create a symlink with an absolute path with wasmtime 0.36.0. The wasmtime runtime uses openat2(2) syscall with flag RESOLVE_BENEATH to open files. The flag causes the syscall to reject symlinks with absolute paths.os.curdir (aka .) seems to behave differently, which breaks some importlib tests that add . to sys.path and indirectly sys.path_importer_cache.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='1.0', version='#1', machine='wasm32') >>> os.name 'posix' >>> sys.platform 'emscripten' >>> sys._emscripten_info sys._emscripten_info( emscripten_version=(3, 1, 8), runtime='Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0', pthreads=False, shared_memory=False ) >>> sys._emscripten_info sys._emscripten_info(emscripten_version=(3, 1, 8), 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.
#ifdef __EMSCRIPTEN__ // Python on Emscripten #endif
__wasm__ (also __wasm)__wasm32__ (also __wasm32)__wasm64____EMSCRIPTEN__ (also EMSCRIPTEN)__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny____wasi__Feature detection flags:
__EMSCRIPTEN_PTHREADS____EMSCRIPTEN_SHARED_MEMORY____wasm_simd128____wasm_sign_ext____wasm_bulk_memory____wasm_atomics____wasm_mutable_globals__