diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 8dd89c68507..3efd6beb035 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -12,8 +12,14 @@ extern "C" { struct pyruntimestate; struct _ceval_runtime_state; +/* WASI has limited call stack. wasmtime 0.36 can handle sufficient amount of + C stack frames for little more than 750 recursions. */ #ifndef Py_DEFAULT_RECURSION_LIMIT -# define Py_DEFAULT_RECURSION_LIMIT 1000 +# ifdef __wasi__ +# define Py_DEFAULT_RECURSION_LIMIT 750 +# else +# define Py_DEFAULT_RECURSION_LIMIT 1000 +# endif #endif #include "pycore_interp.h" // PyInterpreterState.eval_frame diff --git a/Lib/platform.py b/Lib/platform.py index 3f3f25a2c92..c272c407c77 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -186,6 +186,10 @@ def libc_ver(executable=None, lib='', version='', chunksize=16384): executable = sys.executable + if not executable: + # sys.executable is not set. + return lib, version + V = _comparable_version # We use os.path.realpath() # here to work around problems with Cygwin not being diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 28549a645b0..84e1c047f92 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -545,8 +545,14 @@ def collect_ssl(info_add): def collect_socket(info_add): import socket - hostname = socket.gethostname() - info_add('socket.hostname', hostname) + try: + hostname = socket.gethostname() + except OSError: + # WASI SDK 15.0 does not have gethostname(2). + if sys.platform != "wasi": + raise + else: + info_add('socket.hostname', hostname) def collect_sqlite(info_add): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index e4bda940b3d..c284fc67b64 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -199,6 +199,11 @@ def get_original_stdout(): def _force_run(path, func, *args): try: return func(*args) + except FileNotFoundError as err: + # chmod() won't fix a missing file. + if verbose >= 2: + print('%s: %s' % (err.__class__.__name__, err)) + raise except OSError as err: if verbose >= 2: print('%s: %s' % (err.__class__.__name__, err)) diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py index 487a4fa2a95..d4357243dda 100644 --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -109,7 +109,9 @@ class TestSpecifics(unittest.TestCase): self.assertEqual(d['z'], 12) def test_extended_arg(self): - longexpr = 'x = x or ' + '-x' * 2500 + # default: 1000 * 2.5 = 2500 repetitions + repeat = int(sys.getrecursionlimit() * 2.5) + longexpr = 'x = x or ' + '-x' * repeat g = {} code = ''' def f(x): diff --git a/Lib/test/test_fileio.py b/Lib/test/test_fileio.py index e4984d3cd55..c26cdc028cc 100644 --- a/Lib/test/test_fileio.py +++ b/Lib/test/test_fileio.py @@ -9,7 +9,9 @@ from array import array from weakref import proxy from functools import wraps -from test.support import cpython_only, swap_attr, gc_collect, is_emscripten +from test.support import ( + cpython_only, swap_attr, gc_collect, is_emscripten, is_wasi +) from test.support.os_helper import (TESTFN, TESTFN_UNICODE, make_bad_fd) from test.support.warnings_helper import check_warnings from collections import UserList @@ -65,6 +67,7 @@ class AutoFileTests: self.assertRaises((AttributeError, TypeError), setattr, f, attr, 'oops') + @unittest.skipIf(is_wasi, "WASI does not expose st_blksize.") def testBlksize(self): # test private _blksize attribute blksize = io.DEFAULT_BUFFER_SIZE diff --git a/Lib/test/test_largefile.py b/Lib/test/test_largefile.py index 8f6bec16200..3c11c59baef 100644 --- a/Lib/test/test_largefile.py +++ b/Lib/test/test_largefile.py @@ -156,6 +156,8 @@ class TestFileMethods(LargeFileTest): def skip_no_disk_space(path, required): def decorator(fun): def wrapper(*args, **kwargs): + if not hasattr(shutil, "disk_usage"): + raise unittest.SkipTest("requires shutil.disk_usage") if shutil.disk_usage(os.path.realpath(path)).free < required: hsize = int(required / 1024 / 1024) raise unittest.SkipTest( diff --git a/Lib/test/test_logging.py b/Lib/test/test_logging.py index e69afae484a..fd562322a76 100644 --- a/Lib/test/test_logging.py +++ b/Lib/test/test_logging.py @@ -5280,6 +5280,7 @@ class FileHandlerTest(BaseFileTest): self.assertEqual(fp.read().strip(), '1') class RotatingFileHandlerTest(BaseFileTest): + @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.") def test_should_not_rollover(self): # If maxbytes is zero rollover never occurs rh = logging.handlers.RotatingFileHandler( @@ -5387,6 +5388,7 @@ class RotatingFileHandlerTest(BaseFileTest): rh.close() class TimedRotatingFileHandlerTest(BaseFileTest): + @unittest.skipIf(support.is_wasi, "WASI does not have /dev/null.") def test_should_not_rollover(self): # See bpo-45401. Should only ever rollover regular files fh = logging.handlers.TimedRotatingFileHandler( diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 36ad587760d..ae071821e1c 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -11,7 +11,6 @@ import fnmatch import fractions import itertools import locale -import mmap import os import pickle import select @@ -59,6 +58,10 @@ try: except ImportError: INT_MAX = PY_SSIZE_T_MAX = sys.maxsize +try: + import mmap +except ImportError: + mmap = None from test.support.script_helper import assert_python_ok from test.support import unix_shell @@ -2167,7 +2170,8 @@ class TestInvalidFD(unittest.TestCase): @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') @unittest.skipIf( - support.is_emscripten, "musl libc issue on Emscripten, bpo-46390" + support.is_emscripten or support.is_wasi, + "musl libc issue on Emscripten/WASI, bpo-46390" ) def test_fpathconf(self): self.check(os.pathconf, "PC_NAME_MAX") @@ -2460,6 +2464,7 @@ class Win32KillTests(unittest.TestCase): # os.kill on Windows can take an int which gets set as the exit code self._kill(100) + @unittest.skipIf(mmap is None, "requires mmap") def _kill_with_event(self, event, name): tagname = "test_os_%s" % uuid.uuid1() m = mmap.mmap(-1, 1, tagname) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 6d3b299b24c..6aa529b0620 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -107,6 +107,10 @@ class PosixTests(unittest.TestCase): script = os.path.join(dirname, 'signalinterproctester.py') assert_python_ok(script) + @unittest.skipUnless( + hasattr(signal, "valid_signals"), + "requires signal.valid_signals" + ) def test_valid_signals(self): s = signal.valid_signals() self.assertIsInstance(s, set) @@ -212,6 +216,7 @@ class WakeupFDTests(unittest.TestCase): self.assertRaises((ValueError, OSError), signal.set_wakeup_fd, fd) + @unittest.skipUnless(support.has_socket_support, "needs working sockets.") def test_invalid_socket(self): sock = socket.socket() fd = sock.fileno() @@ -241,6 +246,7 @@ class WakeupFDTests(unittest.TestCase): self.assertEqual(signal.set_wakeup_fd(-1), -1) @unittest.skipIf(support.is_emscripten, "Emscripten cannot fstat pipes.") + @unittest.skipUnless(support.has_socket_support, "needs working sockets.") def test_set_wakeup_fd_socket_result(self): sock1 = socket.socket() self.addCleanup(sock1.close) diff --git a/Lib/test/test_tomllib/test_misc.py b/Lib/test/test_tomllib/test_misc.py index 76fa5905fa4..378db58f255 100644 --- a/Lib/test/test_tomllib/test_misc.py +++ b/Lib/test/test_tomllib/test_misc.py @@ -6,6 +6,7 @@ import copy import datetime from decimal import Decimal as D from pathlib import Path +import sys import tempfile import unittest @@ -91,11 +92,13 @@ class TestMiscellaneous(unittest.TestCase): self.assertEqual(obj_copy, expected_obj) def test_inline_array_recursion_limit(self): - nest_count = 470 + # 470 with default recursion limit + nest_count = int(sys.getrecursionlimit() * 0.47) recursive_array_toml = "arr = " + nest_count * "[" + nest_count * "]" tomllib.loads(recursive_array_toml) def test_inline_table_recursion_limit(self): - nest_count = 310 + # 310 with default recursion limit + nest_count = int(sys.getrecursionlimit() * 0.31) recursive_table_toml = nest_count * "key = {" + nest_count * "}" tomllib.loads(recursive_table_toml) diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py index 85dbf4d8f68..66789262dd6 100644 --- a/Lib/test/test_zipimport.py +++ b/Lib/test/test_zipimport.py @@ -804,6 +804,7 @@ class BadFileZipImportTestCase(unittest.TestCase): os_helper.create_empty_file(TESTMOD) self.assertZipFailure(TESTMOD) + @unittest.skipIf(support.is_wasi, "mode 000 not supported.") def testFileUnreadable(self): os_helper.unlink(TESTMOD) fd = os.open(TESTMOD, os.O_CREAT, 000) diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst b/Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst new file mode 100644 index 00000000000..1f9f45a511f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2022-05-15-15-25-05.gh-issue-90473.MoPHYW.rst @@ -0,0 +1 @@ +Decrease default recursion limit on WASI to address limited call stack size. diff --git a/Modules/timemodule.c b/Modules/timemodule.c index 7475ef344b7..18f9ddb909c 100644 --- a/Modules/timemodule.c +++ b/Modules/timemodule.c @@ -1481,7 +1481,7 @@ _PyTime_GetThreadTimeWithInfo(_PyTime_t *tp, _Py_clock_info_t *info) #elif defined(HAVE_CLOCK_GETTIME) && \ defined(CLOCK_PROCESS_CPUTIME_ID) && \ - !defined(__EMSCRIPTEN__) + !defined(__EMSCRIPTEN__) && !defined(__wasi__) #define HAVE_THREAD_TIME #if defined(__APPLE__) && defined(__has_attribute) && __has_attribute(availability) diff --git a/Parser/parser.c b/Parser/parser.c index adc8d509eb7..08bf6d29456 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -7,7 +7,11 @@ # define D(x) #endif -# define MAXSTACK 6000 +#ifdef __wasi__ +# define MAXSTACK 4000 +#else +# define MAXSTACK 6000 +#endif static const int n_keyword_lists = 9; static KeywordToken *reserved_keywords[] = { (KeywordToken[]) {{NULL, -1}}, diff --git a/Tools/peg_generator/pegen/c_generator.py b/Tools/peg_generator/pegen/c_generator.py index 56a1e5a5a14..65bfd5900a6 100644 --- a/Tools/peg_generator/pegen/c_generator.py +++ b/Tools/peg_generator/pegen/c_generator.py @@ -37,7 +37,11 @@ EXTENSION_PREFIX = """\ # define D(x) #endif -# define MAXSTACK 6000 +#ifdef __wasi__ +# define MAXSTACK 4000 +#else +# define MAXSTACK 6000 +#endif """ diff --git a/Tools/wasm/README.md b/Tools/wasm/README.md index 83806f0581a..977b2bb2a8a 100644 --- a/Tools/wasm/README.md +++ b/Tools/wasm/README.md @@ -220,10 +220,27 @@ AddType application/wasm wasm # 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 +WASI builds require [WASI SDK](https://github.com/WebAssembly/wasi-sdk) 15.0+ +and currently [wasix](https://github.com/singlestore-labs/wasix) for POSIX compatibility stubs. +## WASI limitations and issues (WASI SDK 15.0) + +A lot of Emscripten limitations also apply to WASI. Noticable restrictions +are: + +- Call stack size is limited. Default recursion limit and parser stack size + are smaller than in regular Python builds. +- ``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. +- ``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. +- File locking (``fcntl``) is not available. +- ``os.pipe()``, ``os.mkfifo()``, and ``os.mknod()`` are not supported. + + # Detect WebAssembly builds ## Python code diff --git a/Tools/wasm/config.site-wasm32-wasi b/Tools/wasm/config.site-wasm32-wasi index 255e99c279a..ee3fc830e3d 100644 --- a/Tools/wasm/config.site-wasm32-wasi +++ b/Tools/wasm/config.site-wasm32-wasi @@ -17,3 +17,24 @@ ac_cv_header_sys_resource_h=no # undefined symbols / unsupported features ac_cv_func_eventfd=no + +# WASI SDK 15.0 has no pipe syscall. +ac_cv_func_pipe=no + +# WASI SDK 15.0 cannot create fifos and special files. +ac_cv_func_mkfifo=no +ac_cv_func_mkfifoat=no +ac_cv_func_mknod=no +ac_cv_func_mknodat=no + +# fdopendir() fails on SDK 15.0, +# OSError: [Errno 28] Invalid argument: '.' +ac_cv_func_fdopendir=no + +# WASIX stubs we don't want to use. +ac_cv_func_kill=no + +# WASI sockets are limited to operations on given socket fd and inet sockets. +# Disable AF_UNIX and AF_PACKET support, see socketmodule.h. +ac_cv_header_sys_un_h=no +ac_cv_header_netpacket_packet_h=no diff --git a/configure b/configure index 02810880e91..6fa4051310b 100755 --- a/configure +++ b/configure @@ -22611,6 +22611,7 @@ case $ac_sys_system in #( py_cv_module__ctypes_test=n/a + py_cv_module_fcntl=n/a py_cv_module_=n/a diff --git a/configure.ac b/configure.ac index eab326232b1..bef4904325b 100644 --- a/configure.ac +++ b/configure.ac @@ -6690,8 +6690,10 @@ AS_CASE([$ac_sys_system], ], [Emscripten/node*], [], [WASI/*], [ + dnl WASI SDK 15.0 does not support file locking. PY_STDLIB_MOD_SET_NA( [_ctypes_test], + [fcntl], ) ] )