Skip to content

Commit 3acf409

Browse files
committed
gh-84461: Add sys._emscripten_info, improve docs and build
1 parent f2b4e45 commit 3acf409

File tree

8 files changed

+264
-123
lines changed

8 files changed

+264
-123
lines changed

Doc/library/sys.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,35 @@ always available.
314314
yourself to control bytecode file generation.
315315

316316

317+
.. data:: _emscripten_info
318+
319+
A :term:`named tuple` holding information about the environment on the
320+
*wasm32-emscripten* platform. The named tuple is provisional and may change
321+
in the future.
322+
323+
.. tabularcolumns:: |l|L|
324+
325+
+-----------------------------+----------------------------------------------+
326+
| Attribute | Explanation |
327+
+=============================+==============================================+
328+
| :const:`emscripten_version` | Emscripten version as tuple of ints |
329+
| | (major, minor, micro), e.g. ``(3, 1, 8)``. |
330+
+-----------------------------+----------------------------------------------+
331+
| :const:`runtime` | Runtime string, e.g. browser user agent, |
332+
| | ``'Node.js v14.18.2'``, or ``'UNKNOWN'``. |
333+
+-----------------------------+----------------------------------------------+
334+
| :const:`pthreads` | ``True`` if Python is compiled with |
335+
| | Emscripten pthreads support. |
336+
+-----------------------------+----------------------------------------------+
337+
| :const:`shared_memory` | ``True`` if Python is compiled with shared |
338+
| | memory support. |
339+
+-----------------------------+----------------------------------------------+
340+
341+
.. availability:: WebAssembly Emscripten platform (*wasm32-emscripten*).
342+
343+
.. versionadded:: 3.11
344+
345+
317346
.. data:: pycache_prefix
318347

319348
If this is set (not ``None``), Python will write bytecode-cache ``.pyc``

Lib/test/support/threading_helper.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,7 @@ def _can_start_thread() -> bool:
222222
support (-s USE_PTHREADS / __EMSCRIPTEN_PTHREADS__).
223223
"""
224224
if sys.platform == "emscripten":
225-
try:
226-
_thread.start_new_thread(lambda: None, ())
227-
except RuntimeError:
228-
return False
229-
else:
230-
return True
225+
return sys._emscripten_info.pthreads
231226
elif sys.platform == "wasi":
232227
return False
233228
else:

Lib/test/test_sys.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,14 @@ def test_thread_info(self):
629629
self.assertIn(info.name, ('nt', 'pthread', 'solaris', None))
630630
self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))
631631

632+
@unittest.skipUnless(support.is_emscripten, "only available on Emscripten")
633+
def test_emscripten_info(self):
634+
self.assertEqual(len(sys._emscripten_info), 4)
635+
self.assertIsInstance(sys._emscripten_info.emscripten_version, tuple)
636+
self.assertIsInstance(sys._emscripten_info.runtime, (str, type(None)))
637+
self.assertIsInstance(sys._emscripten_info.pthreads, bool)
638+
self.assertIsInstance(sys._emscripten_info.shared_memory, bool)
639+
632640
def test_43581(self):
633641
# Can't use sys.stdout, as this is a StringIO object when
634642
# the test runs under regrtest.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add provisional :data:`sys._emscripten_info` named tuple with build-time and
2+
run-time information about Emscripten platform.

Python/sysmodule.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ extern void *PyWin_DLLhModule;
4848
extern const char *PyWin_DLLVersionString;
4949
#endif
5050

51+
#ifdef __EMSCRIPTEN__
52+
#include <emscripten.h>
53+
#endif
54+
5155
/*[clinic input]
5256
module sys
5357
[clinic start generated code]*/
@@ -2686,6 +2690,107 @@ make_impl_info(PyObject *version_info)
26862690
return NULL;
26872691
}
26882692

2693+
#ifdef __EMSCRIPTEN__
2694+
2695+
PyDoc_STRVAR(emscripten_info__doc__,
2696+
"sys._emscripten_info\n\
2697+
\n\
2698+
WebAssembly Emscripten platform information.");
2699+
2700+
static PyTypeObject EmscriptenInfoType;
2701+
2702+
static PyStructSequence_Field emscripten_info_fields[] = {
2703+
{"emscripten_version", "Emscripten version (major, minor, micro)"},
2704+
{"runtime", "Runtime (Node.JS version, browser user agent)"},
2705+
{"pthreads", "pthread support"},
2706+
{"shared_memory", "shared memory support"},
2707+
{0}
2708+
};
2709+
2710+
static PyStructSequence_Desc emscripten_info_desc = {
2711+
"sys._emscripten_info", /* name */
2712+
emscripten_info__doc__ , /* doc */
2713+
emscripten_info_fields, /* fields */
2714+
4
2715+
};
2716+
2717+
EM_JS(char *, _Py_emscripten_runtime, (void), {
2718+
var info;
2719+
if (typeof navigator == 'object') {
2720+
info = navigator.userAgent;
2721+
} else if (typeof process == 'object') {
2722+
info = "Node.js ".concat(process.version)
2723+
} else {
2724+
info = "UNKNOWN"
2725+
}
2726+
var len = lengthBytesUTF8(info) + 1;
2727+
var res = _malloc(len);
2728+
stringToUTF8(info, res, len);
2729+
return res;
2730+
});
2731+
2732+
static PyObject *
2733+
make_emscripten_info(void)
2734+
{
2735+
PyObject *emscripten_info = NULL;
2736+
PyObject *version = NULL;
2737+
char *ua;
2738+
int pos = 0;
2739+
2740+
emscripten_info = PyStructSequence_New(&EmscriptenInfoType);
2741+
if (emscripten_info == NULL) {
2742+
return NULL;
2743+
}
2744+
2745+
version = Py_BuildValue("(iii)",
2746+
__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__);
2747+
if (version == NULL) {
2748+
goto error;
2749+
}
2750+
PyStructSequence_SET_ITEM(emscripten_info, pos++, version);
2751+
2752+
ua = _Py_emscripten_runtime();
2753+
if (ua != NULL) {
2754+
PyObject *oua = PyUnicode_DecodeUTF8(ua, strlen(ua), "strict");
2755+
free(ua);
2756+
if (oua == NULL) {
2757+
goto error;
2758+
}
2759+
PyStructSequence_SET_ITEM(emscripten_info, pos++, oua);
2760+
} else {
2761+
Py_INCREF(Py_None);
2762+
PyStructSequence_SET_ITEM(emscripten_info, pos++, Py_None);
2763+
}
2764+
2765+
#define SetBoolItem(flag) \
2766+
PyStructSequence_SET_ITEM(emscripten_info, pos++, PyBool_FromLong(flag))
2767+
2768+
#ifdef __EMSCRIPTEN_PTHREADS__
2769+
SetBoolItem(1);
2770+
#else
2771+
SetBoolItem(0);
2772+
#endif
2773+
2774+
#ifdef __EMSCRIPTEN_SHARED_MEMORY__
2775+
SetBoolItem(1);
2776+
#else
2777+
SetBoolItem(0);
2778+
#endif
2779+
2780+
#undef SetBoolItem
2781+
2782+
if (PyErr_Occurred()) {
2783+
goto error;
2784+
}
2785+
return emscripten_info;
2786+
2787+
error:
2788+
Py_CLEAR(emscripten_info);
2789+
return NULL;
2790+
}
2791+
2792+
#endif // __EMSCRIPTEN__
2793+
26892794
static struct PyModuleDef sysmodule = {
26902795
PyModuleDef_HEAD_INIT,
26912796
"sys",
@@ -2821,6 +2926,17 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
28212926
}
28222927
}
28232928

2929+
#ifdef __EMSCRIPTEN__
2930+
if (EmscriptenInfoType.tp_name == NULL) {
2931+
if (_PyStructSequence_InitType(&EmscriptenInfoType,
2932+
&emscripten_info_desc,
2933+
Py_TPFLAGS_DISALLOW_INSTANTIATION) < 0) {
2934+
goto type_init_failed;
2935+
}
2936+
}
2937+
SET_SYS("_emscripten_info", make_emscripten_info());
2938+
#endif
2939+
28242940
/* adding sys.path_hooks and sys.path_importer_cache */
28252941
SET_SYS("meta_path", PyList_New(0));
28262942
SET_SYS("path_importer_cache", PyDict_New());
@@ -3066,6 +3182,9 @@ _PySys_Fini(PyInterpreterState *interp)
30663182
#endif
30673183
_PyStructSequence_FiniType(&Hash_InfoType);
30683184
_PyStructSequence_FiniType(&AsyncGenHooksType);
3185+
#ifdef __EMSCRIPTEN__
3186+
_PyStructSequence_FiniType(&EmscriptenInfoType);
3187+
#endif
30693188
}
30703189
}
30713190

Tools/wasm/README.md

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44

55
This directory contains configuration and helpers to facilitate cross
66
compilation of CPython to WebAssembly (WASM). For now we support
7-
*wasm32-emscripten* builds for modern browser and for *Node.js*. It's not
8-
possible to build for *wasm32-wasi* out-of-the-box yet.
7+
*wasm32-emscripten* builds for modern browser and for *Node.js*. WASI
8+
(*wasm32-wasi*) is work-in-progress
99

1010
## wasm32-emscripten build
1111

12+
For now the build system has two target flavors. The ``Emscripten/browser``
13+
target (``--with-emscripten-target=browser``) is optimized for browsers.
14+
It comes with a reduced and preloaded stdlib without tests and threading
15+
support. The ``Emscripten/node`` target has threading enabled and can
16+
access the file system directly.
17+
1218
Cross compiling to the wasm32-emscripten platform needs the
1319
[Emscripten](https://emscripten.org/) SDK and a build Python interpreter.
1420
Emscripten 3.1.8 or newer are recommended. All commands below are relative
@@ -76,7 +82,7 @@ and header files with debug builds.
7682

7783
### Cross compile to wasm32-emscripten for node
7884

79-
```
85+
```shell
8086
mkdir -p builddir/emscripten-node
8187
pushd builddir/emscripten-node
8288

@@ -91,7 +97,7 @@ emmake make -j$(nproc)
9197
popd
9298
```
9399

94-
```
100+
```shell
95101
node --experimental-wasm-threads --experimental-wasm-bulk-memory builddir/emscripten-node/python.js
96102
```
97103

@@ -150,9 +156,9 @@ functions.
150156
- Most stdlib modules with a dependency on external libraries are missing,
151157
e.g. ``ctypes``, ``readline``, ``sqlite3``, ``ssl``, and more.
152158
- Shared extension modules are not implemented yet. All extension modules
153-
are statically linked into the main binary.
154-
The experimental configure option ``--enable-wasm-dynamic-linking`` enables
155-
dynamic extensions.
159+
are statically linked into the main binary. The experimental configure
160+
option ``--enable-wasm-dynamic-linking`` enables dynamic extensions
161+
supports. It's currently known to crash in combination with threading.
156162
- glibc extensions for date and time formatting are not available.
157163
- ``locales`` module is affected by musl libc issues,
158164
[bpo-46390](https://bugs.python.org/issue46390).
@@ -167,8 +173,10 @@ functions.
167173
distutils, multiprocessing, dbm, tests and similar modules
168174
are not shipped. All other modules are bundled as pre-compiled
169175
``pyc`` files.
170-
- Threading is not supported.
176+
- Threading is disabled.
171177
- In-memory file system (MEMFS) is not persistent and limited.
178+
- Test modules are disabled by default. Use ``--enable-test-modules`` build
179+
test modules like ``_testcapi``.
172180

173181
## wasm32-emscripten in node
174182

@@ -205,11 +213,17 @@ AddType application/wasm wasm
205213
</IfModule>
206214
```
207215

216+
# WASI (wasm32-wasi)
217+
218+
WASI builds require [WASI SDK](https://github.com/WebAssembly/wasi-sdk) and
219+
currently [wasix](https://github.com/singlestore-labs/wasix) for POSIX
220+
compatibility stubs.
221+
208222
# Detect WebAssembly builds
209223

210224
## Python code
211225

212-
```# python
226+
```python
213227
import os, sys
214228

215229
if sys.platform == "emscripten":
@@ -222,7 +236,36 @@ if os.name == "posix":
222236
# Windows does not provide os.uname().
223237
machine = os.uname().machine
224238
if machine.startswith("wasm"):
225-
# WebAssembly (wasm32 or wasm64)
239+
# WebAssembly (wasm32, wasm64 in the future)
240+
```
241+
242+
```python
243+
>>> import os, sys
244+
>>> os.uname()
245+
posix.uname_result(sysname='Emscripten', nodename='emscripten', release='1.0', version='#1', machine='wasm32')
246+
>>> os.name
247+
'posix'
248+
>>> sys.platform
249+
'emscripten'
250+
>>> sys._emscripten_info
251+
sys._emscripten_info(
252+
emscripten_version=(3, 1, 8),
253+
runtime='Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0',
254+
pthreads=False,
255+
shared_memory=False
256+
)
257+
>>> sys._emscripten_info
258+
sys._emscripten_info(emscripten_version=(3, 1, 8), runtime='Node.js v14.18.2', pthreads=True, shared_memory=True)
259+
```
260+
261+
```python
262+
>>> import os, sys
263+
>>> os.uname()
264+
posix.uname_result(sysname='wasi', nodename='(none)', release='0.0.0', version='0.0.0', machine='wasm32')
265+
>>> os.name
266+
'posix'
267+
>>> sys.platform
268+
'wasi'
226269
```
227270

228271
## C code
@@ -231,7 +274,7 @@ Emscripten SDK and WASI SDK define several built-in macros. You can dump a
231274
full list of built-ins with ``emcc -dM -E - < /dev/null`` and
232275
``/path/to/wasi-sdk/bin/clang -dM -E - < /dev/null``.
233276

234-
```# C
277+
```C
235278
#ifdef __EMSCRIPTEN__
236279
// Python on Emscripten
237280
#endif

0 commit comments

Comments
 (0)