diff --git a/Doc/conf.py b/Doc/conf.py index 4ac6f6192a0806..07e0d113a24c10 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -177,6 +177,7 @@ ('c:type', '__int64'), ('c:type', 'unsigned __int64'), ('c:type', 'double'), + ('c:type', '_Float16'), # Standard C structures ('c:struct', 'in6_addr'), ('c:struct', 'in_addr'), diff --git a/Doc/library/struct.rst b/Doc/library/struct.rst index 644598d69d6ec4..fa0fb19d852f86 100644 --- a/Doc/library/struct.rst +++ b/Doc/library/struct.rst @@ -254,7 +254,7 @@ platform-dependent. +--------+--------------------------+--------------------+----------------+------------+ | ``N`` | :c:type:`size_t` | integer | | \(3) | +--------+--------------------------+--------------------+----------------+------------+ -| ``e`` | \(6) | float | 2 | \(4) | +| ``e`` | :c:expr:`_Float16` | float | 2 | \(4), \(6) | +--------+--------------------------+--------------------+----------------+------------+ | ``f`` | :c:expr:`float` | float | 4 | \(4) | +--------+--------------------------+--------------------+----------------+------------+ @@ -328,7 +328,9 @@ Notes: revision of the `IEEE 754 standard `_. It has a sign bit, a 5-bit exponent and 11-bit precision (with 10 bits explicitly stored), and can represent numbers between approximately ``6.1e-05`` and ``6.5e+04`` - at full precision. This type is not widely supported by C compilers: on a + at full precision. This type is not widely supported by C compilers: + it's available as :c:expr:`_Float16` type, if the compiler supports the Annex H + of the C23 standard. On a typical machine, an unsigned short can be used for storage, but not for math operations. See the Wikipedia page on the `half-precision floating-point format `_ for more information. diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index 16913289a02f59..2c83101b6b26fe 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -249,16 +249,7 @@ static inline void _Py_LeaveRecursiveCallTstate(PyThreadState *tstate) { PyAPI_FUNC(void) _Py_InitializeRecursionLimits(PyThreadState *tstate); -static inline int _Py_ReachedRecursionLimit(PyThreadState *tstate) { - uintptr_t here_addr = _Py_get_machine_stack_pointer(); - _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; - assert(_tstate->c_stack_hard_limit != 0); -#if _Py_STACK_GROWS_DOWN - return here_addr <= _tstate->c_stack_soft_limit; -#else - return here_addr >= _tstate->c_stack_soft_limit; -#endif -} +PyAPI_FUNC(int) _Py_ReachedRecursionLimit(PyThreadState *tstate); // Export for test_peg_generator PyAPI_FUNC(int) _Py_ReachedRecursionLimitWithMargin( diff --git a/Lib/ensurepip/__init__.py b/Lib/ensurepip/__init__.py index 6164ea62324cce..93b4e7a820f3ad 100644 --- a/Lib/ensurepip/__init__.py +++ b/Lib/ensurepip/__init__.py @@ -16,7 +16,8 @@ # policies recommend against bundling dependencies. For example, Fedora # installs wheel packages in the /usr/share/python-wheels/ directory and don't # install the ensurepip._bundled package. -if (_pkg_dir := sysconfig.get_config_var('WHEEL_PKG_DIR')) is not None: +_pkg_dir = sysconfig.get_config_var('WHEEL_PKG_DIR') +if _pkg_dir: _WHEEL_PKG_DIR = Path(_pkg_dir).resolve() else: _WHEEL_PKG_DIR = None diff --git a/Lib/test/test_ensurepip.py b/Lib/test/test_ensurepip.py index c62b340f6a340f..20a56ed715d8ab 100644 --- a/Lib/test/test_ensurepip.py +++ b/Lib/test/test_ensurepip.py @@ -7,6 +7,7 @@ import unittest import unittest.mock from pathlib import Path +from test.support import import_helper import ensurepip import ensurepip._uninstall @@ -36,6 +37,15 @@ def test_version_no_dir(self): # when the bundled pip wheel is used, we get _PIP_VERSION self.assertEqual(ensurepip._PIP_VERSION, ensurepip.version()) + def test_wheel_pkg_dir_none(self): + # gh-146310: empty or None WHEEL_PKG_DIR should not search CWD + for value in ('', None): + with unittest.mock.patch('sysconfig.get_config_var', + return_value=value) as get_config_var: + module = import_helper.import_fresh_module('ensurepip') + self.assertIsNone(module._WHEEL_PKG_DIR) + get_config_var.assert_called_once_with('WHEEL_PKG_DIR') + def test_selected_wheel_path_no_dir(self): pip_filename = f'pip-{ensurepip._PIP_VERSION}-py3-none-any.whl' with unittest.mock.patch.object(ensurepip, '_WHEEL_PKG_DIR', None): diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py index 328f2906f90159..69cb96cf4a0c1a 100644 --- a/Lib/test/test_lazy_import/__init__.py +++ b/Lib/test/test_lazy_import/__init__.py @@ -484,8 +484,8 @@ def my_filter(name): sys.set_lazy_imports_filter(my_filter) self.assertIs(sys.get_lazy_imports_filter(), my_filter) - def test_lazy_modules_attribute_is_set(self): - """sys.lazy_modules should be a set per PEP 810.""" + def test_lazy_modules_attribute_is_dict(self): + """sys.lazy_modules should be a dict per PEP 810.""" self.assertIsInstance(sys.lazy_modules, dict) @support.requires_subprocess() @@ -966,9 +966,24 @@ def test_module_added_to_lazy_modules_on_lazy_import(self): def test_lazy_modules_is_per_interpreter(self): """Each interpreter should have independent sys.lazy_modules.""" - # Basic test that sys.lazy_modules exists and is a set + # Basic test that sys.lazy_modules exists and is a dict self.assertIsInstance(sys.lazy_modules, dict) + def test_lazy_module_without_children_is_tracked(self): + code = textwrap.dedent(""" + import sys + lazy import json + assert "json" in sys.lazy_modules, ( + f"expected 'json' in sys.lazy_modules, got {set(sys.lazy_modules)}" + ) + assert sys.lazy_modules["json"] == set(), ( + f"expected empty set for sys.lazy_modules['json'], " + f"got {sys.lazy_modules['json']!r}" + ) + print("OK") + """) + assert_python_ok("-c", code) + @support.requires_subprocess() class CommandLineAndEnvVarTests(unittest.TestCase): @@ -1575,7 +1590,7 @@ def access_modules(idx): self.assertEqual(result.returncode, 0, f"stdout: {result.stdout}, stderr: {result.stderr}") self.assertIn("OK", result.stdout) - def test_concurrent_lazy_modules_set_updates(self): + def test_concurrent_lazy_modules_dict_updates(self): """Multiple threads creating lazy imports should safely update sys.lazy_modules.""" code = textwrap.dedent(""" import sys diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-14-20-56.gh-issue-145059.aB3xKm.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-14-20-56.gh-issue-145059.aB3xKm.rst new file mode 100644 index 00000000000000..e2db5a83a1a9e3 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-17-14-20-56.gh-issue-145059.aB3xKm.rst @@ -0,0 +1 @@ +Fixed ``sys.lazy_modules`` to include lazy modules without submodules. Patch by Bartosz Sławecki. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-27-17-14-18.gh-issue-126910.hooVFQ.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-27-17-14-18.gh-issue-126910.hooVFQ.rst new file mode 100644 index 00000000000000..e3ddf39408804b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-27-17-14-18.gh-issue-126910.hooVFQ.rst @@ -0,0 +1 @@ +Set frame pointers in ``aarch64-unknown-linux-gnu`` JIT code, allowing most native profilers and debuggers to unwind through them. Patch by Diego Russo diff --git a/Misc/NEWS.d/next/Library/2026-03-24-03-49-50.gh-issue-146310.WhlDir.rst b/Misc/NEWS.d/next/Library/2026-03-24-03-49-50.gh-issue-146310.WhlDir.rst new file mode 100644 index 00000000000000..b712595585201b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-24-03-49-50.gh-issue-146310.WhlDir.rst @@ -0,0 +1,2 @@ +The :mod:`ensurepip` module no longer looks for ``pip-*.whl`` wheel packages +in the current directory. diff --git a/Python/ceval.c b/Python/ceval.c index b4c57b65d13d18..f95900ae01a6af 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1201,6 +1201,19 @@ _PyEval_GetIter(_PyStackRef iterable, _PyStackRef *index_or_null, int yield_from return PyStackRef_FromPyObjectSteal(iter_o); } +Py_NO_INLINE int +_Py_ReachedRecursionLimit(PyThreadState *tstate) { + uintptr_t here_addr = _Py_get_machine_stack_pointer(); + _PyThreadStateImpl *_tstate = (_PyThreadStateImpl *)tstate; + assert(_tstate->c_stack_hard_limit != 0); +#if _Py_STACK_GROWS_DOWN + return here_addr <= _tstate->c_stack_soft_limit; +#else + return here_addr >= _tstate->c_stack_soft_limit; +#endif +} + + #if (defined(__GNUC__) && __GNUC__ >= 10 && !defined(__clang__)) && defined(__x86_64__) /* * gh-129987: The SLP autovectorizer can cause poor code generation for diff --git a/Python/import.c b/Python/import.c index f615fe37ba8fbe..e298fbee536c1b 100644 --- a/Python/import.c +++ b/Python/import.c @@ -4377,6 +4377,12 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, Py_ssize_t dot = PyUnicode_FindChar(name, '.', 0, PyUnicode_GET_LENGTH(name), -1); if (dot < 0) { + PyObject *lazy_submodules = ensure_lazy_submodules( + (PyDictObject *)lazy_modules, name); + if (lazy_submodules == NULL) { + goto done; + } + Py_DECREF(lazy_submodules); ret = 0; goto done; } diff --git a/Tools/jit/_optimizers.py b/Tools/jit/_optimizers.py index 83c878d8fe205b..ef28e0c0ddeac8 100644 --- a/Tools/jit/_optimizers.py +++ b/Tools/jit/_optimizers.py @@ -162,6 +162,7 @@ class Optimizer: label_prefix: str symbol_prefix: str re_global: re.Pattern[str] + frame_pointers: bool # The first block in the linked list: _root: _Block = dataclasses.field(init=False, default_factory=_Block) _labels: dict[str, _Block] = dataclasses.field(init=False, default_factory=dict) @@ -193,6 +194,7 @@ class Optimizer: _re_small_const_1 = _RE_NEVER_MATCH _re_small_const_2 = _RE_NEVER_MATCH const_reloc = "" + _frame_pointer_modify: typing.ClassVar[re.Pattern[str]] = _RE_NEVER_MATCH def __post_init__(self) -> None: # Split the code into a linked list of basic blocks. A basic block is an @@ -553,6 +555,16 @@ def _small_const_2(self, inst: Instruction) -> tuple[str, Instruction | None]: def _small_consts_match(self, inst1: Instruction, inst2: Instruction) -> bool: raise NotImplementedError() + def _validate(self) -> None: + for block in self._blocks(): + if not block.instructions: + continue + for inst in block.instructions: + if self.frame_pointers: + assert ( + self._frame_pointer_modify.match(inst.text) is None + ), "Frame pointer should not be modified" + def run(self) -> None: """Run this optimizer.""" self._insert_continue_label() @@ -565,6 +577,7 @@ def run(self) -> None: self._remove_unreachable() self._fixup_external_labels() self._fixup_constants() + self._validate() self.path.write_text(self._body()) @@ -595,6 +608,7 @@ class OptimizerAArch64(Optimizer): # pylint: disable = too-few-public-methods r"\s*(?Pldr)\s+.*(?P_JIT_OP(ARG|ERAND(0|1))_(16|32)).*" ) const_reloc = "CUSTOM_AARCH64_CONST" + _frame_pointer_modify = re.compile(r"\s*stp\s+x29.*") def _get_reg(self, inst: Instruction) -> str: _, rest = inst.text.split(inst.name) @@ -649,4 +663,5 @@ class OptimizerX86(Optimizer): # pylint: disable = too-few-public-methods # https://www.felixcloutier.com/x86/jmp _re_jump = re.compile(r"\s*jmp\s+(?P[\w.]+)") # https://www.felixcloutier.com/x86/ret - _re_return = re.compile(r"\s*ret\b") + _re_return = re.compile(r"\s*retq?\b") + _frame_pointer_modify = re.compile(r"\s*movq?\s+%(\w+),\s+%rbp.*") diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py index fa98dcb5a40851..787fcf53260f3d 100644 --- a/Tools/jit/_targets.py +++ b/Tools/jit/_targets.py @@ -176,8 +176,9 @@ async def _compile( f"{s}", f"{c}", ] + is_shim = opname == "shim" if self.frame_pointers: - frame_pointer = "all" if opname == "shim" else "reserved" + frame_pointer = "all" if is_shim else "reserved" args_s += ["-Xclang", f"-mframe-pointer={frame_pointer}"] args_s += self.args # Allow user-provided CFLAGS to override any defaults @@ -185,12 +186,14 @@ async def _compile( await _llvm.run( "clang", args_s, echo=self.verbose, llvm_version=self.llvm_version ) - self.optimizer( - s, - label_prefix=self.label_prefix, - symbol_prefix=self.symbol_prefix, - re_global=self.re_global, - ).run() + if not is_shim: + self.optimizer( + s, + label_prefix=self.label_prefix, + symbol_prefix=self.symbol_prefix, + re_global=self.re_global, + frame_pointers=self.frame_pointers, + ).run() args_o = [f"--target={self.triple}", "-c", "-o", f"{o}", f"{s}"] await _llvm.run( "clang", args_o, echo=self.verbose, llvm_version=self.llvm_version @@ -593,7 +596,9 @@ def get_target(host: str) -> _COFF32 | _COFF64 | _ELF | _MachO: # -mno-outline-atomics: Keep intrinsics from being emitted. args = ["-fpic", "-mno-outline-atomics", "-fno-plt"] optimizer = _optimizers.OptimizerAArch64 - target = _ELF(host, condition, args=args, optimizer=optimizer) + target = _ELF( + host, condition, args=args, optimizer=optimizer, frame_pointers=True + ) elif re.fullmatch(r"i686-pc-windows-msvc", host): host = "i686-pc-windows-msvc" condition = "defined(_M_IX86)"