diff --git a/graalpython/com.oracle.graal.python.cext/include/cpython/pystate.h b/graalpython/com.oracle.graal.python.cext/include/cpython/pystate.h index 2b4134cb94..cd1897bca7 100644 --- a/graalpython/com.oracle.graal.python.cext/include/cpython/pystate.h +++ b/graalpython/com.oracle.graal.python.cext/include/cpython/pystate.h @@ -111,6 +111,12 @@ typedef struct { int capacity; } GraalPyDeallocState; +typedef struct { + PyObject *tuple_empty; + PyObject *bytes_empty; + PyObject **bytes_characters; +} GraalPySingletons; + typedef struct _stack_chunk { struct _stack_chunk *previous; size_t size; @@ -265,6 +271,14 @@ struct _ts { */ PyObject **small_ints; + /* GraalPy change: Similar to small_ints, we keep native wrappers for + CPython's internal static-object singletons in the PyThreadState rather + than in PyInterpreterState or _PyRuntimeState. The native wrappers are + context-local handle-space pointers even though the represented managed + objects are immutable. Do not add public API singletons such as Py_None + here; CPython exposes those through their dedicated globals. */ + GraalPySingletons singletons; + /* GraalPy change: We add field 'gc' which corresponds to field '&interp->gc'. */ struct _gc_runtime_state *gc; diff --git a/graalpython/com.oracle.graal.python.cext/include/internal/pycore_global_objects.h b/graalpython/com.oracle.graal.python.cext/include/internal/pycore_global_objects.h index 65832418e9..2c4ed78c32 100644 --- a/graalpython/com.oracle.graal.python.cext/include/internal/pycore_global_objects.h +++ b/graalpython/com.oracle.graal.python.cext/include/internal/pycore_global_objects.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2024, 2025, Oracle and/or its affiliates. +/* Copyright (c) 2024, 2026, Oracle and/or its affiliates. * Copyright (C) 1996-2023 Python Software Foundation * * Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 @@ -35,8 +35,11 @@ extern "C" { #define _Py_GLOBAL_OBJECT(NAME) \ _PyRuntime.static_objects.NAME +/* GraalPy change: CPython exposes static singleton storage through _PyRuntime. + GraalPy's native wrappers are context-local handle-space pointers, so keep + them in the PyThreadState next to small_ints. */ #define _Py_SINGLETON(NAME) \ - _Py_GLOBAL_OBJECT(singletons.NAME) + (*(PyThreadState_Get()->singletons.NAME)) #if 0 // GraalPy change struct _Py_cached_objects { diff --git a/graalpython/com.oracle.graal.python.cext/modules/_cpython_sre/sre.c b/graalpython/com.oracle.graal.python.cext/modules/_cpython_sre/sre.c index 70f83da145..d9aeca3d67 100644 --- a/graalpython/com.oracle.graal.python.cext/modules/_cpython_sre/sre.c +++ b/graalpython/com.oracle.graal.python.cext/modules/_cpython_sre/sre.c @@ -3062,12 +3062,7 @@ expand_template(TemplateObject *self, MatchObject *match) } else { // Py_SET_SIZE(list, count); // GraalPy change - PyObject *empty = PyBytes_FromStringAndSize(NULL, 0); - if (empty == NULL) { - goto cleanup; - } - result = _PyBytes_Join(empty, list); - Py_DECREF(empty); + result = _PyBytes_Join((PyObject *)&_Py_SINGLETON(bytes_empty), list); } cleanup: diff --git a/graalpython/com.oracle.graal.python.cext/src/bytesobject.c b/graalpython/com.oracle.graal.python.cext/src/bytesobject.c index 0e0feb5dde..dade5bdc54 100644 --- a/graalpython/com.oracle.graal.python.cext/src/bytesobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/bytesobject.c @@ -9,6 +9,7 @@ #include "capi.h" // GraalPy change #include "Python.h" +#include "pycore_global_objects.h" // _Py_SINGLETON() // GraalPy change #if 0 // GraalPy change #include "pycore_abstract.h" // _PyIndex_Check() #include "pycore_bytesobject.h" // _PyBytes_Find(), _PyBytes_Repeat() @@ -25,6 +26,29 @@ #include +// Return a strong reference to the empty bytes string singleton. +static inline PyObject* bytes_new_empty(void) +{ + return Py_NewRef(&_Py_SINGLETON(bytes_empty)); +} + +// Return a strong reference to a cached one-byte bytes string singleton. +static inline PyObject* bytes_new_character(unsigned char ch) +{ + return Py_NewRef(PyThreadState_Get()->singletons.bytes_characters[ch]); +} + +static inline int bytes_is_character_singleton(PyObject *op) +{ + PyObject **characters = PyThreadState_Get()->singletons.bytes_characters; + for (int i = 0; i < 256; i++) { + if (op == characters[i]) { + return 1; + } + } + return 0; +} + /*[clinic input] class bytes "PyBytesObject *" "&PyBytes_Type" [clinic start generated code]*/ @@ -133,6 +157,12 @@ PyBytes_FromStringAndSize(const char *str, Py_ssize_t size) "Negative size passed to PyBytes_FromStringAndSize"); return NULL; } + if (size == 0) { + return bytes_new_empty(); + } + if (size == 1 && str != NULL) { + return bytes_new_character((unsigned char)*str); + } if (str != NULL) { return GraalPyPrivate_Bytes_FromStringAndSize(str, size); } @@ -143,10 +173,15 @@ PyObject * PyBytes_FromString(const char *str) { // GraalPy change: different implementation - if (str != NULL) { - return GraalPyPrivate_Bytes_FromStringAndSize(str, strlen(str)); - } - return GraalPyPrivate_Bytes_EmptyWithCapacity(0); + assert(str != NULL); + Py_ssize_t size = strlen(str); + if (size == 0) { + return bytes_new_empty(); + } + if (size == 1) { + return bytes_new_character((unsigned char)*str); + } + return GraalPyPrivate_Bytes_FromStringAndSize(str, size); } PyObject * @@ -2927,6 +2962,32 @@ int _PyBytes_Resize(PyObject **pv, Py_ssize_t newsize) { // GraalPy change: different implementation + PyObject *v = *pv; + if (!PyBytes_Check(v) || newsize < 0) { + *pv = NULL; + Py_DECREF(v); + PyErr_BadInternalCall(); + return -1; + } + if (Py_SIZE(v) == newsize) { + return 0; + } + if (Py_SIZE(v) == 0) { + *pv = GraalPyPrivate_Bytes_EmptyWithCapacity(newsize); + Py_DECREF(v); + return *pv == NULL ? -1 : 0; + } + if (bytes_is_character_singleton(v)) { + *pv = NULL; + Py_DECREF(v); + PyErr_BadInternalCall(); + return -1; + } + if (newsize == 0) { + *pv = bytes_new_empty(); + Py_DECREF(v); + return 0; + } return GraalPyPrivate_Bytes_Resize(*pv, newsize); } diff --git a/graalpython/com.oracle.graal.python.cext/src/capi.c b/graalpython/com.oracle.graal.python.cext/src/capi.c index 541450593a..f42088521c 100644 --- a/graalpython/com.oracle.graal.python.cext/src/capi.c +++ b/graalpython/com.oracle.graal.python.cext/src/capi.c @@ -336,6 +336,7 @@ THREAD_LOCAL PyThreadState *tstate_current = NULL; GraalPy_CAPI_HELPER_SYMBOL PyThreadState **GraalPyPrivate_InitThreadStateCurrent(PyThreadState *tstate) { tstate_current = tstate; + graalpy_initialize_thread_state_singletons(tstate); return &tstate_current; } @@ -346,6 +347,7 @@ static void initialize_globals(PyThreadState *tstate) { _Py_EllipsisObjectReference = GraalPyPrivate_Ellipsis(); _Py_TrueStructReference = (struct _longobject*)GraalPyPrivate_True(); _Py_FalseStructReference = (struct _longobject*)GraalPyPrivate_False(); + graalpy_initialize_thread_state_singletons(tstate); } /* internal functions to avoid unnecessary managed <-> native conversions */ diff --git a/graalpython/com.oracle.graal.python.cext/src/capi.h b/graalpython/com.oracle.graal.python.cext/src/capi.h index 71dcefac7b..965e6690b2 100644 --- a/graalpython/com.oracle.graal.python.cext/src/capi.h +++ b/graalpython/com.oracle.graal.python.cext/src/capi.h @@ -171,6 +171,30 @@ extern Py_LOCAL_SYMBOL uint32_t Py_Truffle_Options; extern THREAD_LOCAL Py_LOCAL_SYMBOL PyThreadState *tstate_current; +static inline void graalpy_initialize_thread_state_singletons(PyThreadState *tstate) { + if (tstate == NULL || GraalPyPrivate_Tuple_Empty == NULL || GraalPyPrivate_Bytes_Empty == NULL || GraalPyPrivate_Bytes_FromStringAndSize == NULL) { + return; + } + if (tstate->singletons.tuple_empty == NULL) { + tstate->singletons.tuple_empty = GraalPyPrivate_Tuple_Empty(); + } + if (tstate->singletons.bytes_empty == NULL) { + tstate->singletons.bytes_empty = GraalPyPrivate_Bytes_Empty(); + } + if (tstate->singletons.bytes_characters != NULL && tstate->singletons.bytes_characters[0] == NULL) { + for (int i = 0; i < 256; i++) { + char ch = (char)i; + tstate->singletons.bytes_characters[i] = GraalPyPrivate_Bytes_FromStringAndSize(&ch, 1); + if (tstate->singletons.bytes_characters[i] == NULL) { + Py_FatalError("failed to initialize GraalPy one-byte bytes singleton"); + } + } + } + if (tstate->singletons.tuple_empty == NULL || tstate->singletons.bytes_empty == NULL || tstate->singletons.bytes_characters == NULL || tstate->singletons.bytes_characters[0] == NULL) { + Py_FatalError("failed to initialize GraalPy thread-state singletons"); + } +} + extern Py_LOCAL_SYMBOL int8_t *_graalpy_finalizing; #define graalpy_finalizing (_graalpy_finalizing != NULL && *_graalpy_finalizing) diff --git a/graalpython/com.oracle.graal.python.cext/src/pystate.c b/graalpython/com.oracle.graal.python.cext/src/pystate.c index 5d4a72b997..93a2faada1 100644 --- a/graalpython/com.oracle.graal.python.cext/src/pystate.c +++ b/graalpython/com.oracle.graal.python.cext/src/pystate.c @@ -1422,6 +1422,7 @@ init_threadstate(PyThreadState *tstate, tstate->datastack_top = NULL; tstate->datastack_limit = NULL; tstate->what_event = -1; + graalpy_initialize_thread_state_singletons(tstate); tstate->_status.initialized = 1; } diff --git a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c index 7a1af5d3b8..07b67dff80 100644 --- a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c @@ -14855,8 +14855,8 @@ unicode_ascii_iter_next(unicodeiterobject *it) Py_UCS1 chr = (Py_UCS1)PyUnicode_READ(PyUnicode_1BYTE_KIND, data, it->it_index); it->it_index++; - PyObject *item = (PyObject*)&_Py_SINGLETON(strings).ascii[chr]; - return Py_NewRef(item); + char ch = (char)chr; + return PyUnicode_FromStringAndSize(&ch, 1); } it->it_seq = NULL; Py_DECREF(seq); diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_bytes.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_bytes.py index aabb6d6016..e1fcdd5706 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_bytes.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_bytes.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # The Universal Permissive License (UPL), Version 1.0 @@ -39,7 +39,7 @@ import unittest from . import CPyExtTestCase, CPyExtFunction, CPyExtFunctionOutVars, unhandled_error_compare, CPyExtType, \ - is_native_object + is_native_object, GRAALPYTHON def _reference_from_string_n(args): @@ -76,6 +76,13 @@ def _reference_format(args): fmt_args = tuple(args[1:]) return (fmt % fmt_args).encode() + +def _compare_resize_empty_to_nonempty(cresult, presult): + if GRAALPYTHON: + return cresult is None + return cresult is None or isinstance(cresult, SystemError) + + class CIter: def __iter__(self): return iter([1, 2, 3]) @@ -131,6 +138,116 @@ class TestPyBytes(CPyExtTestCase): arguments=["Py_ssize_t n"], ) + test_PyBytes_FromStringAndSize_empty = CPyExtFunction( + lambda args: 1, + lambda: ((),), + code=""" + static int CheckPyBytesFromStringAndSizeEmpty(void) { + PyObject *from_null = PyBytes_FromStringAndSize(NULL, 0); + PyObject *from_string = PyBytes_FromStringAndSize("ignored", 0); + PyObject *from_c_string = PyBytes_FromString(""); + PyObject *resized_to_empty = PyBytes_FromStringAndSize(NULL, 1); + int resized_to_empty_ok = _PyBytes_Resize(&resized_to_empty, 0) == 0; + int result = from_null != NULL && + from_string != NULL && + from_c_string != NULL && + resized_to_empty != NULL && + resized_to_empty_ok && + PyBytes_CheckExact(from_null) && + PyBytes_GET_SIZE(from_null) == 0; + result = result && + from_null == from_string && + from_null == from_c_string && + from_null == resized_to_empty; + Py_XDECREF(from_null); + Py_XDECREF(from_string); + Py_XDECREF(from_c_string); + Py_XDECREF(resized_to_empty); + return result; + } + """, + resultspec="i", + argspec="", + arguments=[], + callfunction="CheckPyBytesFromStringAndSizeEmpty", + ) + + test_PyBytes_one_byte_singletons = CPyExtFunction( + lambda args: 1, + lambda: ((),), + code=""" + static int CheckPyBytesOneByteSingletons(void) { + int all_from_size_cached = 1; + for (int i = 0; i < 256; i++) { + char ch = (char)i; + PyObject *a = PyBytes_FromStringAndSize(&ch, 1); + PyObject *b = PyBytes_FromStringAndSize(&ch, 1); + if (a == NULL || b == NULL || a != b || + PyBytes_GET_SIZE(a) != 1 || + ((unsigned char *)PyBytes_AS_STRING(a))[0] != (unsigned char)i) { + all_from_size_cached = 0; + } + Py_XDECREF(a); + Py_XDECREF(b); + } + + PyObject *from_string = PyBytes_FromString("x"); + PyObject *from_size = PyBytes_FromStringAndSize("x", 1); + PyObject *from_null = PyBytes_FromStringAndSize(NULL, 1); + PyObject *resize_cached = PyBytes_FromString("x"); + + int from_string_uses_cache = from_string != NULL && from_string == from_size; + int from_null_is_fresh = from_null != NULL && from_null != from_size; + int resize_cached_rejected = _PyBytes_Resize(&resize_cached, 0) < 0; + if (resize_cached_rejected) { + PyErr_Clear(); + } + int resize_fresh_ok = _PyBytes_Resize(&from_null, 0) == 0; + if (!resize_fresh_ok) { + PyErr_Clear(); + } + + int result = all_from_size_cached && + from_string_uses_cache && + from_null_is_fresh && + resize_cached_rejected && + resize_fresh_ok && + from_null != NULL && + PyBytes_GET_SIZE(from_null) == 0; + + Py_XDECREF(from_string); + Py_XDECREF(from_size); + Py_XDECREF(from_null); + Py_XDECREF(resize_cached); + return result; + } + """, + resultspec="i", + argspec="", + arguments=[], + callfunction="CheckPyBytesOneByteSingletons", + ) + + test_PyBytes_Resize_empty_to_nonempty = CPyExtFunction( + lambda args: None, + lambda: ((),), + code=""" + static PyObject *CheckPyBytesResizeEmptyToNonempty(void) { + PyObject *resized_from_empty = PyBytes_FromString(""); + if (_PyBytes_Resize(&resized_from_empty, 3) < 0) { + return NULL; + } + Py_XDECREF(resized_from_empty); + Py_RETURN_NONE; + } + """, + resultspec="O", + argspec="", + arguments=[], + callfunction="CheckPyBytesResizeEmptyToNonempty", + cmpfunc=_compare_resize_empty_to_nonempty, + ) + # PyBytes_FromString test_PyBytes_FromString = CPyExtFunction( lambda arg: bytes(arg[0], "utf-8"), diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_misc.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_misc.py index f5f4f52eb5..125420f671 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_misc.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_misc.py @@ -97,6 +97,31 @@ class TestMisc(CPyExtTestCase): arguments=["PyObject* ellipsis_singleton"], ) + test_empty_tuple_user = CPyExtFunction( + lambda args: 1, + lambda: ( + tuple(), + ), + code=""" + static int CheckEmptyTupleUser(PyObject* ignored) { + PyObject *tuple_empty = PyTuple_New(0); + int result = tuple_empty != NULL && + PyTuple_CheckExact(tuple_empty) && + PyTuple_GET_SIZE(tuple_empty) == 0; + Py_XDECREF(tuple_empty); + return result; + } + """, + resultspec="i", + argspec="", + arguments=["PyObject* ignored"], + callfunction="CheckEmptyTupleUser", + ) + + def test_sre_bytes_empty_user(self): + import re + self.assertEqual(re.sub(b"a", b"", b"banana"), b"bnn") + test_PyImport_ImportModule = CPyExtFunction( _reference_importmodule, lambda: ( diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBytesBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBytesBuiltins.java index a2b5466dae..213e9842f5 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBytesBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextBytesBuiltins.java @@ -218,6 +218,11 @@ static long GraalPyPrivate_Bytes_EmptyWithCapacity(long size) { } } + @CApiBuiltin(ret = PyObjectRawPointer, call = Ignored) + static long GraalPyPrivate_Bytes_Empty() { + return PythonToNativeNewRefNode.executeLongUncached(PFactory.createEmptyBytes(PythonLanguage.get(null))); + } + @CApiBuiltin(ret = PyObjectRawPointer, args = {Py_ssize_t}, call = Ignored) static long GraalPyPrivate_ByteArray_EmptyWithCapacity(long size) { try { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTupleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTupleBuiltins.java index 7768822b1f..1ea514751d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTupleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextTupleBuiltins.java @@ -46,10 +46,12 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectBorrowed; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectPtr; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectRawPointer; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectTransfer; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.Py_ssize_t; import static com.oracle.graal.python.runtime.PythonContext.NATIVE_NULL; +import com.oracle.graal.python.PythonLanguage; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; @@ -58,6 +60,7 @@ import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.EnsurePythonObjectNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNewRefNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.EnsureCapacityNode; import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes.GetItemNode; @@ -72,6 +75,7 @@ import com.oracle.graal.python.nodes.PRaiseNode; import com.oracle.graal.python.nodes.builtins.TupleNodes.GetNativeTupleStorage; import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.object.PFactory; import com.oracle.graal.python.runtime.sequence.storage.NativeSequenceStorage; import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage; import com.oracle.truffle.api.dsl.Bind; @@ -84,6 +88,11 @@ public final class PythonCextTupleBuiltins { + @CApiBuiltin(ret = PyObjectRawPointer, call = Ignored) + static long GraalPyPrivate_Tuple_Empty() { + return PythonToNativeNewRefNode.executeLongUncached(PFactory.createEmptyTuple(PythonLanguage.get(null))); + } + @CApiBuiltin(ret = PyObjectBorrowed, args = {PyObject, Py_ssize_t}, call = Ignored) public abstract static class GraalPyPrivate_Tuple_GetItem extends CApiBinaryBuiltinNode { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java index e1976c748d..b464cd3376 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java @@ -1274,6 +1274,9 @@ public void finalizeCApi(boolean cancelling) { } // The singletons can be freed now freeSingletonNativeWrappers(handleContext); + // Thread-state singleton fields point to handle-table stubs. Clear those roots + // before the generic native stub cleanup below frees the stubs. + context.clearNativeThreadStateSingletons(); // Now we can clear all native memory that was simply allocated from Java. This // must be done after the the singleton wrappers were cleared because they might // also end up in the lookup table and may otherwise be double-freed. diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PThreadState.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PThreadState.java index d3b3ae097c..17bf7287e3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PThreadState.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PThreadState.java @@ -148,9 +148,12 @@ private static long allocateCLayout() { CStructAccess.writePtrField(ptr, CFields.PyThreadState__dict, NULLPTR); CApiContext cApiContext = pythonContext.getCApiContext(); long smallInts = mallocPtrArray(PY_NSMALLNEGINTS + PY_NSMALLPOSINTS); + long bytesCharacters = callocPtrArray(256); long deallocatingState = CStructAccess.getFieldPtr(ptr, CFields.PyThreadState__graalpy_deallocating); long deallocating = mallocPtrArray(GRAALPY_DEALLOC_STACK_INITIAL_CAPACITY); + long singletons = CStructAccess.getFieldPtr(ptr, CFields.PyThreadState__singletons); CStructAccess.writePtrField(ptr, CFields.PyThreadState__small_ints, smallInts); + CStructAccess.writePtrField(singletons, CFields.GraalPySingletons__bytes_characters, bytesCharacters); CStructAccess.writePtrField(deallocatingState, CFields.GraalPyDeallocState__items, deallocating); for (int i = -PY_NSMALLNEGINTS; i < PY_NSMALLPOSINTS; i++) { writePtrArrayElement(smallInts, i + PY_NSMALLNEGINTS, CApiTransitions.HandlePointerConverter.intToPointer(i)); @@ -169,6 +172,22 @@ private static long allocateCLayout() { return ptr; } + public static void clearSingletons(PythonThreadState threadState) { + long nativeCompanion = threadState.getNativePointer(); + if (nativeCompanion == UNINITIALIZED || nativeCompanion == NATIVE_POINTER_FREED) { + return; + } + long singletons = CStructAccess.getFieldPtr(nativeCompanion, CFields.PyThreadState__singletons); + long bytesCharacters = CStructAccess.readPtrField(singletons, CFields.GraalPySingletons__bytes_characters); + CStructAccess.writePtrField(singletons, CFields.GraalPySingletons__tuple_empty, NULLPTR); + CStructAccess.writePtrField(singletons, CFields.GraalPySingletons__bytes_empty, NULLPTR); + assert bytesCharacters != NULLPTR; + // Drop singleton roots before the handle table frees the corresponding stubs. + for (int i = 0; i < 256; i++) { + writePtrArrayElement(bytesCharacters, i, NULLPTR); + } + } + public static int growDeallocatingStack(long nativeThreadState, long newCapacity) { CompilerAsserts.neverPartOfCompilation(); assert nativeThreadState != NULLPTR; @@ -218,6 +237,10 @@ public static void dispose(PythonThreadState threadState, boolean markShuttingDo if (deallocatingItems != NULLPTR) { NativeMemory.free(deallocatingItems); } + long singletons = CStructAccess.getFieldPtr(nativeCompanion, CFields.PyThreadState__singletons); + long bytesCharacters = CStructAccess.readPtrField(singletons, CFields.GraalPySingletons__bytes_characters); + assert bytesCharacters != NULLPTR; + NativeMemory.free(bytesCharacters); // TODO(fa): decref PyThreadState__dict LOGGER.fine(String.format("Freeing (PyThreadState *)0x%x", nativeCompanion)); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java index 2d06ed4e96..a5f266443e 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CFields.java @@ -360,12 +360,17 @@ public enum CFields { PyThreadState__current_exception(PyObject), PyThreadState__dict(PyObject), PyThreadState__small_ints(PyObjectPtr), + PyThreadState__singletons(Pointer), PyThreadState__gc(Pointer), PyThreadState__graalpy_deallocating(Pointer), PyThreadState__py_recursion_limit(Int), PyThreadState__py_recursion_remaining(Int), PyThreadState__c_recursion_remaining(Int), + GraalPySingletons__tuple_empty(PyObject), + GraalPySingletons__bytes_empty(PyObject), + GraalPySingletons__bytes_characters(PyObjectPtr), + GraalPyDeallocState__items(PyObjectPtr), GraalPyDeallocState__len(Int), GraalPyDeallocState__capacity(Int), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java index 10b5791200..e7445ee848 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/structs/CStructs.java @@ -91,6 +91,7 @@ public enum CStructs { PyGetSetDef, PyMemberDef, PyThreadState, + GraalPySingletons, GraalPyDeallocState, wchar_t, long__long, diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java index 96d957c668..b3630699ac 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonContext.java @@ -2742,6 +2742,10 @@ private void applyToAllThreadStates(Consumer action) { } } + public void clearNativeThreadStateSingletons() { + applyToAllThreadStates(PThreadState::clearSingletons); + } + @TruffleBoundary public void setSentinelLockWeakref(WeakReference sentinelLock) { getThreadState(getLanguage()).sentinelLock = sentinelLock;