From 45536d0894ece0e3ab60120f8d107de36030286d Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 00:11:45 +0900 Subject: [PATCH 01/19] gh-128137: Split out interned field from state --- Include/cpython/unicodeobject.h | 36 ++++++++++++++++++--------------- Objects/unicodeobject.c | 20 +++++++++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 91799137101280..a1cc14886fa023 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -99,17 +99,17 @@ typedef struct { PyObject_HEAD Py_ssize_t length; /* Number of code points in the string */ Py_hash_t hash; /* Hash value; -1 if not set */ + /* If interned is non-zero, the two references from the + dictionary to this object are *not* counted in ob_refcnt. + The possible values here are: + 0: Not Interned + 1: Interned + 2: Interned and Immortal + 3: Interned, Immortal, and Static + This categorization allows the runtime to determine the right + cleanup mechanism at runtime shutdown. */ + uint16_t interned; struct { - /* If interned is non-zero, the two references from the - dictionary to this object are *not* counted in ob_refcnt. - The possible values here are: - 0: Not Interned - 1: Interned - 2: Interned and Immortal - 3: Interned, Immortal, and Static - This categorization allows the runtime to determine the right - cleanup mechanism at runtime shutdown. */ - unsigned int interned:2; /* Character size: - PyUnicode_1BYTE_KIND (1): @@ -132,21 +132,21 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - unsigned int kind:3; + uint16_t kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - unsigned int compact:1; + uint16_t compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - unsigned int ascii:1; + uint16_t ascii:1; /* The object is statically allocated. */ - unsigned int statically_allocated:1; + uint16_t statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to 4 bytes (see issue #19537 on m68k). */ - unsigned int :24; + uint16_t :10; } state; } PyASCIIObject; @@ -195,7 +195,11 @@ typedef struct { /* Use only if you know it's a string */ static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) { - return _PyASCIIObject_CAST(op)->state.interned; +#ifdef Py_GIL_DISABLED + return _Py_atomic_load_uint16_relaxed(&(_PyASCIIObject_CAST(op)->interned)); +#else + return _PyASCIIObject_CAST(op)->interned; +#endif } #define PyUnicode_CHECK_INTERNED(op) PyUnicode_CHECK_INTERNED(_PyObject_CAST(op)) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 1aab9cf37768a8..0ccada8e9e5220 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1409,7 +1409,7 @@ PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar) data = unicode + 1; _PyUnicode_LENGTH(unicode) = size; _PyUnicode_HASH(unicode) = -1; - _PyUnicode_STATE(unicode).interned = 0; + _PyASCIIObject_CAST(unicode)->interned = 0; _PyUnicode_STATE(unicode).kind = kind; _PyUnicode_STATE(unicode).compact = 1; _PyUnicode_STATE(unicode).ascii = is_ascii; @@ -1711,7 +1711,7 @@ unicode_dealloc(PyObject *unicode) _Py_SetImmortal(unicode); return; } - switch (_PyUnicode_STATE(unicode).interned) { + switch (_PyASCIIObject_CAST(unicode)->interned) { case SSTATE_NOT_INTERNED: break; case SSTATE_INTERNED_MORTAL: @@ -1739,7 +1739,7 @@ unicode_dealloc(PyObject *unicode) // so it can't cause trouble (except wasted memory) // - if it wasn't popped, it'll remain interned _Py_SetImmortal(unicode); - _PyUnicode_STATE(unicode).interned = SSTATE_INTERNED_IMMORTAL; + _PyASCIIObject_CAST(unicode)->interned = SSTATE_INTERNED_IMMORTAL; return; } if (r == 0) { @@ -15470,7 +15470,7 @@ unicode_subtype_new(PyTypeObject *type, PyObject *unicode) #else _PyUnicode_HASH(self) = _PyUnicode_HASH(unicode); #endif - _PyUnicode_STATE(self).interned = 0; + _PyASCIIObject_CAST(self)->interned = 0; _PyUnicode_STATE(self).kind = kind; _PyUnicode_STATE(self).compact = 0; _PyUnicode_STATE(self).ascii = _PyUnicode_STATE(unicode).ascii; @@ -15689,7 +15689,7 @@ intern_static(PyInterpreterState *interp, PyObject *s /* stolen */) assert(r == NULL); /* but just in case (for the non-debug build), handle this */ if (r != NULL && r != s) { - assert(_PyUnicode_STATE(r).interned == SSTATE_INTERNED_IMMORTAL_STATIC); + assert(_PyASCIIObject_CAST(r)->interned == SSTATE_INTERNED_IMMORTAL_STATIC); assert(_PyUnicode_CHECK(r)); Py_DECREF(s); return Py_NewRef(r); @@ -15699,7 +15699,7 @@ intern_static(PyInterpreterState *interp, PyObject *s /* stolen */) Py_FatalError("failed to intern static string"); } - _PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL_STATIC; + _PyASCIIObject_CAST(s)->interned = SSTATE_INTERNED_IMMORTAL_STATIC; return s; } @@ -15726,7 +15726,7 @@ immortalize_interned(PyObject *s) _Py_DecRefTotal(_PyThreadState_GET()); } #endif - _PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL; + FT_ATOMIC_STORE_UINT16_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_IMMORTAL); _Py_SetImmortal(s); } @@ -15833,7 +15833,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, /* NOT_INTERNED -> INTERNED_MORTAL */ - assert(_PyUnicode_STATE(s).interned == SSTATE_NOT_INTERNED); + assert(_PyASCIIObject_CAST(s)->interned == SSTATE_NOT_INTERNED); if (!_Py_IsImmortal(s)) { /* The two references in interned dict (key and value) are not counted. @@ -15845,7 +15845,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, _Py_DecRefTotal(_PyThreadState_GET()); #endif } - _PyUnicode_STATE(s).interned = SSTATE_INTERNED_MORTAL; + FT_ATOMIC_STORE_UINT16_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_MORTAL); /* INTERNED_MORTAL -> INTERNED_IMMORTAL (if needed) */ @@ -15981,7 +15981,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) Py_UNREACHABLE(); } if (!shared) { - _PyUnicode_STATE(s).interned = SSTATE_NOT_INTERNED; + FT_ATOMIC_STORE_UINT16_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_NOT_INTERNED); } } #ifdef INTERNED_STATS From f30b355474b116a4b0e60c01081194c07ae653ed Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 00:52:28 +0900 Subject: [PATCH 02/19] Address code review --- Include/cpython/unicodeobject.h | 14 +++++++------- Objects/unicodeobject.c | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index a1cc14886fa023..4c0dd86e816670 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -108,7 +108,7 @@ typedef struct { 3: Interned, Immortal, and Static This categorization allows the runtime to determine the right cleanup mechanism at runtime shutdown. */ - uint16_t interned; + uint8_t interned; struct { /* Character size: @@ -132,21 +132,21 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - uint16_t kind:3; + unsigned int kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - uint16_t compact:1; + unsigned int compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - uint16_t ascii:1; + unsigned int ascii:1; /* The object is statically allocated. */ - uint16_t statically_allocated:1; + unsigned int statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to 4 bytes (see issue #19537 on m68k). */ - uint16_t :10; + unsigned int :10; } state; } PyASCIIObject; @@ -196,7 +196,7 @@ typedef struct { /* Use only if you know it's a string */ static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) { #ifdef Py_GIL_DISABLED - return _Py_atomic_load_uint16_relaxed(&(_PyASCIIObject_CAST(op)->interned)); + return _Py_atomic_load_uint8_relaxed(&(_PyASCIIObject_CAST(op)->interned)); #else return _PyASCIIObject_CAST(op)->interned; #endif diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 0ccada8e9e5220..547ea3738f0d01 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -15726,7 +15726,7 @@ immortalize_interned(PyObject *s) _Py_DecRefTotal(_PyThreadState_GET()); } #endif - FT_ATOMIC_STORE_UINT16_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_IMMORTAL); + FT_ATOMIC_STORE_UINT8_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_IMMORTAL); _Py_SetImmortal(s); } @@ -15845,7 +15845,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, _Py_DecRefTotal(_PyThreadState_GET()); #endif } - FT_ATOMIC_STORE_UINT16_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_MORTAL); + FT_ATOMIC_STORE_UINT8_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_MORTAL); /* INTERNED_MORTAL -> INTERNED_IMMORTAL (if needed) */ @@ -15981,7 +15981,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) Py_UNREACHABLE(); } if (!shared) { - FT_ATOMIC_STORE_UINT16_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_NOT_INTERNED); + FT_ATOMIC_STORE_UINT8_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_NOT_INTERNED); } } #ifdef INTERNED_STATS From 18a8e6be63e285908131af4f93fd2192f76aa643 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 01:18:16 +0900 Subject: [PATCH 03/19] Maintain unused size as 24 bits --- Include/cpython/unicodeobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 4c0dd86e816670..9f2469c018ac12 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -146,7 +146,7 @@ typedef struct { unsigned int statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to 4 bytes (see issue #19537 on m68k). */ - unsigned int :10; + unsigned int :24; } state; } PyASCIIObject; From 5c659ef15049c061b457a77281913eb9c81153df Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 01:27:25 +0900 Subject: [PATCH 04/19] Reserve unused bit as 18 bits --- Include/cpython/unicodeobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 9f2469c018ac12..57a51bf53c8e8c 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -146,7 +146,7 @@ typedef struct { unsigned int statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to 4 bytes (see issue #19537 on m68k). */ - unsigned int :24; + unsigned int :18; } state; } PyASCIIObject; From c80347579d400a2a08ca23a40f9e2d6d9ae1d707 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 01:34:19 +0900 Subject: [PATCH 05/19] Address code review --- Include/cpython/unicodeobject.h | 25 +++++++++++++------------ Objects/unicodeobject.c | 20 ++++++++++---------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 57a51bf53c8e8c..dfa91ed13eb38c 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -99,17 +99,18 @@ typedef struct { PyObject_HEAD Py_ssize_t length; /* Number of code points in the string */ Py_hash_t hash; /* Hash value; -1 if not set */ - /* If interned is non-zero, the two references from the - dictionary to this object are *not* counted in ob_refcnt. - The possible values here are: - 0: Not Interned - 1: Interned - 2: Interned and Immortal - 3: Interned, Immortal, and Static - This categorization allows the runtime to determine the right - cleanup mechanism at runtime shutdown. */ - uint8_t interned; + struct { + /* If interned is non-zero, the two references from the + dictionary to this object are *not* counted in ob_refcnt. + The possible values here are: + 0: Not Interned + 1: Interned + 2: Interned and Immortal + 3: Interned, Immortal, and Static + This categorization allows the runtime to determine the right + cleanup mechanism at runtime shutdown. */ + uint8_t interned; /* Character size: - PyUnicode_1BYTE_KIND (1): @@ -196,9 +197,9 @@ typedef struct { /* Use only if you know it's a string */ static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) { #ifdef Py_GIL_DISABLED - return _Py_atomic_load_uint8_relaxed(&(_PyASCIIObject_CAST(op)->interned)); + return _Py_atomic_load_uint8_relaxed(&_PyASCIIObject_CAST(op)->state.interned); #else - return _PyASCIIObject_CAST(op)->interned; + return _PyASCIIObject_CAST(op)->state.interned; #endif } #define PyUnicode_CHECK_INTERNED(op) PyUnicode_CHECK_INTERNED(_PyObject_CAST(op)) diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 547ea3738f0d01..5214469fd162ab 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -1409,7 +1409,7 @@ PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar) data = unicode + 1; _PyUnicode_LENGTH(unicode) = size; _PyUnicode_HASH(unicode) = -1; - _PyASCIIObject_CAST(unicode)->interned = 0; + _PyUnicode_STATE(unicode).interned = 0; _PyUnicode_STATE(unicode).kind = kind; _PyUnicode_STATE(unicode).compact = 1; _PyUnicode_STATE(unicode).ascii = is_ascii; @@ -1711,7 +1711,7 @@ unicode_dealloc(PyObject *unicode) _Py_SetImmortal(unicode); return; } - switch (_PyASCIIObject_CAST(unicode)->interned) { + switch (_PyUnicode_STATE(unicode).interned) { case SSTATE_NOT_INTERNED: break; case SSTATE_INTERNED_MORTAL: @@ -1739,7 +1739,7 @@ unicode_dealloc(PyObject *unicode) // so it can't cause trouble (except wasted memory) // - if it wasn't popped, it'll remain interned _Py_SetImmortal(unicode); - _PyASCIIObject_CAST(unicode)->interned = SSTATE_INTERNED_IMMORTAL; + _PyUnicode_STATE(unicode).interned = SSTATE_INTERNED_IMMORTAL; return; } if (r == 0) { @@ -15470,7 +15470,7 @@ unicode_subtype_new(PyTypeObject *type, PyObject *unicode) #else _PyUnicode_HASH(self) = _PyUnicode_HASH(unicode); #endif - _PyASCIIObject_CAST(self)->interned = 0; + _PyUnicode_STATE(self).interned = 0; _PyUnicode_STATE(self).kind = kind; _PyUnicode_STATE(self).compact = 0; _PyUnicode_STATE(self).ascii = _PyUnicode_STATE(unicode).ascii; @@ -15689,7 +15689,7 @@ intern_static(PyInterpreterState *interp, PyObject *s /* stolen */) assert(r == NULL); /* but just in case (for the non-debug build), handle this */ if (r != NULL && r != s) { - assert(_PyASCIIObject_CAST(r)->interned == SSTATE_INTERNED_IMMORTAL_STATIC); + assert(_PyUnicode_STATE(r).interned == SSTATE_INTERNED_IMMORTAL_STATIC); assert(_PyUnicode_CHECK(r)); Py_DECREF(s); return Py_NewRef(r); @@ -15699,7 +15699,7 @@ intern_static(PyInterpreterState *interp, PyObject *s /* stolen */) Py_FatalError("failed to intern static string"); } - _PyASCIIObject_CAST(s)->interned = SSTATE_INTERNED_IMMORTAL_STATIC; + _PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL_STATIC; return s; } @@ -15726,7 +15726,7 @@ immortalize_interned(PyObject *s) _Py_DecRefTotal(_PyThreadState_GET()); } #endif - FT_ATOMIC_STORE_UINT8_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_IMMORTAL); + FT_ATOMIC_STORE_UINT8_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_IMMORTAL); _Py_SetImmortal(s); } @@ -15833,7 +15833,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, /* NOT_INTERNED -> INTERNED_MORTAL */ - assert(_PyASCIIObject_CAST(s)->interned == SSTATE_NOT_INTERNED); + assert(_PyUnicode_STATE(s).interned == SSTATE_NOT_INTERNED); if (!_Py_IsImmortal(s)) { /* The two references in interned dict (key and value) are not counted. @@ -15845,7 +15845,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, _Py_DecRefTotal(_PyThreadState_GET()); #endif } - FT_ATOMIC_STORE_UINT8_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_INTERNED_MORTAL); + FT_ATOMIC_STORE_UINT8_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_MORTAL); /* INTERNED_MORTAL -> INTERNED_IMMORTAL (if needed) */ @@ -15981,7 +15981,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) Py_UNREACHABLE(); } if (!shared) { - FT_ATOMIC_STORE_UINT8_RELAXED(_PyASCIIObject_CAST(s)->interned, SSTATE_NOT_INTERNED); + FT_ATOMIC_STORE_UINT8_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_NOT_INTERNED); } } #ifdef INTERNED_STATS From 2dfd450879f94a4a9af6847ca784574974d4e394 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 01:36:09 +0900 Subject: [PATCH 06/19] nit --- Include/cpython/unicodeobject.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index dfa91ed13eb38c..996947d4478bb7 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -102,14 +102,14 @@ typedef struct { struct { /* If interned is non-zero, the two references from the - dictionary to this object are *not* counted in ob_refcnt. - The possible values here are: - 0: Not Interned - 1: Interned - 2: Interned and Immortal - 3: Interned, Immortal, and Static - This categorization allows the runtime to determine the right - cleanup mechanism at runtime shutdown. */ + dictionary to this object are *not* counted in ob_refcnt. + The possible values here are: + 0: Not Interned + 1: Interned + 2: Interned and Immortal + 3: Interned, Immortal, and Static + This categorization allows the runtime to determine the right + cleanup mechanism at runtime shutdown. */ uint8_t interned; /* Character size: From 69ad22442fcb8edf8f9aa56dfa8327accdacc19d Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 01:36:34 +0900 Subject: [PATCH 07/19] nit --- Include/cpython/unicodeobject.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 996947d4478bb7..919251c06eed5e 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -99,7 +99,6 @@ typedef struct { PyObject_HEAD Py_ssize_t length; /* Number of code points in the string */ Py_hash_t hash; /* Hash value; -1 if not set */ - struct { /* If interned is non-zero, the two references from the dictionary to this object are *not* counted in ob_refcnt. From b1b396b5e904672477537c8ecd64ee7e25e72389 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 01:40:17 +0900 Subject: [PATCH 08/19] Add NEWS.d --- .../2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst new file mode 100644 index 00000000000000..a3b7cde7f67676 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-01-40-12.gh-issue-128137.gsTwr_.rst @@ -0,0 +1,2 @@ +Update :c:type:`PyASCIIObject` layout to handle interned field with the +atomic operation. Patch by Donghee Na. From 4327a6d9b340dd3a48f5d44adbc83a8f5bd12c76 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 20:40:59 +0900 Subject: [PATCH 09/19] Resize struct padding --- Lib/test/test_str.py | 2 +- Lib/test/test_sys.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 4de6c1cba152bd..beb25c5eb8ade6 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -2449,7 +2449,7 @@ def test_expandtabs_optimization(self): self.assertIs(s.expandtabs(), s) def test_raiseMemError(self): - asciifields = "nnb" + asciifields = "nnb7x" compactfields = asciifields + "nP" ascii_struct_size = support.calcobjsize(asciifields) compact_struct_size = support.calcobjsize(compactfields) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index d839893d2c657e..93252d1d2da06c 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1762,7 +1762,7 @@ class newstyleclass(object): pass '\u0100'*40, '\uffff'*100, '\U00010000'*30, '\U0010ffff'*100] # also update field definitions in test_unicode.test_raiseMemError - asciifields = "nnb" + asciifields = "nnb7x" compactfields = asciifields + "nP" unicodefields = compactfields + "P" for s in samples: From d86e0afa01e80e7e7484a540e3cac4db57e1077d Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 21:05:42 +0900 Subject: [PATCH 10/19] Update for WASI --- Lib/test/test_str.py | 4 +++- Lib/test/test_sys.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index beb25c5eb8ade6..a256c4119e18aa 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -2449,7 +2449,9 @@ def test_expandtabs_optimization(self): self.assertIs(s.expandtabs(), s) def test_raiseMemError(self): - asciifields = "nnb7x" + asciifields = "nnb" + if not support.is_wasi: + asciifields = asciifields + "3x" compactfields = asciifields + "nP" ascii_struct_size = support.calcobjsize(asciifields) compact_struct_size = support.calcobjsize(compactfields) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 93252d1d2da06c..6f4621b6f7271d 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1762,7 +1762,9 @@ class newstyleclass(object): pass '\u0100'*40, '\uffff'*100, '\U00010000'*30, '\U0010ffff'*100] # also update field definitions in test_unicode.test_raiseMemError - asciifields = "nnb7x" + asciifields = "nnb" + if not support.is_wasi: + asciifields = asciifields + "3x" compactfields = asciifields + "nP" unicodefields = compactfields + "P" for s in samples: From f5c4d96ae8544803d5116380742254af26ced2c1 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Tue, 24 Dec 2024 21:23:30 +0900 Subject: [PATCH 11/19] fix --- Lib/test/test_str.py | 2 +- Lib/test/test_sys.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index a256c4119e18aa..82be790c52a946 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -2451,7 +2451,7 @@ def test_expandtabs_optimization(self): def test_raiseMemError(self): asciifields = "nnb" if not support.is_wasi: - asciifields = asciifields + "3x" + asciifields = asciifields + "7x" compactfields = asciifields + "nP" ascii_struct_size = support.calcobjsize(asciifields) compact_struct_size = support.calcobjsize(compactfields) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 6f4621b6f7271d..910c8f291c708f 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1764,7 +1764,7 @@ class newstyleclass(object): pass # also update field definitions in test_unicode.test_raiseMemError asciifields = "nnb" if not support.is_wasi: - asciifields = asciifields + "3x" + asciifields = asciifields + "7x" compactfields = asciifields + "nP" unicodefields = compactfields + "P" for s in samples: From 53147e76fc21a54f58e3fc842b6c1c7eb4a85cba Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 1 Jan 2025 18:49:17 +0900 Subject: [PATCH 12/19] Address code review --- Include/cpython/unicodeobject.h | 16 ++++++++++------ Lib/test/test_str.py | 2 -- Lib/test/test_sys.py | 2 -- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 919251c06eed5e..0dfea8a3940d8e 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -132,21 +132,25 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - unsigned int kind:3; + unsigned char kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - unsigned int compact:1; + unsigned char compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - unsigned int ascii:1; + unsigned char ascii:1; /* The object is statically allocated. */ - unsigned int statically_allocated:1; + unsigned char statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to - 4 bytes (see issue #19537 on m68k). */ - unsigned int :18; + 4 bytes (see issue #19537 on m68k) and we use unsigned char to avoid + the extra four bytes on 32-bit Windows. This is restricted features + for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ + unsigned char :2; + unsigned char :8; + unsigned char :8; } state; } PyASCIIObject; diff --git a/Lib/test/test_str.py b/Lib/test/test_str.py index 82be790c52a946..4de6c1cba152bd 100644 --- a/Lib/test/test_str.py +++ b/Lib/test/test_str.py @@ -2450,8 +2450,6 @@ def test_expandtabs_optimization(self): def test_raiseMemError(self): asciifields = "nnb" - if not support.is_wasi: - asciifields = asciifields + "7x" compactfields = asciifields + "nP" ascii_struct_size = support.calcobjsize(asciifields) compact_struct_size = support.calcobjsize(compactfields) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 910c8f291c708f..d839893d2c657e 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -1763,8 +1763,6 @@ class newstyleclass(object): pass '\U00010000'*30, '\U0010ffff'*100] # also update field definitions in test_unicode.test_raiseMemError asciifields = "nnb" - if not support.is_wasi: - asciifields = asciifields + "7x" compactfields = asciifields + "nP" unicodefields = compactfields + "P" for s in samples: From 30c2eb2dd4afb8164eb228795d2e2c0b0b205fce Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Wed, 1 Jan 2025 21:51:29 +0900 Subject: [PATCH 13/19] Use unsigned short instead --- Include/cpython/unicodeobject.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 0dfea8a3940d8e..a17169dc4f1955 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -132,25 +132,23 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - unsigned char kind:3; + unsigned short kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - unsigned char compact:1; + unsigned short compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - unsigned char ascii:1; + unsigned short ascii:1; /* The object is statically allocated. */ - unsigned char statically_allocated:1; + unsigned short statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to - 4 bytes (see issue #19537 on m68k) and we use unsigned char to avoid + 4 bytes (see issue #19537 on m68k) and we use unsigned short to avoid the extra four bytes on 32-bit Windows. This is restricted features for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ - unsigned char :2; - unsigned char :8; - unsigned char :8; + unsigned short :10; } state; } PyASCIIObject; From 7d4955c2d5c953055f84b5613aae153c218e0596 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 2 Jan 2025 09:18:54 +0900 Subject: [PATCH 14/19] Address code review --- Include/cpython/unicodeobject.h | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index a17169dc4f1955..1fcb42364a72e1 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -132,23 +132,25 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - unsigned short kind:3; + unsigned char kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - unsigned short compact:1; + unsigned char compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - unsigned short ascii:1; + unsigned char ascii:1; /* The object is statically allocated. */ - unsigned short statically_allocated:1; + unsigned char statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to 4 bytes (see issue #19537 on m68k) and we use unsigned short to avoid the extra four bytes on 32-bit Windows. This is restricted features for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ - unsigned short :10; + unsigned char :2; + unsigned char :8; + unsigned char :8; } state; } PyASCIIObject; From 958d0d72d7465d6f7ad1e98a35d81bcc31f704be Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 2 Jan 2025 09:29:53 +0900 Subject: [PATCH 15/19] Revert "Address code review" This reverts commit 7d4955c2d5c953055f84b5613aae153c218e0596. --- Include/cpython/unicodeobject.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 1fcb42364a72e1..a17169dc4f1955 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -132,25 +132,23 @@ typedef struct { * all characters are in the range U+0000-U+10FFFF * at least one character is in the range U+10000-U+10FFFF */ - unsigned char kind:3; + unsigned short kind:3; /* Compact is with respect to the allocation scheme. Compact unicode objects only require one memory block while non-compact objects use one block for the PyUnicodeObject struct and another for its data buffer. */ - unsigned char compact:1; + unsigned short compact:1; /* The string only contains characters in the range U+0000-U+007F (ASCII) and the kind is PyUnicode_1BYTE_KIND. If ascii is set and compact is set, use the PyASCIIObject structure. */ - unsigned char ascii:1; + unsigned short ascii:1; /* The object is statically allocated. */ - unsigned char statically_allocated:1; + unsigned short statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to 4 bytes (see issue #19537 on m68k) and we use unsigned short to avoid the extra four bytes on 32-bit Windows. This is restricted features for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ - unsigned char :2; - unsigned char :8; - unsigned char :8; + unsigned short :10; } state; } PyASCIIObject; From fbc9c52afb543deae42bb25ba443e6b69bf2c2da Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 2 Jan 2025 09:36:04 +0900 Subject: [PATCH 16/19] Add extra padding --- Include/cpython/unicodeobject.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index a17169dc4f1955..57e128008210e7 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -145,10 +145,11 @@ typedef struct { /* The object is statically allocated. */ unsigned short statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to - 4 bytes (see issue #19537 on m68k) and we use unsigned short to avoid + 24 bits (see issue #19537 on m68k) and we use unsigned short to avoid the extra four bytes on 32-bit Windows. This is restricted features for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ unsigned short :10; + unsigned char :8; } state; } PyASCIIObject; From 2eaf1cf99967477817b76899240829f265acca34 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 2 Jan 2025 10:12:22 +0900 Subject: [PATCH 17/19] Increase interned to 16bits and adjust layout --- Include/cpython/unicodeobject.h | 3 +-- Objects/unicodeobject.c | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 57e128008210e7..47e49feb5a5132 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -109,7 +109,7 @@ typedef struct { 3: Interned, Immortal, and Static This categorization allows the runtime to determine the right cleanup mechanism at runtime shutdown. */ - uint8_t interned; + uint16_t interned; /* Character size: - PyUnicode_1BYTE_KIND (1): @@ -149,7 +149,6 @@ typedef struct { the extra four bytes on 32-bit Windows. This is restricted features for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ unsigned short :10; - unsigned char :8; } state; } PyASCIIObject; diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 5214469fd162ab..7808b6b674a127 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -15726,7 +15726,7 @@ immortalize_interned(PyObject *s) _Py_DecRefTotal(_PyThreadState_GET()); } #endif - FT_ATOMIC_STORE_UINT8_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_IMMORTAL); + FT_ATOMIC_STORE_UINT16_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_IMMORTAL); _Py_SetImmortal(s); } @@ -15845,7 +15845,7 @@ intern_common(PyInterpreterState *interp, PyObject *s /* stolen */, _Py_DecRefTotal(_PyThreadState_GET()); #endif } - FT_ATOMIC_STORE_UINT8_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_MORTAL); + FT_ATOMIC_STORE_UINT16_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_INTERNED_MORTAL); /* INTERNED_MORTAL -> INTERNED_IMMORTAL (if needed) */ @@ -15981,7 +15981,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp) Py_UNREACHABLE(); } if (!shared) { - FT_ATOMIC_STORE_UINT8_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_NOT_INTERNED); + FT_ATOMIC_STORE_UINT16_RELAXED(_PyUnicode_STATE(s).interned, SSTATE_NOT_INTERNED); } } #ifdef INTERNED_STATS From 4fa26a8c6fc1f4d60eaecc54eafc8ea1cc7b198f Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 2 Jan 2025 11:02:08 +0900 Subject: [PATCH 18/19] fix --- Include/cpython/unicodeobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 47e49feb5a5132..70f1f2c98d7651 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -198,7 +198,7 @@ typedef struct { /* Use only if you know it's a string */ static inline unsigned int PyUnicode_CHECK_INTERNED(PyObject *op) { #ifdef Py_GIL_DISABLED - return _Py_atomic_load_uint8_relaxed(&_PyASCIIObject_CAST(op)->state.interned); + return _Py_atomic_load_uint16_relaxed(&_PyASCIIObject_CAST(op)->state.interned); #else return _PyASCIIObject_CAST(op)->state.interned; #endif From 91907aae25e3880542be18756938aee5c74650f9 Mon Sep 17 00:00:00 2001 From: Donghee Na Date: Thu, 2 Jan 2025 11:16:32 +0900 Subject: [PATCH 19/19] nit --- Include/cpython/unicodeobject.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/unicodeobject.h b/Include/cpython/unicodeobject.h index 70f1f2c98d7651..46a01c8e591709 100644 --- a/Include/cpython/unicodeobject.h +++ b/Include/cpython/unicodeobject.h @@ -145,7 +145,7 @@ typedef struct { /* The object is statically allocated. */ unsigned short statically_allocated:1; /* Padding to ensure that PyUnicode_DATA() is always aligned to - 24 bits (see issue #19537 on m68k) and we use unsigned short to avoid + 4 bytes (see issue #19537 on m68k) and we use unsigned short to avoid the extra four bytes on 32-bit Windows. This is restricted features for specific compilers including GCC, MSVC, Clang and IBM's XL compiler. */ unsigned short :10;