diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 8f7b9ac15a0d22..d31d0ce9c85e9a 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -3413,11 +3413,6 @@ features: :class:`!statx_result` has the following attributes: - .. attribute:: stx_mask - - Bitmask of :const:`STATX_* ` constants specifying the - information retrieved, which may differ from what was requested. - .. attribute:: stx_atime Time of most recent access expressed in seconds. @@ -3442,9 +3437,9 @@ features: .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.11. - .. attribute:: stx_atomic_write_unit_min + .. attribute:: stx_atomic_write_unit_max - Minimum size for direct I/O with torn-write protection. + Maximum size for direct I/O with torn-write protection. Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from :attr:`~statx_result.stx_mask`. @@ -3452,25 +3447,25 @@ features: .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.11. - .. attribute:: stx_atomic_write_unit_max + .. attribute:: stx_atomic_write_unit_max_opt - Maximum size for direct I/O with torn-write protection. + Maximum optimized size for direct I/O with torn-write protection. Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.11. + userspace API headers >= 6.16. - .. attribute:: stx_atomic_write_unit_max_opt + .. attribute:: stx_atomic_write_unit_min - Maximum optimized size for direct I/O with torn-write protection. + Minimum size for direct I/O with torn-write protection. Equal to ``None`` if :data:`STATX_WRITE_ATOMIC` is missing from :attr:`~statx_result.stx_mask`. .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel - userspace API headers >= 6.16. + userspace API headers >= 6.11. .. attribute:: stx_attributes @@ -3536,9 +3531,9 @@ features: Minor number of the device on which this file resides. - .. attribute:: stx_dio_offset_align + .. attribute:: stx_dio_mem_align - Direct I/O file offset alignment requirement. + Direct I/O memory buffer alignment requirement. Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from :attr:`~statx_result.stx_mask`. @@ -3546,9 +3541,9 @@ features: .. availability:: Linux >= 4.11 with glibc >= 2.28 and build-time kernel userspace API headers >= 6.1. - .. attribute:: stx_dio_mem_align + .. attribute:: stx_dio_offset_align - Direct I/O memory buffer alignment requirement. + Direct I/O file offset alignment requirement. Equal to ``None`` if :data:`STATX_DIOALIGN` is missing from :attr:`~statx_result.stx_mask`. @@ -3580,6 +3575,11 @@ features: Equal to ``None`` if :data:`STATX_INO` is missing from :attr:`~statx_result.stx_mask`. + .. attribute:: stx_mask + + Bitmask of :const:`STATX_* ` constants specifying the + information retrieved, which may differ from what was requested. + .. attribute:: stx_mnt_id Mount identifier. @@ -3594,6 +3594,9 @@ features: File mode: file type and file mode bits (permissions). + Equal to ``None`` if :data:`STATX_TYPE | STATX_MODE ` + is missing from :attr:`~statx_result.stx_mask`. + .. attribute:: stx_mtime Time of most recent content modification expressed in seconds. diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index efe9fb3b6c7c6a..94fcb1d8aee52b 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -50,27 +50,58 @@ extern "C" { CPython refcounting operations on it! */ +#define Py_INT_TAG 3 +#define Py_TAG_INVALID 2 +#define Py_TAG_REFCNT 1 +#define Py_TAG_BITS 3 -#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) +#define Py_TAGGED_SHIFT 2 -#define Py_TAG_BITS 0 +#if !defined(Py_GIL_DISABLED) && defined(Py_STACKREF_DEBUG) PyAPI_FUNC(PyObject *) _Py_stackref_get_object(_PyStackRef ref); PyAPI_FUNC(PyObject *) _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber); -PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, const char *filename, int linenumber); +PyAPI_FUNC(_PyStackRef) _Py_stackref_create(PyObject *obj, uint16_t flags, const char *filename, int linenumber); PyAPI_FUNC(void) _Py_stackref_record_borrow(_PyStackRef ref, const char *filename, int linenumber); extern void _Py_stackref_associate(PyInterpreterState *interp, PyObject *obj, _PyStackRef ref); static const _PyStackRef PyStackRef_NULL = { .index = 0 }; -static const _PyStackRef PyStackRef_ERROR = { .index = 2 }; +static const _PyStackRef PyStackRef_ERROR = { .index = (1 << Py_TAGGED_SHIFT) }; -// Use the first 3 even numbers for None, True and False. -// Odd numbers are reserved for (tagged) integers -#define PyStackRef_None ((_PyStackRef){ .index = 4 } ) -#define PyStackRef_False ((_PyStackRef){ .index = 6 }) -#define PyStackRef_True ((_PyStackRef){ .index = 8 }) +#define PyStackRef_None ((_PyStackRef){ .index = (2 << Py_TAGGED_SHIFT) } ) +#define PyStackRef_False ((_PyStackRef){ .index = (3 << Py_TAGGED_SHIFT) }) +#define PyStackRef_True ((_PyStackRef){ .index = (4 << Py_TAGGED_SHIFT) }) + +#define INITIAL_STACKREF_INDEX (5 << Py_TAGGED_SHIFT) + +static inline _PyStackRef +PyStackRef_Wrap(void *ptr) +{ + assert(ptr != NULL); +#ifdef Py_DEBUG + assert(((uint64_t)ptr & Py_TAG_BITS) == 0); + return (_PyStackRef){ .index = ((uint64_t)ptr) | Py_TAG_INVALID }; +#else + return (_PyStackRef){ .index = (uint64_t)ptr }; +#endif +} + +static inline void * +PyStackRef_Unwrap(_PyStackRef ref) +{ +#ifdef Py_DEBUG + assert ((ref.index & Py_TAG_BITS) == Py_TAG_INVALID); + return (void *)(ref.index & ~Py_TAG_BITS); +#else + return (void *)(ref.index); +#endif +} -#define INITIAL_STACKREF_INDEX 10 +static inline int +PyStackRef_RefcountOnObject(_PyStackRef ref) +{ + return (ref.index & Py_TAG_REFCNT) == 0; +} static inline int PyStackRef_IsNull(_PyStackRef ref) @@ -81,7 +112,13 @@ PyStackRef_IsNull(_PyStackRef ref) static inline bool PyStackRef_IsError(_PyStackRef ref) { - return ref.index == 2; + return ref.index == (1 << Py_TAGGED_SHIFT); +} + +static inline bool +PyStackRef_IsMalformed(_PyStackRef ref) +{ + return (ref.index & Py_TAG_BITS) == Py_TAG_INVALID; } static inline bool @@ -112,7 +149,7 @@ PyStackRef_IsNone(_PyStackRef ref) static inline bool PyStackRef_IsTaggedInt(_PyStackRef ref) { - return (ref.index & 1) == 1; + return (ref.index & Py_TAG_BITS) == Py_INT_TAG; } static inline PyObject * @@ -123,50 +160,68 @@ _PyStackRef_AsPyObjectBorrow(_PyStackRef ref, const char *filename, int linenumb _Py_stackref_record_borrow(ref, filename, linenumber); return _Py_stackref_get_object(ref); } - #define PyStackRef_AsPyObjectBorrow(REF) _PyStackRef_AsPyObjectBorrow((REF), __FILE__, __LINE__) static inline PyObject * _PyStackRef_AsPyObjectSteal(_PyStackRef ref, const char *filename, int linenumber) { - return _Py_stackref_close(ref, filename, linenumber); + PyObject *obj = _Py_stackref_close(ref, filename, linenumber); + if (PyStackRef_RefcountOnObject(ref)) { + return obj; + } + return Py_NewRef(obj); } #define PyStackRef_AsPyObjectSteal(REF) _PyStackRef_AsPyObjectSteal((REF), __FILE__, __LINE__) static inline _PyStackRef _PyStackRef_FromPyObjectNew(PyObject *obj, const char *filename, int linenumber) { - Py_INCREF(obj); - return _Py_stackref_create(obj, filename, linenumber); + assert(obj != NULL); + uint16_t flags = 0; + if (!_Py_IsImmortal(obj)) { + _Py_INCREF_MORTAL(obj); + } else { + flags = Py_TAG_REFCNT; + } + return _Py_stackref_create(obj, flags, filename, linenumber); } #define PyStackRef_FromPyObjectNew(obj) _PyStackRef_FromPyObjectNew(_PyObject_CAST(obj), __FILE__, __LINE__) static inline _PyStackRef _PyStackRef_FromPyObjectSteal(PyObject *obj, const char *filename, int linenumber) { - return _Py_stackref_create(obj, filename, linenumber); + assert(obj != NULL); + uint16_t flags = 0; + if (_Py_IsImmortal(obj)) { + flags = Py_TAG_REFCNT; + } + return _Py_stackref_create(obj, flags, filename, linenumber); } #define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj), __FILE__, __LINE__) static inline _PyStackRef _PyStackRef_FromPyObjectBorrow(PyObject *obj, const char *filename, int linenumber) { - return _Py_stackref_create(obj, filename, linenumber); + return _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber); } #define PyStackRef_FromPyObjectBorrow(obj) _PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj), __FILE__, __LINE__) static inline void _PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber) { + assert(!PyStackRef_IsError(ref)); + assert(!PyStackRef_IsNull(ref)); if (PyStackRef_IsTaggedInt(ref)) { return; } PyObject *obj = _Py_stackref_close(ref, filename, linenumber); - Py_DECREF(obj); + assert(Py_REFCNT(obj) > 0); + if (PyStackRef_RefcountOnObject(ref)) { + Py_DECREF(obj); + } } #define PyStackRef_CLOSE(REF) _PyStackRef_CLOSE((REF), __FILE__, __LINE__) - static inline void _PyStackRef_XCLOSE(_PyStackRef ref, const char *filename, int linenumber) { @@ -182,31 +237,46 @@ static inline _PyStackRef _PyStackRef_DUP(_PyStackRef ref, const char *filename, int linenumber) { assert(!PyStackRef_IsError(ref)); + assert(!PyStackRef_IsNull(ref)); if (PyStackRef_IsTaggedInt(ref)) { return ref; } - else { - PyObject *obj = _Py_stackref_get_object(ref); + PyObject *obj = _Py_stackref_get_object(ref); + uint16_t flags = 0; + if (PyStackRef_RefcountOnObject(ref)) { Py_INCREF(obj); - return _Py_stackref_create(obj, filename, linenumber); + } else { + flags = Py_TAG_REFCNT; } + return _Py_stackref_create(obj, flags, filename, linenumber); } #define PyStackRef_DUP(REF) _PyStackRef_DUP(REF, __FILE__, __LINE__) -extern void _PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *filename, int linenumber); -#define PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT) _PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT, __FILE__, __LINE__) - -static inline _PyStackRef -PyStackRef_MakeHeapSafe(_PyStackRef ref) +static inline void +_PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *filename, int linenumber) { - return ref; + assert(!PyStackRef_IsError(ref)); + assert(!PyStackRef_IsNull(ref)); + assert(!PyStackRef_IsTaggedInt(ref)); + PyObject *obj = _Py_stackref_close(ref, filename, linenumber); + if (PyStackRef_RefcountOnObject(ref)) { + _Py_DECREF_SPECIALIZED(obj, destruct); + } } +#define PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT) _PyStackRef_CLOSE_SPECIALIZED(REF, DESTRUCT, __FILE__, __LINE__) static inline _PyStackRef -PyStackRef_Borrow(_PyStackRef ref) +_PyStackRef_Borrow(_PyStackRef ref, const char *filename, int linenumber) { - return PyStackRef_DUP(ref); + assert(!PyStackRef_IsError(ref)); + assert(!PyStackRef_IsNull(ref)); + if (PyStackRef_IsTaggedInt(ref)) { + return ref; + } + PyObject *obj = _Py_stackref_get_object(ref); + return _Py_stackref_create(obj, Py_TAG_REFCNT, filename, linenumber); } +#define PyStackRef_Borrow(REF) _PyStackRef_Borrow((REF), __FILE__, __LINE__) #define PyStackRef_CLEAR(REF) \ do { \ @@ -219,28 +289,47 @@ PyStackRef_Borrow(_PyStackRef ref) static inline _PyStackRef _PyStackRef_FromPyObjectStealMortal(PyObject *obj, const char *filename, int linenumber) { + assert(obj != NULL); assert(!_Py_IsImmortal(obj)); - return _Py_stackref_create(obj, filename, linenumber); + return _Py_stackref_create(obj, 0, filename, linenumber); } #define PyStackRef_FromPyObjectStealMortal(obj) _PyStackRef_FromPyObjectStealMortal(_PyObject_CAST(obj), __FILE__, __LINE__) static inline bool PyStackRef_IsHeapSafe(_PyStackRef ref) { - return true; + if ((ref.index & Py_TAG_BITS) != Py_TAG_REFCNT || PyStackRef_IsNull(ref)) { + // Tagged ints and ERROR are included. + return true; + } + + PyObject *obj = _Py_stackref_get_object(ref); + return _Py_IsImmortal(obj); } +static inline _PyStackRef +_PyStackRef_MakeHeapSafe(_PyStackRef ref, const char *filename, int linenumber) +{ + if (PyStackRef_IsHeapSafe(ref)) { + return ref; + } + + PyObject *obj = _Py_stackref_close(ref, filename, linenumber); + Py_INCREF(obj); + return _Py_stackref_create(obj, 0, filename, linenumber); +} +#define PyStackRef_MakeHeapSafe(REF) _PyStackRef_MakeHeapSafe(REF, __FILE__, __LINE__) + static inline _PyStackRef _PyStackRef_FromPyObjectNewMortal(PyObject *obj, const char *filename, int linenumber) { + assert(obj != NULL); assert(!_Py_IsStaticImmortal(obj)); Py_INCREF(obj); - return _Py_stackref_create(obj, filename, linenumber); + return _Py_stackref_create(obj, 0, filename, linenumber); } #define PyStackRef_FromPyObjectNewMortal(obj) _PyStackRef_FromPyObjectNewMortal(_PyObject_CAST(obj), __FILE__, __LINE__) -#define PyStackRef_RefcountOnObject(REF) 1 - extern int PyStackRef_Is(_PyStackRef a, _PyStackRef b); extern bool PyStackRef_IsTaggedInt(_PyStackRef ref); @@ -257,11 +346,6 @@ PyStackRef_IsNullOrInt(_PyStackRef ref); #else -#define Py_INT_TAG 3 -#define Py_TAG_INVALID 2 -#define Py_TAG_REFCNT 1 -#define Py_TAG_BITS 3 - static const _PyStackRef PyStackRef_ERROR = { .bits = Py_TAG_INVALID }; /* Wrap a pointer in a stack ref. @@ -273,6 +357,7 @@ PyStackRef_Wrap(void *ptr) { assert(ptr != NULL); #ifdef Py_DEBUG + assert(((uintptr_t)ptr & Py_TAG_BITS) == 0); return (_PyStackRef){ .bits = ((uintptr_t)ptr) | Py_TAG_INVALID }; #else return (_PyStackRef){ .bits = (uintptr_t)ptr }; @@ -318,8 +403,8 @@ PyStackRef_IsTaggedInt(_PyStackRef i) static inline _PyStackRef PyStackRef_TagInt(intptr_t i) { - assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << 2), 2) == i); - return (_PyStackRef){ .bits = ((((uintptr_t)i) << 2) | Py_INT_TAG) }; + assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << Py_TAGGED_SHIFT), Py_TAGGED_SHIFT) == i); + return (_PyStackRef){ .bits = ((((uintptr_t)i) << Py_TAGGED_SHIFT) | Py_INT_TAG) }; } static inline intptr_t @@ -327,7 +412,7 @@ PyStackRef_UntagInt(_PyStackRef i) { assert(PyStackRef_IsTaggedInt(i)); intptr_t val = (intptr_t)i.bits; - return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, 2); + return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, Py_TAGGED_SHIFT); } @@ -335,8 +420,8 @@ static inline _PyStackRef PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref) { assert((ref.bits & Py_TAG_BITS) == Py_INT_TAG); // Is tagged int - assert((ref.bits & (~Py_TAG_BITS)) != (INT_MAX & (~Py_TAG_BITS))); // Isn't about to overflow - return (_PyStackRef){ .bits = ref.bits + 4 }; + assert((ref.bits & (~Py_TAG_BITS)) != (INTPTR_MAX & (~Py_TAG_BITS))); // Isn't about to overflow + return (_PyStackRef){ .bits = ref.bits + (1 << Py_TAGGED_SHIFT) }; } #define PyStackRef_IsDeferredOrTaggedInt(ref) (((ref).bits & Py_TAG_REFCNT) != 0) diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 9a40c5c2a1f1f3..ddb8a63095bce5 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -748,7 +748,7 @@ def check_statx_attributes(self, filename): if name.startswith('STATX_'): maximal_mask |= getattr(os, name) result = os.statx(filename, maximal_mask) - basic_result = os.stat(filename) + stat_result = os.stat(filename) time_attributes = ('stx_atime', 'stx_btime', 'stx_ctime', 'stx_mtime') # gh-83714: stx_btime can be None on tmpfs even if STATX_BTIME mask @@ -757,62 +757,108 @@ def check_statx_attributes(self, filename): if getattr(result, name) is not None] self.check_timestamp_agreement(result, time_attributes) - # Check that valid attributes match os.stat. + def getmask(name): + return getattr(os, name, 0) + requirements = ( - ('stx_mode', os.STATX_TYPE | os.STATX_MODE), - ('stx_nlink', os.STATX_NLINK), - ('stx_uid', os.STATX_UID), - ('stx_gid', os.STATX_GID), ('stx_atime', os.STATX_ATIME), ('stx_atime_ns', os.STATX_ATIME), - ('stx_mtime', os.STATX_MTIME), - ('stx_mtime_ns', os.STATX_MTIME), + ('stx_atomic_write_segments_max', getmask('STATX_WRITE_ATOMIC')), + ('stx_atomic_write_unit_max', getmask('STATX_WRITE_ATOMIC')), + ('stx_atomic_write_unit_max_opt', getmask('STATX_WRITE_ATOMIC')), + ('stx_atomic_write_unit_min', getmask('STATX_WRITE_ATOMIC')), + ('stx_attributes', 0), + ('stx_attributes_mask', 0), + ('stx_blksize', 0), + ('stx_blocks', os.STATX_BLOCKS), + ('stx_btime', os.STATX_BTIME), + ('stx_btime_ns', os.STATX_BTIME), ('stx_ctime', os.STATX_CTIME), ('stx_ctime_ns', os.STATX_CTIME), + ('stx_dev', 0), + ('stx_dev_major', 0), + ('stx_dev_minor', 0), + ('stx_dio_mem_align', getmask('STATX_DIOALIGN')), + ('stx_dio_offset_align', getmask('STATX_DIOALIGN')), + ('stx_dio_read_offset_align', getmask('STATX_DIO_READ_ALIGN')), + ('stx_gid', os.STATX_GID), ('stx_ino', os.STATX_INO), - ('stx_size', os.STATX_SIZE), - ('stx_blocks', os.STATX_BLOCKS), - ('stx_birthtime', os.STATX_BTIME), - ('stx_birthtime_ns', os.STATX_BTIME), - # unconditionally valid members - ('stx_blksize', 0), + ('stx_mask', 0), + ('stx_mnt_id', getmask('STATX_MNT_ID')), + ('stx_mode', os.STATX_TYPE | os.STATX_MODE), + ('stx_mtime', os.STATX_MTIME), + ('stx_mtime_ns', os.STATX_MTIME), + ('stx_nlink', os.STATX_NLINK), ('stx_rdev', 0), - ('stx_dev', 0), + ('stx_rdev_major', 0), + ('stx_rdev_minor', 0), + ('stx_size', os.STATX_SIZE), + ('stx_subvol', getmask('STATX_SUBVOL')), + ('stx_uid', os.STATX_UID), ) - for name, bits in requirements: - st_name = "st_" + name[4:] - if result.stx_mask & bits == bits and hasattr(basic_result, st_name): - x = getattr(result, name) - b = getattr(basic_result, st_name) - self.assertEqual(type(x), type(b)) - if isinstance(x, float): - self.assertAlmostEqual(x, b, msg=name) + optional_members = { + 'stx_atomic_write_segments_max', + 'stx_atomic_write_unit_max', + 'stx_atomic_write_unit_max_opt', + 'stx_atomic_write_unit_min', + 'stx_dio_mem_align', + 'stx_dio_offset_align', + 'stx_dio_read_offset_align', + 'stx_mnt_id', + 'stx_subvol', + } + float_type = { + 'stx_atime', + 'stx_btime', + 'stx_ctime', + 'stx_mtime', + } + + members = set(name for name in dir(result) + if name.startswith('stx_')) + tested = set(name for name, mask in requirements) + if members - tested: + raise ValueError(f"statx members not tested: {members - tested}") + + for name, mask in requirements: + with self.subTest(name=name): + try: + x = getattr(result, name) + except AttributeError: + if name in optional_members: + continue + else: + raise + + if not(result.stx_mask & mask == mask): + self.assertIsNone(x) + continue + + if name in float_type: + self.assertIsInstance(x, float) else: - self.assertEqual(x, b, msg=name) + self.assertIsInstance(x, int) + + # Compare with stat_result + try: + b = getattr(stat_result, "st_" + name[4:]) + except AttributeError: + pass + else: + self.assertEqual(type(x), type(b)) + if isinstance(x, float): + self.assertAlmostEqual(x, b) + else: + self.assertEqual(x, b) self.assertEqual(result.stx_rdev_major, os.major(result.stx_rdev)) self.assertEqual(result.stx_rdev_minor, os.minor(result.stx_rdev)) self.assertEqual(result.stx_dev_major, os.major(result.stx_dev)) self.assertEqual(result.stx_dev_minor, os.minor(result.stx_dev)) - members = [name for name in dir(result) - if name.startswith('stx_')] - for name in members: - try: - setattr(result, name, 1) - self.fail("No exception raised") - except AttributeError: - pass - self.assertEqual(result.stx_attributes & result.stx_attributes_mask, result.stx_attributes) - # statx_result is not a tuple or tuple-like object. - with self.assertRaisesRegex(TypeError, 'not subscriptable'): - result[0] - with self.assertRaisesRegex(TypeError, 'cannot unpack'): - _, _ = result - @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') def test_statx_attributes(self): self.check_statx_attributes(self.fname) @@ -829,6 +875,27 @@ def test_statx_attributes_bytes(self): def test_statx_attributes_pathlike(self): self.check_statx_attributes(FakePath(self.fname)) + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') + def test_statx_result(self): + result = os.statx(self.fname, os.STATX_BASIC_STATS) + + # Check that attributes are read-only + members = [name for name in dir(result) + if name.startswith('stx_')] + for name in members: + try: + setattr(result, name, 1) + except AttributeError: + pass + else: + self.fail("No exception raised") + + # statx_result is not a tuple or tuple-like object. + with self.assertRaisesRegex(TypeError, 'not subscriptable'): + result[0] + with self.assertRaisesRegex(TypeError, 'cannot unpack'): + _, _ = result + @unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()') def test_statvfs_attributes(self): result = os.statvfs(self.fname) diff --git a/Misc/ACKS b/Misc/ACKS index 6876380e0ba8d2..f5f15f2eb7ea24 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1921,6 +1921,7 @@ Tim Tisdall Jason Tishler Christian Tismer Jim Tittsler +Abhishek Tiwari Frank J. Tobin James Tocknell Bennett Todd diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-03-17-51-43.gh-issue-139475._684ED.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-03-17-51-43.gh-issue-139475._684ED.rst new file mode 100644 index 00000000000000..f4d50b7d0207a0 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-03-17-51-43.gh-issue-139475._684ED.rst @@ -0,0 +1,2 @@ +Changes in stackref debugging mode when ``Py_STACKREF_DEBUG`` is set. We use +the same pattern of refcounting for stackrefs as in production build. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-11-30-16.gh-issue-135904.3WE5oW.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-11-30-16.gh-issue-135904.3WE5oW.rst new file mode 100644 index 00000000000000..b52a57dba4acae --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-11-30-16.gh-issue-135904.3WE5oW.rst @@ -0,0 +1,3 @@ +Add special labels to the assembly created during stencil creation to +support relocations that the native object file format does not support. +Specifically, 19 bit branches for AArch64 in Mach-O object files. diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-23-26-37.gh-issue-140443.wT5i1A.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-23-26-37.gh-issue-140443.wT5i1A.rst new file mode 100644 index 00000000000000..a1fff8fef7ebe2 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-23-26-37.gh-issue-140443.wT5i1A.rst @@ -0,0 +1,5 @@ +The logarithm functions (such as :func:`math.log10` and :func:`math.log`) may now produce +slightly different results for extremely large integers that cannot be +converted to floats without overflow. These results are generally more +accurate, with reduced worst-case error and a tighter overall error +distribution. diff --git a/Modules/mathmodule.c b/Modules/mathmodule.c index c631beb9ce5477..be88841716b004 100644 --- a/Modules/mathmodule.c +++ b/Modules/mathmodule.c @@ -2309,7 +2309,7 @@ loghelper(PyObject* arg, double (*func)(double)) assert(e >= 0); assert(!PyErr_Occurred()); /* Value is ~= x * 2**e, so the log ~= log(x) + log(2) * e. */ - result = func(x) + func(2.0) * e; + result = fma(func(2.0), (double)e, func(x)); } else /* Successfully converted x to a double. */ diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 465af26b1c5a8c..a30712f75d5d06 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3314,7 +3314,6 @@ os_lstat_impl(PyObject *module, path_t *path, int dir_fd) #ifdef HAVE_STATX typedef struct { PyObject_HEAD - double atime_sec, btime_sec, ctime_sec, mtime_sec; dev_t rdev, dev; struct statx stx; } Py_statx_result; @@ -3332,7 +3331,6 @@ static PyMemberDef pystatx_result_members[] = { MM(stx_mask, Py_T_UINT, mask, "member validity mask"), MM(stx_blksize, Py_T_UINT, blksize, "blocksize for filesystem I/O"), MM(stx_attributes, Py_T_ULONGLONG, attributes, "Linux inode attribute bits"), - MM(stx_mode, Py_T_USHORT, mode, "protection bits"), MM(stx_attributes_mask, Py_T_ULONGLONG, attributes_mask, "Mask of supported bits in stx_attributes"), MM(stx_rdev_major, Py_T_UINT, rdev_major, "represented device major number"), @@ -3381,6 +3379,17 @@ STATX_GET_UINT(stx_atomic_write_unit_max_opt, STATX_WRITE_ATOMIC) #endif +static PyObject* +pystatx_result_get_stx_mode(PyObject *op, void *Py_UNUSED(context)) +{ + Py_statx_result *self = Py_statx_result_CAST(op); + if (!(self->stx.stx_mask & (STATX_TYPE | STATX_MODE))) { + Py_RETURN_NONE; + } + return PyLong_FromUnsignedLong(self->stx.stx_mode); +} + + #define STATX_GET_ULONGLONG(ATTR, MASK) \ static PyObject* \ pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ @@ -3404,7 +3413,7 @@ STATX_GET_ULONGLONG(stx_subvol, STATX_SUBVOL) #endif -#define STATX_GET_DOUBLE(ATTR, MEMBER, MASK) \ +#define STATX_GET_DOUBLE(ATTR, MASK) \ static PyObject* \ pystatx_result_get_##ATTR(PyObject *op, void *Py_UNUSED(context)) \ { \ @@ -3412,14 +3421,15 @@ STATX_GET_ULONGLONG(stx_subvol, STATX_SUBVOL) if (!(self->stx.stx_mask & MASK)) { \ Py_RETURN_NONE; \ } \ - double sec = self->MEMBER; \ + struct statx_timestamp *ts = &self->stx.ATTR; \ + double sec = ((double)ts->tv_sec + ts->tv_nsec * 1e-9); \ return PyFloat_FromDouble(sec); \ } -STATX_GET_DOUBLE(stx_atime, atime_sec, STATX_ATIME) -STATX_GET_DOUBLE(stx_btime, btime_sec, STATX_BTIME) -STATX_GET_DOUBLE(stx_ctime, ctime_sec, STATX_CTIME) -STATX_GET_DOUBLE(stx_mtime, mtime_sec, STATX_MTIME) +STATX_GET_DOUBLE(stx_atime, STATX_ATIME) +STATX_GET_DOUBLE(stx_btime, STATX_BTIME) +STATX_GET_DOUBLE(stx_ctime, STATX_CTIME) +STATX_GET_DOUBLE(stx_mtime, STATX_MTIME) #define STATX_GET_NSEC(ATTR, MEMBER, MASK) \ static PyObject* \ @@ -3444,6 +3454,7 @@ STATX_GET_NSEC(stx_mtime_ns, stx_mtime, STATX_MTIME) {#attr, pystatx_result_get_##attr, NULL, PyDoc_STR(doc), NULL} static PyGetSetDef pystatx_result_getset[] = { + G(stx_mode, "protection bits"), G(stx_nlink, "number of hard links"), G(stx_uid, "user ID of owner"), G(stx_gid, "group ID of owner"), @@ -3670,14 +3681,6 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int flags, return path_error(path); } - v->atime_sec = ((double)v->stx.stx_atime.tv_sec - + 1e-9 * v->stx.stx_atime.tv_nsec); - v->btime_sec = ((double)v->stx.stx_btime.tv_sec - + 1e-9 * v->stx.stx_btime.tv_nsec); - v->ctime_sec = ((double)v->stx.stx_ctime.tv_sec - + 1e-9 * v->stx.stx_ctime.tv_nsec); - v->mtime_sec = ((double)v->stx.stx_mtime.tv_sec - + 1e-9 * v->stx.stx_mtime.tv_nsec); v->rdev = makedev(v->stx.stx_rdev_major, v->stx.stx_rdev_minor); v->dev = makedev(v->stx.stx_dev_major, v->stx.stx_dev_minor); diff --git a/Python/stackrefs.c b/Python/stackrefs.c index ecc0012ef17b39..720916e0854f5c 100644 --- a/Python/stackrefs.c +++ b/Python/stackrefs.c @@ -109,18 +109,19 @@ _Py_stackref_close(_PyStackRef ref, const char *filename, int linenumber) } _PyStackRef -_Py_stackref_create(PyObject *obj, const char *filename, int linenumber) +_Py_stackref_create(PyObject *obj, uint16_t flags, const char *filename, int linenumber) { if (obj == NULL) { Py_FatalError("Cannot create a stackref for NULL"); } PyInterpreterState *interp = PyInterpreterState_Get(); uint64_t new_id = interp->next_stackref; - interp->next_stackref = new_id + 2; + interp->next_stackref = new_id + (1 << Py_TAGGED_SHIFT); TableEntry *entry = make_table_entry(obj, filename, linenumber); if (entry == NULL) { Py_FatalError("No memory left for stackref debug table"); } + new_id |= flags; if (_Py_hashtable_set(interp->open_stackrefs_table, (void *)new_id, entry) < 0) { Py_FatalError("No memory left for stackref debug table"); } @@ -194,16 +195,10 @@ _Py_stackref_report_leaks(PyInterpreterState *interp) } } -void -_PyStackRef_CLOSE_SPECIALIZED(_PyStackRef ref, destructor destruct, const char *filename, int linenumber) -{ - PyObject *obj = _Py_stackref_close(ref, filename, linenumber); - _Py_DECREF_SPECIALIZED(obj, destruct); -} - _PyStackRef PyStackRef_TagInt(intptr_t i) { - return (_PyStackRef){ .index = (i << 1) + 1 }; + assert(Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, (i << Py_TAGGED_SHIFT), Py_TAGGED_SHIFT) == i); + return (_PyStackRef){ .index = (i << Py_TAGGED_SHIFT) | Py_INT_TAG }; } intptr_t @@ -211,7 +206,7 @@ PyStackRef_UntagInt(_PyStackRef i) { assert(PyStackRef_IsTaggedInt(i)); intptr_t val = (intptr_t)i.index; - return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, 1); + return Py_ARITHMETIC_RIGHT_SHIFT(intptr_t, val, Py_TAGGED_SHIFT); } bool @@ -223,8 +218,9 @@ PyStackRef_IsNullOrInt(_PyStackRef ref) _PyStackRef PyStackRef_IncrementTaggedIntNoOverflow(_PyStackRef ref) { - assert(ref.index <= INT_MAX - 2); // No overflow - return (_PyStackRef){ .index = ref.index + 2 }; + assert(PyStackRef_IsTaggedInt(ref)); + assert((ref.index & (~Py_TAG_BITS)) != (INTPTR_MAX & (~Py_TAG_BITS))); // Isn't about to overflow + return (_PyStackRef){ .index = ref.index + (1 << Py_TAGGED_SHIFT) }; } diff --git a/Tools/jit/_optimizers.py b/Tools/jit/_optimizers.py index 866417398b0ba5..0adc550ba5e84c 100644 --- a/Tools/jit/_optimizers.py +++ b/Tools/jit/_optimizers.py @@ -9,7 +9,7 @@ _RE_NEVER_MATCH = re.compile(r"(?!)") # Dictionary mapping branch instructions to their inverted branch instructions. # If a branch cannot be inverted, the value is None: -_X86_BRANCHES = { +_X86_BRANCH_NAMES = { # https://www.felixcloutier.com/x86/jcc "ja": "jna", "jae": "jnae", @@ -37,7 +37,11 @@ "loopz": None, } # Update with all of the inverted branches, too: -_X86_BRANCHES |= {v: k for k, v in _X86_BRANCHES.items() if v} +_X86_BRANCH_NAMES |= {v: k for k, v in _X86_BRANCH_NAMES.items() if v} +# No custom relocations needed +_X86_BRANCHES: dict[str, tuple[str | None, str | None]] = { + k: (v, None) for k, v in _X86_BRANCH_NAMES.items() +} _AARCH64_COND_CODES = { # https://developer.arm.com/documentation/dui0801/b/CJAJIHAD?lang=en @@ -58,12 +62,15 @@ "hi": "ls", "ls": "hi", } +# MyPy doesn't understand that a invariant variable can be initialized by a covariant value +CUSTOM_AARCH64_BRANCH19: str | None = "CUSTOM_AARCH64_BRANCH19" + # Branches are either b.{cond} or bc.{cond} -_AARCH64_BRANCHES = { - "b." + cond: ("b." + inverse if inverse else None) +_AARCH64_BRANCHES: dict[str, tuple[str | None, str | None]] = { + "b." + cond: (("b." + inverse if inverse else None), CUSTOM_AARCH64_BRANCH19) for (cond, inverse) in _AARCH64_COND_CODES.items() } | { - "bc." + cond: ("bc." + inverse if inverse else None) + "bc." + cond: (("bc." + inverse if inverse else None), CUSTOM_AARCH64_BRANCH19) for (cond, inverse) in _AARCH64_COND_CODES.items() } @@ -113,7 +120,8 @@ class Optimizer: r'\s*(?P