Skip to content

gh-84461: Add sys._emscripten_info, improve docs and build (gh-91781) #91781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,35 @@ always available.
yourself to control bytecode file generation.


.. data:: _emscripten_info

A :term:`named tuple` holding information about the environment on the
*wasm32-emscripten* platform. The named tuple is provisional and may change
in the future.

.. tabularcolumns:: |l|L|

+-----------------------------+----------------------------------------------+
| Attribute | Explanation |
+=============================+==============================================+
| :const:`emscripten_version` | Emscripten version as tuple of ints |
| | (major, minor, micro), e.g. ``(3, 1, 8)``. |
+-----------------------------+----------------------------------------------+
| :const:`runtime` | Runtime string, e.g. browser user agent, |
| | ``'Node.js v14.18.2'``, or ``'UNKNOWN'``. |
+-----------------------------+----------------------------------------------+
| :const:`pthreads` | ``True`` if Python is compiled with |
| | Emscripten pthreads support. |
+-----------------------------+----------------------------------------------+
| :const:`shared_memory` | ``True`` if Python is compiled with shared |
| | memory support. |
+-----------------------------+----------------------------------------------+

.. availability:: WebAssembly Emscripten platform (*wasm32-emscripten*).

.. versionadded:: 3.11


.. data:: pycache_prefix

If this is set (not ``None``), Python will write bytecode-cache ``.pyc``
Expand Down
7 changes: 1 addition & 6 deletions Lib/test/support/threading_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,7 @@ def _can_start_thread() -> bool:
support (-s USE_PTHREADS / __EMSCRIPTEN_PTHREADS__).
"""
if sys.platform == "emscripten":
try:
_thread.start_new_thread(lambda: None, ())
except RuntimeError:
return False
else:
return True
return sys._emscripten_info.pthreads
elif sys.platform == "wasi":
return False
else:
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,14 @@ def test_thread_info(self):
self.assertIn(info.name, ('nt', 'pthread', 'solaris', None))
self.assertIn(info.lock, ('semaphore', 'mutex+cond', None))

@unittest.skipUnless(support.is_emscripten, "only available on Emscripten")
def test_emscripten_info(self):
self.assertEqual(len(sys._emscripten_info), 4)
self.assertIsInstance(sys._emscripten_info.emscripten_version, tuple)
self.assertIsInstance(sys._emscripten_info.runtime, (str, type(None)))
self.assertIsInstance(sys._emscripten_info.pthreads, bool)
self.assertIsInstance(sys._emscripten_info.shared_memory, bool)

def test_43581(self):
# Can't use sys.stdout, as this is a StringIO object when
# the test runs under regrtest.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add provisional :data:`sys._emscripten_info` named tuple with build-time and
run-time information about Emscripten platform.
118 changes: 118 additions & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ extern void *PyWin_DLLhModule;
extern const char *PyWin_DLLVersionString;
#endif

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

/*[clinic input]
module sys
[clinic start generated code]*/
Expand Down Expand Up @@ -2686,6 +2690,107 @@ make_impl_info(PyObject *version_info)
return NULL;
}

#ifdef __EMSCRIPTEN__

PyDoc_STRVAR(emscripten_info__doc__,
"sys._emscripten_info\n\
\n\
WebAssembly Emscripten platform information.");

static PyTypeObject *EmscriptenInfoType;

static PyStructSequence_Field emscripten_info_fields[] = {
{"emscripten_version", "Emscripten version (major, minor, micro)"},
{"runtime", "Runtime (Node.JS version, browser user agent)"},
{"pthreads", "pthread support"},
{"shared_memory", "shared memory support"},
{0}
};

static PyStructSequence_Desc emscripten_info_desc = {
"sys._emscripten_info", /* name */
emscripten_info__doc__ , /* doc */
emscripten_info_fields, /* fields */
4
};

EM_JS(char *, _Py_emscripten_runtime, (void), {
var info;
if (typeof navigator == 'object') {
info = navigator.userAgent;
} else if (typeof process == 'object') {
info = "Node.js ".concat(process.version)
} else {
info = "UNKNOWN"
}
var len = lengthBytesUTF8(info) + 1;
var res = _malloc(len);
stringToUTF8(info, res, len);
return res;
});

static PyObject *
make_emscripten_info(void)
{
PyObject *emscripten_info = NULL;
PyObject *version = NULL;
char *ua;
int pos = 0;

emscripten_info = PyStructSequence_New(EmscriptenInfoType);
if (emscripten_info == NULL) {
return NULL;
}

version = Py_BuildValue("(iii)",
__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__);
if (version == NULL) {
goto error;
}
PyStructSequence_SET_ITEM(emscripten_info, pos++, version);

ua = _Py_emscripten_runtime();
if (ua != NULL) {
PyObject *oua = PyUnicode_DecodeUTF8(ua, strlen(ua), "strict");
free(ua);
if (oua == NULL) {
goto error;
}
PyStructSequence_SET_ITEM(emscripten_info, pos++, oua);
} else {
Py_INCREF(Py_None);
PyStructSequence_SET_ITEM(emscripten_info, pos++, Py_None);
}

#define SetBoolItem(flag) \
PyStructSequence_SET_ITEM(emscripten_info, pos++, PyBool_FromLong(flag))

#ifdef __EMSCRIPTEN_PTHREADS__
SetBoolItem(1);
#else
SetBoolItem(0);
#endif

#ifdef __EMSCRIPTEN_SHARED_MEMORY__
SetBoolItem(1);
#else
SetBoolItem(0);
#endif

#undef SetBoolItem

if (PyErr_Occurred()) {
goto error;
}
return emscripten_info;

error:
Py_CLEAR(emscripten_info);
return NULL;
}

#endif // __EMSCRIPTEN__

static struct PyModuleDef sysmodule = {
PyModuleDef_HEAD_INIT,
"sys",
Expand Down Expand Up @@ -2821,6 +2926,16 @@ _PySys_InitCore(PyThreadState *tstate, PyObject *sysdict)
}
}

#ifdef __EMSCRIPTEN__
if (EmscriptenInfoType == NULL) {
EmscriptenInfoType = PyStructSequence_NewType(&emscripten_info_desc);
if (EmscriptenInfoType == NULL) {
goto type_init_failed;
}
}
SET_SYS("_emscripten_info", make_emscripten_info());
#endif

/* adding sys.path_hooks and sys.path_importer_cache */
SET_SYS("meta_path", PyList_New(0));
SET_SYS("path_importer_cache", PyDict_New());
Expand Down Expand Up @@ -3066,6 +3181,9 @@ _PySys_Fini(PyInterpreterState *interp)
#endif
_PyStructSequence_FiniType(&Hash_InfoType);
_PyStructSequence_FiniType(&AsyncGenHooksType);
#ifdef __EMSCRIPTEN__
Py_CLEAR(EmscriptenInfoType);
#endif
}
}

Expand Down
65 changes: 54 additions & 11 deletions Tools/wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@

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*. It's not
possible to build for *wasm32-wasi* out-of-the-box yet.
*wasm32-emscripten* builds for modern browser and for *Node.js*. WASI
(*wasm32-wasi*) is work-in-progress

## 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](https://emscripten.org/) SDK and a build Python interpreter.
Emscripten 3.1.8 or newer are recommended. All commands below are relative
Expand Down Expand Up @@ -76,7 +82,7 @@ and header files with debug builds.

### Cross compile to wasm32-emscripten for node

```
```shell
mkdir -p builddir/emscripten-node
pushd builddir/emscripten-node

Expand All @@ -91,7 +97,7 @@ emmake make -j$(nproc)
popd
```

```
```shell
node --experimental-wasm-threads --experimental-wasm-bulk-memory builddir/emscripten-node/python.js
```

Expand Down Expand Up @@ -150,9 +156,9 @@ functions.
- Most stdlib modules with a dependency on external libraries are missing,
e.g. ``ctypes``, ``readline``, ``sqlite3``, ``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-linking`` enables
dynamic extensions.
are statically linked into the main binary. The experimental configure
option ``--enable-wasm-dynamic-linking`` enables dynamic extensions
supports. It's currently known to crash in combination with threading.
- glibc extensions for date and time formatting are not available.
- ``locales`` module is affected by musl libc issues,
[bpo-46390](https://bugs.python.org/issue46390).
Expand All @@ -167,8 +173,10 @@ functions.
distutils, multiprocessing, dbm, tests and similar modules
are not shipped. All other modules are bundled as pre-compiled
``pyc`` files.
- Threading is not supported.
- Threading is disabled.
- In-memory file system (MEMFS) is not persistent and limited.
- Test modules are disabled by default. Use ``--enable-test-modules`` build
test modules like ``_testcapi``.

## wasm32-emscripten in node

Expand Down Expand Up @@ -205,11 +213,17 @@ AddType application/wasm wasm
</IfModule>
```

# WASI (wasm32-wasi)

WASI builds require [WASI SDK](https://github.com/WebAssembly/wasi-sdk) and
currently [wasix](https://github.com/singlestore-labs/wasix) for POSIX
compatibility stubs.

# Detect WebAssembly builds

## Python code

```# python
```python
import os, sys

if sys.platform == "emscripten":
Expand All @@ -222,7 +236,36 @@ if os.name == "posix":
# Windows does not provide os.uname().
machine = os.uname().machine
if machine.startswith("wasm"):
# WebAssembly (wasm32 or wasm64)
# WebAssembly (wasm32, wasm64 in the future)
```

```python
>>> 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)
```

```python
>>> 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'
```

## C code
Expand All @@ -231,7 +274,7 @@ 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``.

```# C
```C
#ifdef __EMSCRIPTEN__
// Python on Emscripten
#endif
Expand Down
Loading