diff --git a/.gitattributes b/.gitattributes index 8c37dbbb631022..acfd62411542f1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -84,6 +84,7 @@ Lib/keyword.py generated Lib/test/levenshtein_examples.json generated Lib/test/test_stable_abi_ctypes.py generated Lib/token.py generated +Misc/sbom.spdx.json generated Objects/typeslots.inc generated PC/python3dll.c generated Parser/parser.c generated diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index f6ebbfacfba509..aab319cbe7405e 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -106,8 +106,8 @@ Functions and classes provided: This function is a :term:`decorator` that can be used to define a factory function for :keyword:`async with` statement asynchronous context managers, - without needing to create a class or separate :meth:`__aenter__` and - :meth:`__aexit__` methods. It must be applied to an :term:`asynchronous + without needing to create a class or separate :meth:`~object.__aenter__` and + :meth:`~object.__aexit__` methods. It must be applied to an :term:`asynchronous generator` function. A simple example:: @@ -616,12 +616,12 @@ Functions and classes provided: asynchronous context managers, as well as having coroutines for cleanup logic. - The :meth:`close` method is not implemented, :meth:`aclose` must be used + The :meth:`~ExitStack.close` method is not implemented; :meth:`aclose` must be used instead. .. coroutinemethod:: enter_async_context(cm) - Similar to :meth:`enter_context` but expects an asynchronous context + Similar to :meth:`ExitStack.enter_context` but expects an asynchronous context manager. .. versionchanged:: 3.11 @@ -630,16 +630,16 @@ Functions and classes provided: .. method:: push_async_exit(exit) - Similar to :meth:`push` but expects either an asynchronous context manager + Similar to :meth:`ExitStack.push` but expects either an asynchronous context manager or a coroutine function. .. method:: push_async_callback(callback, /, *args, **kwds) - Similar to :meth:`callback` but expects a coroutine function. + Similar to :meth:`ExitStack.callback` but expects a coroutine function. .. coroutinemethod:: aclose() - Similar to :meth:`close` but properly handles awaitables. + Similar to :meth:`ExitStack.close` but properly handles awaitables. Continuing the example for :func:`asynccontextmanager`:: diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst index 0138557f5fd84c..8381e508139fbd 100644 --- a/Doc/library/inspect.rst +++ b/Doc/library/inspect.rst @@ -492,7 +492,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes): Methods implemented via descriptors that also pass one of the other tests return ``False`` from the :func:`ismethoddescriptor` test, simply because the other tests promise more -- you can, e.g., count on having the - :ref:`__func__ ` attribute (etc) when an object passes + :attr:`~method.__func__` attribute (etc) when an object passes :func:`ismethod`. diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 6c4a7d5b02e859..9aac2242159ea7 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -619,6 +619,9 @@ Pure paths provide the following methods and properties: >>> PurePath('a/b.py').match(pattern) True + .. versionchanged:: 3.12 + Accepts an object implementing the :class:`os.PathLike` interface. + As with other methods, case-sensitivity follows platform defaults:: >>> PurePosixPath('b.py').match('*.PY') diff --git a/Doc/library/shelve.rst b/Doc/library/shelve.rst index 219219af6fd87f..88802d717d7383 100644 --- a/Doc/library/shelve.rst +++ b/Doc/library/shelve.rst @@ -149,13 +149,14 @@ Restrictions .. class:: BsdDbShelf(dict, protocol=None, writeback=False, keyencoding='utf-8') - A subclass of :class:`Shelf` which exposes :meth:`first`, :meth:`!next`, - :meth:`previous`, :meth:`last` and :meth:`set_location` which are available - in the third-party :mod:`bsddb` module from `pybsddb + A subclass of :class:`Shelf` which exposes :meth:`!first`, :meth:`!next`, + :meth:`!previous`, :meth:`!last` and :meth:`!set_location` methods. + These are available + in the third-party :mod:`!bsddb` module from `pybsddb `_ but not in other database modules. The *dict* object passed to the constructor must support those methods. This is generally accomplished by calling one of - :func:`bsddb.hashopen`, :func:`bsddb.btopen` or :func:`bsddb.rnopen`. The + :func:`!bsddb.hashopen`, :func:`!bsddb.btopen` or :func:`!bsddb.rnopen`. The optional *protocol*, *writeback*, and *keyencoding* parameters have the same interpretation as for the :class:`Shelf` class. diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index d30d289710b129..f61ef8b0ecc7ba 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -346,6 +346,8 @@ Directory and files operations .. versionchanged:: 3.13 :func:`!rmtree` now ignores :exc:`FileNotFoundError` exceptions for all but the top-level path. + Exceptions other than :exc:`OSError` and subclasses of :exc:`!OSError` + are now always propagated to the caller. .. attribute:: rmtree.avoids_symlink_attacks diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 44c13bd9474ea1..1265b5b12e492d 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5328,25 +5328,30 @@ Methods .. index:: pair: object; method Methods are functions that are called using the attribute notation. There are -two flavors: built-in methods (such as :meth:`append` on lists) and class -instance methods. Built-in methods are described with the types that support -them. +two flavors: :ref:`built-in methods ` (such as :meth:`append` +on lists) and :ref:`class instance method `. +Built-in methods are described with the types that support them. If you access a method (a function defined in a class namespace) through an instance, you get a special object: a :dfn:`bound method` (also called -:dfn:`instance method`) object. When called, it will add the ``self`` argument +:ref:`instance method `) object. When called, it will add +the ``self`` argument to the argument list. Bound methods have two special read-only attributes: -``m.__self__`` is the object on which the method operates, and ``m.__func__`` is +:attr:`m.__self__ ` is the object on which the method +operates, and :attr:`m.__func__ ` is the function implementing the method. Calling ``m(arg-1, arg-2, ..., arg-n)`` is completely equivalent to calling ``m.__func__(m.__self__, arg-1, arg-2, ..., arg-n)``. -Like function objects, bound method objects support getting arbitrary +Like :ref:`function objects `, bound method objects support +getting arbitrary attributes. However, since method attributes are actually stored on the -underlying function object (``meth.__func__``), setting method attributes on +underlying function object (:attr:`method.__func__`), setting method attributes on bound methods is disallowed. Attempting to set an attribute on a method results in an :exc:`AttributeError` being raised. In order to set a method -attribute, you need to explicitly set it on the underlying function object:: +attribute, you need to explicitly set it on the underlying function object: + +.. doctest:: >>> class C: ... def method(self): @@ -5361,7 +5366,7 @@ attribute, you need to explicitly set it on the underlying function object:: >>> c.method.whoami 'my name is method' -See :ref:`types` for more information. +See :ref:`instance-methods` for more information. .. index:: object; code, code object diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 3bcc170faa087a..27d379a8b70f31 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -519,6 +519,8 @@ These are the types to which the function call operation (see section :ref:`calls`) can be applied: +.. _user-defined-funcs: + User-defined functions ^^^^^^^^^^^^^^^^^^^^^^ @@ -654,43 +656,64 @@ callable object (normally a user-defined function). single: __name__ (method attribute) single: __module__ (method attribute) -Special read-only attributes: :attr:`__self__` is the class instance object, -:attr:`__func__` is the function object; :attr:`__doc__` is the method's -documentation (same as ``__func__.__doc__``); :attr:`~definition.__name__` is the -method name (same as ``__func__.__name__``); :attr:`__module__` is the -name of the module the method was defined in, or ``None`` if unavailable. +Special read-only attributes: + +.. list-table:: + + * - .. attribute:: method.__self__ + - Refers to the class instance object to which the method is + :ref:`bound ` + + * - .. attribute:: method.__func__ + - Refers to the original function object + + * - .. attribute:: method.__doc__ + - The method's documentation (same as :attr:`!method.__func__.__doc__`). + A :class:`string ` if the original function had a docstring, else + ``None``. + + * - .. attribute:: method.__name__ + - The name of the method (same as :attr:`!method.__func__.__name__`) + + * - .. attribute:: method.__module__ + - The name of the module the method was defined in, or ``None`` if + unavailable. Methods also support accessing (but not setting) the arbitrary function -attributes on the underlying function object. +attributes on the underlying :ref:`function object `. User-defined method objects may be created when getting an attribute of a class (perhaps via an instance of that class), if that attribute is a -user-defined function object or a class method object. +user-defined :ref:`function object ` or a +:class:`classmethod` object. + +.. _method-binding: When an instance method object is created by retrieving a user-defined -function object from a class via one of its instances, its -:attr:`__self__` attribute is the instance, and the method object is said -to be bound. The new method's :attr:`__func__` attribute is the original -function object. - -When an instance method object is created by retrieving a class method -object from a class or instance, its :attr:`__self__` attribute is the -class itself, and its :attr:`__func__` attribute is the function object +:ref:`function object ` from a class via one of its +instances, its :attr:`~method.__self__` attribute is the instance, and the +method object is said to be *bound*. The new method's :attr:`~method.__func__` +attribute is the original function object. + +When an instance method object is created by retrieving a :class:`classmethod` +object from a class or instance, its :attr:`~method.__self__` attribute is the +class itself, and its :attr:`~method.__func__` attribute is the function object underlying the class method. When an instance method object is called, the underlying function -(:attr:`__func__`) is called, inserting the class instance -(:attr:`__self__`) in front of the argument list. For instance, when +(:attr:`~method.__func__`) is called, inserting the class instance +(:attr:`~method.__self__`) in front of the argument list. For instance, when :class:`!C` is a class which contains a definition for a function :meth:`!f`, and ``x`` is an instance of :class:`!C`, calling ``x.f(1)`` is equivalent to calling ``C.f(x, 1)``. -When an instance method object is derived from a class method object, the -"class instance" stored in :attr:`__self__` will actually be the class +When an instance method object is derived from a :class:`classmethod` object, the +"class instance" stored in :attr:`~method.__self__` will actually be the class itself, so that calling either ``x.f(1)`` or ``C.f(1)`` is equivalent to calling ``f(C,1)`` where ``f`` is the underlying function. -Note that the transformation from function object to instance method +Note that the transformation from :ref:`function object ` +to instance method object happens each time the attribute is retrieved from the instance. In some cases, a fruitful optimization is to assign the attribute to a local variable and call that local variable. Also notice that this @@ -774,6 +797,8 @@ set to ``None`` (but see the next item); :attr:`__module__` is the name of the module the function was defined in or ``None`` if unavailable. +.. _builtin-methods: + Built-in methods ^^^^^^^^^^^^^^^^ @@ -785,8 +810,9 @@ Built-in methods This is really a different disguise of a built-in function, this time containing an object passed to the C function as an implicit extra argument. An example of a built-in method is ``alist.append()``, assuming *alist* is a list object. In -this case, the special read-only attribute :attr:`__self__` is set to the object -denoted by *alist*. +this case, the special read-only attribute :attr:`!__self__` is set to the object +denoted by *alist*. (The attribute has the same semantics as it does with +:attr:`other instance methods `.) Classes @@ -901,8 +927,9 @@ https://www.python.org/download/releases/2.3/mro/. When a class attribute reference (for class :class:`!C`, say) would yield a class method object, it is transformed into an instance method object whose -:attr:`__self__` attribute is :class:`!C`. When it would yield a static -method object, it is transformed into the object wrapped by the static method +:attr:`~method.__self__` attribute is :class:`!C`. +When it would yield a :class:`staticmethod` object, +it is transformed into the object wrapped by the static method object. See section :ref:`descriptors` for another way in which attributes retrieved from a class may differ from those actually contained in its :attr:`~object.__dict__`. @@ -970,7 +997,7 @@ in which attribute references are searched. When an attribute is not found there, and the instance's class has an attribute by that name, the search continues with the class attributes. If a class attribute is found that is a user-defined function object, it is transformed into an instance method -object whose :attr:`__self__` attribute is the instance. Static method and +object whose :attr:`~method.__self__` attribute is the instance. Static method and class method objects are also transformed; see above under "Classes". See section :ref:`descriptors` for another way in which attributes of a class retrieved via its instances may differ from the objects actually stored in diff --git a/Doc/tools/.nitignore b/Doc/tools/.nitignore index 8a033f019372f7..5ef68cc089d436 100644 --- a/Doc/tools/.nitignore +++ b/Doc/tools/.nitignore @@ -39,7 +39,6 @@ Doc/library/collections.abc.rst Doc/library/collections.rst Doc/library/concurrent.futures.rst Doc/library/configparser.rst -Doc/library/contextlib.rst Doc/library/csv.rst Doc/library/datetime.rst Doc/library/dbm.rst @@ -86,7 +85,6 @@ Doc/library/readline.rst Doc/library/resource.rst Doc/library/rlcompleter.rst Doc/library/select.rst -Doc/library/shelve.rst Doc/library/signal.rst Doc/library/smtplib.rst Doc/library/socket.rst diff --git a/Doc/tutorial/classes.rst b/Doc/tutorial/classes.rst index 7b92e1a51b6e67..3bf138ca225ee5 100644 --- a/Doc/tutorial/classes.rst +++ b/Doc/tutorial/classes.rst @@ -769,8 +769,10 @@ data from a string buffer instead, and pass it as an argument. or arithmetic operators, and assigning such a "pseudo-file" to sys.stdin will not cause the interpreter to read further input from it.) -Instance method objects have attributes, too: ``m.__self__`` is the instance -object with the method :meth:`!m`, and ``m.__func__`` is the function object +:ref:`Instance method objects ` have attributes, too: +:attr:`m.__self__ ` is the instance +object with the method :meth:`!m`, and :attr:`m.__func__ ` is +the :ref:`function object ` corresponding to the method. diff --git a/Doc/whatsnew/2.6.rst b/Doc/whatsnew/2.6.rst index 8bdbb0fa352ed1..e8c1709c42abac 100644 --- a/Doc/whatsnew/2.6.rst +++ b/Doc/whatsnew/2.6.rst @@ -1678,8 +1678,8 @@ Some smaller changes made to the core Python language are: * Instance method objects have new attributes for the object and function comprising the method; the new synonym for :attr:`!im_self` is - :ref:`__self__ `, and :attr:`!im_func` is also available as - :ref:`__func__ `. + :attr:`~method.__self__`, and :attr:`!im_func` is also available as + :attr:`~method.__func__`. The old names are still supported in Python 2.6, but are gone in 3.0. * An obscure change: when you use the :func:`locals` function inside a diff --git a/Doc/whatsnew/2.7.rst b/Doc/whatsnew/2.7.rst index 162dd74637479a..cf6d26859bb6a2 100644 --- a/Doc/whatsnew/2.7.rst +++ b/Doc/whatsnew/2.7.rst @@ -858,9 +858,10 @@ Some smaller changes made to the core Python language are: .. XXX bytearray doesn't seem to be documented -* When using ``@classmethod`` and ``@staticmethod`` to wrap +* When using :class:`@classmethod ` and + :class:`@staticmethod ` to wrap methods as class or static methods, the wrapper object now - exposes the wrapped function as their :ref:`__func__ ` + exposes the wrapped function as their :attr:`~method.__func__` attribute. (Contributed by Amaury Forgeot d'Arc, after a suggestion by George Sakkis; :issue:`5982`.) diff --git a/Include/object.h b/Include/object.h index 85abd30b5ad7d6..bd576b0bd43211 100644 --- a/Include/object.h +++ b/Include/object.h @@ -279,6 +279,10 @@ _Py_ThreadId(void) __asm__ ("" : "=r" (tp)); tid = tp; #endif +#elif defined(__s390__) && defined(__GNUC__) + // Both GCC and Clang have supported __builtin_thread_pointer + // for s390 from long time ago. + tid = (uintptr_t)__builtin_thread_pointer(); #else # error "define _Py_ThreadId for this platform" #endif diff --git a/Include/opcode_ids.h b/Include/opcode_ids.h index ba25bd459c1bcd..47f809e345f61c 100644 --- a/Include/opcode_ids.h +++ b/Include/opcode_ids.h @@ -1,6 +1,6 @@ -// This file is generated by Tools/cases_generator/generate_cases.py +// This file is generated by Tools/cases_generator/opcode_id_generator.py // from: -// Python/bytecodes.c +// ['./Python/bytecodes.c'] // Do not edit! #ifndef Py_OPCODE_IDS_H @@ -55,7 +55,6 @@ extern "C" { #define UNARY_NEGATIVE 42 #define UNARY_NOT 43 #define WITH_EXCEPT_START 44 -#define HAVE_ARGUMENT 45 #define BINARY_OP 45 #define BUILD_CONST_KEY_MAP 46 #define BUILD_LIST 47 @@ -200,7 +199,6 @@ extern "C" { #define UNPACK_SEQUENCE_LIST 216 #define UNPACK_SEQUENCE_TUPLE 217 #define UNPACK_SEQUENCE_TWO_TUPLE 218 -#define MIN_INSTRUMENTED_OPCODE 236 #define INSTRUMENTED_RESUME 236 #define INSTRUMENTED_END_FOR 237 #define INSTRUMENTED_END_SEND 238 @@ -233,6 +231,9 @@ extern "C" { #define SETUP_WITH 266 #define STORE_FAST_MAYBE_NULL 267 +#define HAVE_ARGUMENT 45 +#define MIN_INSTRUMENTED_OPCODE 236 + #ifdef __cplusplus } #endif diff --git a/Include/pymacconfig.h b/Include/pymacconfig.h index 806e41955efd7f..615abe103ca038 100644 --- a/Include/pymacconfig.h +++ b/Include/pymacconfig.h @@ -7,7 +7,9 @@ #define PY_MACCONFIG_H #ifdef __APPLE__ +#undef ALIGNOF_MAX_ALIGN_T #undef SIZEOF_LONG +#undef SIZEOF_LONG_DOUBLE #undef SIZEOF_PTHREAD_T #undef SIZEOF_SIZE_T #undef SIZEOF_TIME_T @@ -20,6 +22,7 @@ #undef DOUBLE_IS_BIG_ENDIAN_IEEE754 #undef DOUBLE_IS_LITTLE_ENDIAN_IEEE754 #undef HAVE_GCC_ASM_FOR_X87 +#undef HAVE_GCC_ASM_FOR_X64 #undef VA_LIST_IS_ARRAY #if defined(__LP64__) && defined(__x86_64__) @@ -74,8 +77,14 @@ # define DOUBLE_IS_LITTLE_ENDIAN_IEEE754 #endif -#ifdef __i386__ +#if defined(__i386__) || defined(__x86_64__) # define HAVE_GCC_ASM_FOR_X87 +# define ALIGNOF_MAX_ALIGN_T 16 +# define HAVE_GCC_ASM_FOR_X64 1 +# define SIZEOF_LONG_DOUBLE 16 +#else +# define ALIGNOF_MAX_ALIGN_T 8 +# define SIZEOF_LONG_DOUBLE 8 #endif #endif // __APPLE__ diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 4423c6d9586896..237536389deb84 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -204,14 +204,13 @@ def __repr__(self): return "<{}.parents>".format(type(self._path).__name__) -class PurePath: - """Base class for manipulating paths without I/O. +class _PurePathBase: + """Base class for pure path objects. - PurePath represents a filesystem path and offers operations which - don't imply any actual filesystem I/O. Depending on your system, - instantiating a PurePath will return either a PurePosixPath or a - PureWindowsPath object. You can also instantiate either of these classes - directly, regardless of your system. + This class *does not* provide several magic methods that are defined in + its subclass PurePath. They are: __fspath__, __bytes__, __reduce__, + __hash__, __eq__, __lt__, __le__, __gt__, __ge__. Its initializer and path + joining methods accept only strings, not os.PathLike objects more broadly. """ __slots__ = ( @@ -237,22 +236,6 @@ class PurePath: # for the first time. It's used to implement `_str_normcase` '_str', - # The `_str_normcase_cached` slot stores the string path with - # normalized case. It is set when the `_str_normcase` property is - # accessed for the first time. It's used to implement `__eq__()` - # `__hash__()`, and `_parts_normcase` - '_str_normcase_cached', - - # The `_parts_normcase_cached` slot stores the case-normalized - # string path after splitting on path separators. It's set when the - # `_parts_normcase` property is accessed for the first time. It's used - # to implement comparison methods like `__lt__()`. - '_parts_normcase_cached', - - # The `_hash` slot stores the hash of the case-normalized string - # path. It's set when `__hash__()` is called for the first time. - '_hash', - # The '_resolving' slot stores a boolean indicating whether the path # is being processed by `_PathBase.resolve()`. This prevents duplicate # work from occurring when `resolve()` calls `stat()` or `readlink()`. @@ -260,6 +243,21 @@ class PurePath: ) pathmod = os.path + def __init__(self, *args): + self._load_raw_paths(args) + + def _load_raw_paths(self, args): + paths = [] + for path in args: + if not isinstance(path, str): + raise TypeError( + "argument should be a str, " + f"not {type(path).__name__!r}") + if path: + paths.append(path) + self._raw_paths = paths + self._resolving = False + def with_segments(self, *pathsegments): """Construct a new path object from any number of path-like objects. Subclasses may override this method to customize how new path objects @@ -490,7 +488,7 @@ def relative_to(self, other, /, *_deprecated, walk_up=False): warnings._deprecated("pathlib.PurePath.relative_to(*args)", msg, remove=(3, 14)) other = self.with_segments(other, *_deprecated) - elif not isinstance(other, PurePath): + elif not isinstance(other, _PurePathBase): other = self.with_segments(other) for step, path in enumerate(other._ancestors): if path in self._ancestors: @@ -515,7 +513,7 @@ def is_relative_to(self, other, /, *_deprecated): warnings._deprecated("pathlib.PurePath.is_relative_to(*args)", msg, remove=(3, 14)) other = self.with_segments(other, *_deprecated) - elif not isinstance(other, PurePath): + elif not isinstance(other, _PurePathBase): other = self.with_segments(other) return other.without_trailing_sep() in self._ancestors @@ -534,7 +532,7 @@ def joinpath(self, *pathsegments): paths) or a totally different path (if one of the arguments is anchored). """ - return self.with_segments(self, *pathsegments) + return self.with_segments(*self._raw_paths, *pathsegments) def __truediv__(self, key): try: @@ -544,7 +542,7 @@ def __truediv__(self, key): def __rtruediv__(self, key): try: - return self.with_segments(key, self) + return self.with_segments(key, *self._raw_paths) except TypeError: return NotImplemented @@ -602,7 +600,7 @@ def match(self, path_pattern, *, case_sensitive=None): """ Return True if this path matches the given pattern. """ - if not isinstance(path_pattern, PurePath): + if not isinstance(path_pattern, _PurePathBase): path_pattern = self.with_segments(path_pattern) if case_sensitive is None: case_sensitive = _is_case_sensitive(self.pathmod) @@ -617,6 +615,35 @@ def match(self, path_pattern, *, case_sensitive=None): match = _compile_pattern(pattern_str, sep, case_sensitive) return match(str(self)) is not None + +class PurePath(_PurePathBase): + """Base class for manipulating paths without I/O. + + PurePath represents a filesystem path and offers operations which + don't imply any actual filesystem I/O. Depending on your system, + instantiating a PurePath will return either a PurePosixPath or a + PureWindowsPath object. You can also instantiate either of these classes + directly, regardless of your system. + """ + + __slots__ = ( + # The `_str_normcase_cached` slot stores the string path with + # normalized case. It is set when the `_str_normcase` property is + # accessed for the first time. It's used to implement `__eq__()` + # `__hash__()`, and `_parts_normcase` + '_str_normcase_cached', + + # The `_parts_normcase_cached` slot stores the case-normalized + # string path after splitting on path separators. It's set when the + # `_parts_normcase` property is accessed for the first time. It's used + # to implement comparison methods like `__lt__()`. + '_parts_normcase_cached', + + # The `_hash` slot stores the hash of the case-normalized string + # path. It's set when `__hash__()` is called for the first time. + '_hash', + ) + def __new__(cls, *args, **kwargs): """Construct a PurePath from one or several strings and or existing PurePath objects. The strings and path objects are combined so as @@ -627,7 +654,7 @@ def __new__(cls, *args, **kwargs): cls = PureWindowsPath if os.name == 'nt' else PurePosixPath return object.__new__(cls) - def __init__(self, *args): + def _load_raw_paths(self, args): paths = [] for arg in args: if isinstance(arg, PurePath): @@ -765,7 +792,7 @@ class PureWindowsPath(PurePath): # Filesystem-accessing classes -class _PathBase(PurePath): +class _PathBase(_PurePathBase): """Base class for concrete path objects. This class provides dummy implementations for many methods that derived @@ -779,8 +806,6 @@ class _PathBase(PurePath): such as paths in archive files or on remote storage systems. """ __slots__ = () - __bytes__ = None - __fspath__ = None # virtual paths have no local file system representation @classmethod def _unsupported(cls, method_name): @@ -1395,7 +1420,7 @@ def as_uri(self): self._unsupported("as_uri") -class Path(_PathBase): +class Path(_PathBase, PurePath): """PurePath subclass that can make system calls. Path represents a filesystem path but unlike PurePath, also offers @@ -1405,8 +1430,6 @@ class Path(_PathBase): but cannot instantiate a WindowsPath on a POSIX system or vice versa. """ __slots__ = () - __bytes__ = PurePath.__bytes__ - __fspath__ = PurePath.__fspath__ as_uri = PurePath.as_uri def __init__(self, *args, **kwargs): diff --git a/Lib/statistics.py b/Lib/statistics.py index 4da06889c6db46..83aaedb04515e0 100644 --- a/Lib/statistics.py +++ b/Lib/statistics.py @@ -527,8 +527,10 @@ def count(iterable): def geometric_mean(data): """Convert data to floats and compute the geometric mean. - Raises a StatisticsError if the input dataset is empty, - if it contains a zero, or if it contains a negative value. + Raises a StatisticsError if the input dataset is empty + or if it contains a negative value. + + Returns zero if the product of inputs is zero. No special efforts are made to achieve exact results. (However, this may change in the future.) @@ -536,11 +538,25 @@ def geometric_mean(data): >>> round(geometric_mean([54, 24, 36]), 9) 36.0 """ - try: - return exp(fmean(map(log, data))) - except ValueError: - raise StatisticsError('geometric mean requires a non-empty dataset ' - 'containing positive numbers') from None + n = 0 + found_zero = False + def count_positive(iterable): + nonlocal n, found_zero + for n, x in enumerate(iterable, start=1): + if x > 0.0 or math.isnan(x): + yield x + elif x == 0.0: + found_zero = True + else: + raise StatisticsError('No negative inputs allowed', x) + total = fsum(map(log, count_positive(data))) + if not n: + raise StatisticsError('Must have a non-empty dataset') + if math.isnan(total): + return math.nan + if found_zero: + return math.nan if total == math.inf else 0.0 + return exp(total / n) def harmonic_mean(data, weights=None): diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 8f51e41cca93f1..a52c8beaa8b023 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -49,8 +49,35 @@ def test_is_notimplemented(self): # Tests for the pure classes. # -class PurePathTest(unittest.TestCase): - cls = pathlib.PurePath + +class PurePathBaseTest(unittest.TestCase): + cls = pathlib._PurePathBase + + def test_magic_methods(self): + P = self.cls + self.assertFalse(hasattr(P, '__fspath__')) + self.assertFalse(hasattr(P, '__bytes__')) + self.assertIs(P.__reduce__, object.__reduce__) + self.assertIs(P.__hash__, object.__hash__) + self.assertIs(P.__eq__, object.__eq__) + self.assertIs(P.__lt__, object.__lt__) + self.assertIs(P.__le__, object.__le__) + self.assertIs(P.__gt__, object.__gt__) + self.assertIs(P.__ge__, object.__ge__) + + +class DummyPurePath(pathlib._PurePathBase): + def __eq__(self, other): + if not isinstance(other, DummyPurePath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + + +class DummyPurePathTest(unittest.TestCase): + cls = DummyPurePath # Keys are canonical paths, values are list of tuples of arguments # supposed to produce equal paths. @@ -84,12 +111,6 @@ def test_constructor_common(self): P('/a', 'b', 'c') P('a/b/c') P('/a/b/c') - P(FakePath("a/b/c")) - self.assertEqual(P(P('a')), P('a')) - self.assertEqual(P(P('a'), 'b'), P('a/b')) - self.assertEqual(P(P('a'), P('b')), P('a/b')) - self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c"))) - self.assertEqual(P(P('./a:b')), P('./a:b')) def test_concrete_class(self): if self.cls is pathlib.PurePath: @@ -195,8 +216,6 @@ def test_join_common(self): self.assertIs(type(pp), type(p)) pp = p.joinpath('c', 'd') self.assertEqual(pp, P('a/b/c/d')) - pp = p.joinpath(P('c')) - self.assertEqual(pp, P('a/b/c')) pp = p.joinpath('/c') self.assertEqual(pp, P('/c')) @@ -213,8 +232,6 @@ def test_div_common(self): self.assertEqual(pp, P('a/b/c/d')) pp = 'c' / p / 'd' self.assertEqual(pp, P('c/a/b/d')) - pp = p / P('c') - self.assertEqual(pp, P('a/b/c')) pp = p/ '/c' self.assertEqual(pp, P('/c')) @@ -834,6 +851,29 @@ def test_is_relative_to_trailing_sep(self): self.assertFalse(P('foo/oof/').is_relative_to('foo/bar')) self.assertFalse(P('foo/oof/').is_relative_to('foo/bar/')) + +class PurePathTest(DummyPurePathTest): + cls = pathlib.PurePath + + def test_constructor_nested(self): + P = self.cls + P(FakePath("a/b/c")) + self.assertEqual(P(P('a')), P('a')) + self.assertEqual(P(P('a'), 'b'), P('a/b')) + self.assertEqual(P(P('a'), P('b')), P('a/b')) + self.assertEqual(P(P('a'), P('b'), P('c')), P(FakePath("a/b/c"))) + self.assertEqual(P(P('./a:b')), P('./a:b')) + + def test_join_nested(self): + P = self.cls + p = P('a/b').joinpath(P('c')) + self.assertEqual(p, P('a/b/c')) + + def test_div_nested(self): + P = self.cls + p = P('a/b') / P('c') + self.assertEqual(p, P('a/b/c')) + def test_pickling_common(self): P = self.cls for pathstr in ('a', 'a/', 'a/b', 'a/b/c', '/', '/a/b', '/a/b/c', 'a/b/c/'): @@ -1746,7 +1786,7 @@ class cls(pathlib.PurePath): # Tests for the virtual classes. # -class PathBaseTest(PurePathTest): +class PathBaseTest(PurePathBaseTest): cls = pathlib._PathBase def test_unsupported_operation(self): @@ -1837,6 +1877,14 @@ class DummyPath(pathlib._PathBase): _directories = {} _symlinks = {} + def __eq__(self, other): + if not isinstance(other, DummyPath): + return NotImplemented + return str(self) == str(other) + + def __hash__(self): + return hash(str(self)) + def stat(self, *, follow_symlinks=True): if follow_symlinks: path = str(self.resolve()) @@ -1908,7 +1956,7 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False): self.mkdir(mode, parents=False, exist_ok=exist_ok) -class DummyPathTest(unittest.TestCase): +class DummyPathTest(DummyPurePathTest): """Tests for PathBase methods that use stat(), open() and iterdir().""" cls = DummyPath @@ -2215,7 +2263,7 @@ def _check(path, glob, expected): def test_rglob_common(self): def _check(glob, expected): - self.assertEqual(sorted(glob), sorted(P(BASE, q) for q in expected)) + self.assertEqual(set(glob), {P(BASE, q) for q in expected}) P = self.cls p = P(BASE) it = p.rglob("fileA") @@ -2399,7 +2447,7 @@ def test_glob_above_recursion_limit(self): # directory_depth > recursion_limit directory_depth = recursion_limit + 10 base = self.cls(BASE, 'deep') - path = self.cls(base, *(['d'] * directory_depth)) + path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) with set_recursion_limit(recursion_limit): @@ -2942,7 +2990,7 @@ def test_walk_above_recursion_limit(self): # directory_depth > recursion_limit directory_depth = recursion_limit + 10 base = self.cls(BASE, 'deep') - path = self.cls(base, *(['d'] * directory_depth)) + path = base.joinpath(*(['d'] * directory_depth)) path.mkdir(parents=True) with set_recursion_limit(recursion_limit): diff --git a/Lib/test/test_statistics.py b/Lib/test/test_statistics.py index b24fc3c3d077fe..bf2c254c9ee7d9 100644 --- a/Lib/test/test_statistics.py +++ b/Lib/test/test_statistics.py @@ -2302,10 +2302,12 @@ def test_error_cases(self): StatisticsError = statistics.StatisticsError with self.assertRaises(StatisticsError): geometric_mean([]) # empty input - with self.assertRaises(StatisticsError): - geometric_mean([3.5, 0.0, 5.25]) # zero input with self.assertRaises(StatisticsError): geometric_mean([3.5, -4.0, 5.25]) # negative input + with self.assertRaises(StatisticsError): + geometric_mean([0.0, -4.0, 5.25]) # negative input with zero + with self.assertRaises(StatisticsError): + geometric_mean([3.5, -math.inf, 5.25]) # negative infinity with self.assertRaises(StatisticsError): geometric_mean(iter([])) # empty iterator with self.assertRaises(TypeError): @@ -2328,6 +2330,12 @@ def test_special_values(self): with self.assertRaises(ValueError): geometric_mean([Inf, -Inf]) + # Cases with zero + self.assertEqual(geometric_mean([3, 0.0, 5]), 0.0) # Any zero gives a zero + self.assertEqual(geometric_mean([3, -0.0, 5]), 0.0) # Negative zero allowed + self.assertTrue(math.isnan(geometric_mean([0, NaN]))) # NaN beats zero + self.assertTrue(math.isnan(geometric_mean([0, Inf]))) # Because 0.0 * Inf -> NaN + def test_mixed_int_and_float(self): # Regression test for b.p.o. issue #28327 geometric_mean = statistics.geometric_mean diff --git a/Makefile.pre.in b/Makefile.pre.in index f57894a2118e74..6ca11f080dcc3f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1587,13 +1587,14 @@ regen-cases: $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/generate_cases.py \ $(CASESFLAG) \ - -n $(srcdir)/Include/opcode_ids.h.new \ -t $(srcdir)/Python/opcode_targets.h.new \ -m $(srcdir)/Include/internal/pycore_opcode_metadata.h.new \ -e $(srcdir)/Python/executor_cases.c.h.new \ -p $(srcdir)/Lib/_opcode_metadata.py.new \ -a $(srcdir)/Python/abstract_interp_cases.c.h.new \ $(srcdir)/Python/bytecodes.c + $(PYTHON_FOR_REGEN) \ + $(srcdir)/Tools/cases_generator/opcode_id_generator.py -o $(srcdir)/Include/opcode_ids.h.new $(srcdir)/Python/bytecodes.c $(PYTHON_FOR_REGEN) \ $(srcdir)/Tools/cases_generator/tier1_generator.py -o $(srcdir)/Python/generated_cases.c.h.new $(srcdir)/Python/bytecodes.c $(UPDATE_FILE) $(srcdir)/Python/generated_cases.c.h $(srcdir)/Python/generated_cases.c.h.new diff --git a/Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst b/Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst new file mode 100644 index 00000000000000..4f12d128f49fb3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-11-02-34-01.gh-issue-110109.RFCmHs.rst @@ -0,0 +1,3 @@ +Add private ``pathlib._PurePathBase`` class: a base class for +:class:`pathlib.PurePath` that omits certain magic methods. It may be made +public (along with ``_PathBase``) in future. diff --git a/Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst b/Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst new file mode 100644 index 00000000000000..263b13d1762bf1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-12-08-11-17-17.gh-issue-112540.Pm5egX.rst @@ -0,0 +1,2 @@ +The statistics.geometric_mean() function now returns zero for datasets +containing a zero. Formerly, it would raise an exception. diff --git a/Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst b/Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst new file mode 100644 index 00000000000000..0badace7928745 --- /dev/null +++ b/Misc/NEWS.d/next/macOS/2023-12-07-14-19-46.gh-issue-110820.DIxb_F.rst @@ -0,0 +1,3 @@ +Make sure the preprocessor definitions for ``ALIGNOF_MAX_ALIGN_T``, +``SIZEOF_LONG_DOUBLE`` and ``HAVE_GCC_ASM_FOR_X64`` are correct for +Universal 2 builds on macOS. diff --git a/PC/_wmimodule.cpp b/PC/_wmimodule.cpp index 215350acfb0d8e..5ab6dcb032550b 100644 --- a/PC/_wmimodule.cpp +++ b/PC/_wmimodule.cpp @@ -44,6 +44,7 @@ struct _query_data { LPCWSTR query; HANDLE writePipe; HANDLE readPipe; + HANDLE initEvent; HANDLE connectEvent; }; @@ -81,13 +82,16 @@ _query_thread(LPVOID param) IID_IWbemLocator, (LPVOID *)&locator ); } + if (SUCCEEDED(hr) && !SetEvent(data->initEvent)) { + hr = HRESULT_FROM_WIN32(GetLastError()); + } if (SUCCEEDED(hr)) { hr = locator->ConnectServer( bstr_t(L"ROOT\\CIMV2"), NULL, NULL, 0, NULL, 0, 0, &services ); } - if (!SetEvent(data->connectEvent)) { + if (SUCCEEDED(hr) && !SetEvent(data->connectEvent)) { hr = HRESULT_FROM_WIN32(GetLastError()); } if (SUCCEEDED(hr)) { @@ -193,6 +197,24 @@ _query_thread(LPVOID param) } +static DWORD +wait_event(HANDLE event, DWORD timeout) +{ + DWORD err = 0; + switch (WaitForSingleObject(event, timeout)) { + case WAIT_OBJECT_0: + break; + case WAIT_TIMEOUT: + err = WAIT_TIMEOUT; + break; + default: + err = GetLastError(); + break; + } + return err; +} + + /*[clinic input] _wmi.exec_query @@ -235,8 +257,11 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) Py_BEGIN_ALLOW_THREADS + data.initEvent = CreateEvent(NULL, TRUE, FALSE, NULL); data.connectEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - if (!data.connectEvent || !CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) { + if (!data.initEvent || !data.connectEvent || + !CreatePipe(&data.readPipe, &data.writePipe, NULL, 0)) + { err = GetLastError(); } else { hThread = CreateThread(NULL, 0, _query_thread, (LPVOID*)&data, 0, NULL); @@ -251,16 +276,12 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) // gh-112278: If current user doesn't have permission to query the WMI, the // function IWbemLocator::ConnectServer will hang for 5 seconds, and there // is no way to specify the timeout. So we use an Event object to simulate - // a timeout. - switch (WaitForSingleObject(data.connectEvent, 100)) { - case WAIT_OBJECT_0: - break; - case WAIT_TIMEOUT: - err = WAIT_TIMEOUT; - break; - default: - err = GetLastError(); - break; + // a timeout. The initEvent will be set after COM initialization, it will + // take a longer time when first initialized. The connectEvent will be set + // after connected to WMI. + err = wait_event(data.initEvent, 1000); + if (!err) { + err = wait_event(data.connectEvent, 100); } while (!err) { @@ -306,6 +327,7 @@ _wmi_exec_query_impl(PyObject *module, PyObject *query) } CloseHandle(hThread); + CloseHandle(data.initEvent); CloseHandle(data.connectEvent); hThread = NULL; diff --git a/Tools/cases_generator/cwriter.py b/Tools/cases_generator/cwriter.py index 0b7edd03fd9e47..34e39855a9b40a 100644 --- a/Tools/cases_generator/cwriter.py +++ b/Tools/cases_generator/cwriter.py @@ -48,8 +48,13 @@ def maybe_indent(self, txt: str) -> None: if offset <= self.indents[-1] or offset > 40: offset = self.indents[-1] + 4 self.indents.append(offset) - elif "{" in txt or is_label(txt): + if is_label(txt): self.indents.append(self.indents[-1] + 4) + elif "{" in txt: + if 'extern "C"' in txt: + self.indents.append(self.indents[-1]) + else: + self.indents.append(self.indents[-1] + 4) def emit_text(self, txt: str) -> None: self.out.write(txt) diff --git a/Tools/cases_generator/generate_cases.py b/Tools/cases_generator/generate_cases.py index 4b7f028970bd0c..d0fdc4a0aeb7b0 100644 --- a/Tools/cases_generator/generate_cases.py +++ b/Tools/cases_generator/generate_cases.py @@ -101,13 +101,6 @@ arg_parser.add_argument( "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT ) -arg_parser.add_argument( - "-n", - "--opcode_ids_h", - type=str, - help="Header file with opcode number definitions", - default=DEFAULT_OPCODE_IDS_H_OUTPUT, -) arg_parser.add_argument( "-t", "--opcode_targets_h", @@ -334,42 +327,8 @@ def map_op(op: int, name: str) -> None: self.opmap = opmap self.markers = markers - def write_opcode_ids( - self, opcode_ids_h_filename: str, opcode_targets_filename: str - ) -> None: - """Write header file that defined the opcode IDs""" - - with open(opcode_ids_h_filename, "w") as f: - # Create formatter - self.out = Formatter(f, 0) - - self.write_provenance_header() - - self.out.emit("") - self.out.emit("#ifndef Py_OPCODE_IDS_H") - self.out.emit("#define Py_OPCODE_IDS_H") - self.out.emit("#ifdef __cplusplus") - self.out.emit('extern "C" {') - self.out.emit("#endif") - self.out.emit("") - self.out.emit("/* Instruction opcodes for compiled code */") - - def define(name: str, opcode: int) -> None: - self.out.emit(f"#define {name:<38} {opcode:>3}") - - all_pairs: list[tuple[int, int, str]] = [] - # the second item in the tuple sorts the markers before the ops - all_pairs.extend((i, 1, name) for (name, i) in self.markers.items()) - all_pairs.extend((i, 2, name) for (name, i) in self.opmap.items()) - for i, _, name in sorted(all_pairs): - assert name is not None - define(name, i) - - self.out.emit("") - self.out.emit("#ifdef __cplusplus") - self.out.emit("}") - self.out.emit("#endif") - self.out.emit("#endif /* !Py_OPCODE_IDS_H */") + def write_opcode_targets(self, opcode_targets_filename: str) -> None: + """Write header file that defines the jump target table""" with open(opcode_targets_filename, "w") as f: # Create formatter @@ -885,7 +844,7 @@ def main() -> None: # These raise OSError if output can't be written a.assign_opcode_ids() - a.write_opcode_ids(args.opcode_ids_h, args.opcode_targets_h) + a.write_opcode_targets(args.opcode_targets_h) a.write_metadata(args.metadata, args.pymetadata) a.write_executor_instructions(args.executor_cases, args.emit_line_directives) a.write_abstract_interpreter_instructions( diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py new file mode 100644 index 00000000000000..76900d1efffd5d --- /dev/null +++ b/Tools/cases_generator/generators_common.py @@ -0,0 +1,19 @@ +from pathlib import Path +from typing import TextIO + +ROOT = Path(__file__).parent.parent.parent +DEFAULT_INPUT = (ROOT / "Python/bytecodes.c").absolute() + + +def root_relative_path(filename: str) -> str: + return Path(filename).relative_to(ROOT).as_posix() + + +def write_header(generator: str, source: str, outfile: TextIO) -> None: + outfile.write( + f"""// This file is generated by {root_relative_path(generator)} +// from: +// {source} +// Do not edit! +""" + ) diff --git a/Tools/cases_generator/opcode_id_generator.py b/Tools/cases_generator/opcode_id_generator.py new file mode 100644 index 00000000000000..a1f6f62156ebd3 --- /dev/null +++ b/Tools/cases_generator/opcode_id_generator.py @@ -0,0 +1,153 @@ +"""Generate the list of opcode IDs. +Reads the instruction definitions from bytecodes.c. +Writes the IDs to opcode._ids.h by default. +""" + +import argparse +import os.path +import sys + +from analyzer import ( + Analysis, + Instruction, + analyze_files, +) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, +) +from cwriter import CWriter +from typing import TextIO + + +DEFAULT_OUTPUT = ROOT / "Include/opcode_ids.h" + + +def generate_opcode_header(filenames: str, analysis: Analysis, outfile: TextIO) -> None: + write_header(__file__, filenames, outfile) + out = CWriter(outfile, 0, False) + out.emit("\n") + instmap: dict[str, int] = {} + + # 0 is reserved for cache entries. This helps debugging. + instmap["CACHE"] = 0 + + # 17 is reserved as it is the initial value for the specializing counter. + # This helps catch cases where we attempt to execute a cache. + instmap["RESERVED"] = 17 + + # 149 is RESUME - it is hard coded as such in Tools/build/deepfreeze.py + instmap["RESUME"] = 149 + instmap["INSTRUMENTED_LINE"] = 254 + + instrumented = [ + name for name in analysis.instructions if name.startswith("INSTRUMENTED") + ] + + # Special case: this instruction is implemented in ceval.c + # rather than bytecodes.c, so we need to add it explicitly + # here (at least until we add something to bytecodes.c to + # declare external instructions). + instrumented.append("INSTRUMENTED_LINE") + + specialized: set[str] = set() + no_arg: list[str] = [] + has_arg: list[str] = [] + + for family in analysis.families.values(): + specialized.update(inst.name for inst in family.members) + + for inst in analysis.instructions.values(): + name = inst.name + if name in specialized: + continue + if name in instrumented: + continue + if inst.properties.oparg: + has_arg.append(name) + else: + no_arg.append(name) + + # Specialized ops appear in their own section + # Instrumented opcodes are at the end of the valid range + min_internal = 150 + min_instrumented = 254 - (len(instrumented) - 1) + assert min_internal + len(specialized) < min_instrumented + + next_opcode = 1 + + def add_instruction(name: str) -> None: + nonlocal next_opcode + if name in instmap: + return # Pre-defined name + while next_opcode in instmap.values(): + next_opcode += 1 + instmap[name] = next_opcode + next_opcode += 1 + + for name in sorted(no_arg): + add_instruction(name) + for name in sorted(has_arg): + add_instruction(name) + # For compatibility + next_opcode = min_internal + for name in sorted(specialized): + add_instruction(name) + next_opcode = min_instrumented + for name in instrumented: + add_instruction(name) + + for op, name in enumerate(sorted(analysis.pseudos), 256): + instmap[name] = op + + assert 255 not in instmap.values() + + out.emit( + """#ifndef Py_OPCODE_IDS_H +#define Py_OPCODE_IDS_H +#ifdef __cplusplus +extern "C" { +#endif + +/* Instruction opcodes for compiled code */ +""" + ) + + def write_define(name: str, op: int) -> None: + out.emit(f"#define {name:<38} {op:>3}\n") + + for op, name in sorted([(op, name) for (name, op) in instmap.items()]): + write_define(name, op) + + out.emit("\n") + write_define("HAVE_ARGUMENT", len(no_arg)) + write_define("MIN_INSTRUMENTED_OPCODE", min_instrumented) + + out.emit("\n") + out.emit("#ifdef __cplusplus\n") + out.emit("}\n") + out.emit("#endif\n") + out.emit("#endif /* !Py_OPCODE_IDS_H */\n") + + +arg_parser = argparse.ArgumentParser( + description="Generate the header file with all opcode IDs.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) + +arg_parser.add_argument( + "-o", "--output", type=str, help="Generated code", default=DEFAULT_OUTPUT +) + +arg_parser.add_argument( + "input", nargs=argparse.REMAINDER, help="Instruction definition file(s)" +) + +if __name__ == "__main__": + args = arg_parser.parse_args() + if len(args.input) == 0: + args.input.append(DEFAULT_INPUT) + data = analyze_files(args.input) + with open(args.output, "w") as outfile: + generate_opcode_header(args.input, data, outfile) diff --git a/Tools/cases_generator/tier1_generator.py b/Tools/cases_generator/tier1_generator.py index eba926435d2415..9787403b3bbc47 100644 --- a/Tools/cases_generator/tier1_generator.py +++ b/Tools/cases_generator/tier1_generator.py @@ -17,33 +17,18 @@ StackItem, analysis_error, ) +from generators_common import ( + DEFAULT_INPUT, + ROOT, + write_header, +) from cwriter import CWriter from typing import TextIO, Iterator from lexer import Token from stack import StackOffset -HERE = os.path.dirname(__file__) -ROOT = os.path.join(HERE, "../..") -THIS = os.path.relpath(__file__, ROOT).replace(os.path.sep, "/") - -DEFAULT_INPUT = os.path.relpath(os.path.join(ROOT, "Python/bytecodes.c")) -DEFAULT_OUTPUT = os.path.relpath(os.path.join(ROOT, "Python/generated_cases.c.h")) - - -def write_header(filename: str, outfile: TextIO) -> None: - outfile.write( - f"""// This file is generated by {THIS} -// from: -// {filename} -// Do not edit! - -#ifdef TIER_TWO - #error "This file is for Tier 1 only" -#endif -#define TIER_ONE 1 -""" - ) +DEFAULT_OUTPUT = ROOT / "Python/generated_cases.c.h" FOOTER = "#undef TIER_ONE\n" @@ -351,7 +336,15 @@ def uses_this(inst: Instruction) -> bool: def generate_tier1( filenames: str, analysis: Analysis, outfile: TextIO, lines: bool ) -> None: - write_header(filenames, outfile) + write_header(__file__, filenames, outfile) + outfile.write( + """ +#ifdef TIER_TWO + #error "This file is for Tier 1 only" +#endif +#define TIER_ONE 1 +""" + ) out = CWriter(outfile, 2, lines) out.emit("\n") for name, inst in sorted(analysis.instructions.items()): diff --git a/configure b/configure index 8894122a11e86e..5f880d6d8edd96 100755 --- a/configure +++ b/configure @@ -27885,6 +27885,9 @@ printf "%s\n" "$TEST_MODULES" >&6; } # libatomic __atomic_fetch_or_8(), or not, depending on the C compiler and the # compiler flags. # +# gh-112779: On RISC-V, GCC 12 and earlier require libatomic support for 1-byte +# and 2-byte operations, but not for 8-byte operations. +# # Avoid #include or #include . The header # requires header which is only written below by AC_OUTPUT below. # If the check is done after AC_OUTPUT, modifying LIBS has no effect @@ -27924,12 +27927,19 @@ typedef intptr_t Py_ssize_t; int main() { - uint64_t byte; - _Py_atomic_store_uint64(&byte, 2); - if (_Py_atomic_or_uint64(&byte, 8) != 2) { + uint64_t value; + _Py_atomic_store_uint64(&value, 2); + if (_Py_atomic_or_uint64(&value, 8) != 2) { + return 1; // error + } + if (_Py_atomic_load_uint64(&value) != 10) { + return 1; // error + } + uint8_t byte = 0xb8; + if (_Py_atomic_or_uint8(&byte, 0x2d) != 0xb8) { return 1; // error } - if (_Py_atomic_load_uint64(&byte) != 10) { + if (_Py_atomic_load_uint8(&byte) != 0xbd) { return 1; // error } return 0; // all good diff --git a/configure.ac b/configure.ac index 1512e6d9e8c42a..c07d7ce6bdc918 100644 --- a/configure.ac +++ b/configure.ac @@ -7023,6 +7023,9 @@ AC_SUBST([TEST_MODULES]) # libatomic __atomic_fetch_or_8(), or not, depending on the C compiler and the # compiler flags. # +# gh-112779: On RISC-V, GCC 12 and earlier require libatomic support for 1-byte +# and 2-byte operations, but not for 8-byte operations. +# # Avoid #include or #include . The header # requires header which is only written below by AC_OUTPUT below. # If the check is done after AC_OUTPUT, modifying LIBS has no effect @@ -7052,12 +7055,19 @@ typedef intptr_t Py_ssize_t; int main() { - uint64_t byte; - _Py_atomic_store_uint64(&byte, 2); - if (_Py_atomic_or_uint64(&byte, 8) != 2) { + uint64_t value; + _Py_atomic_store_uint64(&value, 2); + if (_Py_atomic_or_uint64(&value, 8) != 2) { + return 1; // error + } + if (_Py_atomic_load_uint64(&value) != 10) { + return 1; // error + } + uint8_t byte = 0xb8; + if (_Py_atomic_or_uint8(&byte, 0x2d) != 0xb8) { return 1; // error } - if (_Py_atomic_load_uint64(&byte) != 10) { + if (_Py_atomic_load_uint8(&byte) != 0xbd) { return 1; // error } return 0; // all good