diff --git a/docs/contributor/IMPLEMENTATION_DETAILS.md b/docs/contributor/IMPLEMENTATION_DETAILS.md index 7b9cef7609..65e1c010a0 100644 --- a/docs/contributor/IMPLEMENTATION_DETAILS.md +++ b/docs/contributor/IMPLEMENTATION_DETAILS.md @@ -315,6 +315,24 @@ When a managed object is passed to a native extension code: We then clear the `PythonObjectReference.strongReference` field, and the memory management is then again left solely to the Java tracing GC. +##### Unicode Objects + +Managed strings exposed to native code use `GraalPyUnicodeObject` as their +native companion. Like CPython, GraalPy supports two allocation layouts: + +* **Compact:** The native allocation is over-allocated, and the character data + follows the structure at `sizeof(GraalPyUnicodeObject)`. +* **Non-compact:** The structure's `data` field points to a separately allocated + buffer. + +Native character data is stored only in this companion, not duplicated in +hidden managed attributes. `PyUnicode_New` delegates to +`GraalPyPrivate_Unicode_New` and eagerly creates a compact companion because a +native-created string is expected to need native data. In contrast, when an +existing managed `PString` is passed to native code, its character data remains +uninitialized until requested through the C API. GraalPy then allocates and +initializes a non-compact buffer lazily. + #### Native Objects Native objects allocated using `PyObject_GC_New` in the native code are backed diff --git a/graalpython/com.oracle.graal.python.cext/include/cpython/unicodeobject.h b/graalpython/com.oracle.graal.python.cext/include/cpython/unicodeobject.h index d7280274d0..1d03b269fb 100644 --- a/graalpython/com.oracle.graal.python.cext/include/cpython/unicodeobject.h +++ b/graalpython/com.oracle.graal.python.cext/include/cpython/unicodeobject.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2020, 2025, Oracle and/or its affiliates. +/* Copyright (c) 2020, 2026, Oracle and/or its affiliates. * Copyright (C) 1996-2020 Python Software Foundation * * Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 @@ -254,9 +254,9 @@ PyAPI_FUNC(int) GraalPyUnicode_KIND(PyObject*); #define PyUnicode_KIND(op) ((enum PyUnicode_Kind)GraalPyUnicode_KIND(_PyObject_CAST(op))) /* Return a void pointer to the raw unicode buffer. */ -static inline void* _PyUnicode_COMPACT_DATA(PyObject *Py_UNUSED(op)) { - // strings are never compact in GraalPy - return NULL; +PyAPI_FUNC(void*) GraalPyUnicode_COMPACT_DATA(PyObject *op); +static inline void* _PyUnicode_COMPACT_DATA(PyObject *op) { + return GraalPyUnicode_COMPACT_DATA(op); } PyAPI_FUNC(void*) GraalPyUnicode_NONCOMPACT_DATA(PyObject *op); diff --git a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c index 38bbd4ccd2..182c71bb4b 100644 --- a/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/unicodeobject.c @@ -1221,12 +1221,9 @@ resize_compact(PyObject *unicode, Py_ssize_t length) } /* - * A managed GraalPyUnicodeObject is physically compact: a single native - * allocation contains its header and the character data immediately after it. - * PyUnicode_IS_COMPACT nevertheless reports false because it denotes - * CPython's PyASCIIObject/PyCompactUnicodeObject layout, which a handle-space - * GraalPyUnicodeObject does not use. Its tagged handle and NativeMemory - * allocation therefore cannot be passed to PyObject_Realloc. + * The unicode objects used by the writer are created using PyUnicode_New. Those + * objects are unmaterialized strings backed by a compact (i.e. over-allocated) + * GraalPyUnicodeObject. Therefore, it cannot be passed to PyObject_Realloc. * * A unicode writer owns an unmaterialized string and only needs to shrink its * logical size when it finishes. This function does not resize the allocation: @@ -1241,7 +1238,7 @@ graalpy_resize_compact(PyObject *unicode, Py_ssize_t length) int kind; assert(unicode_modifiable(unicode)); - assert(!PyUnicode_IS_COMPACT(unicode)); + assert(PyUnicode_IS_COMPACT(unicode)); assert(points_to_py_handle_space(unicode)); native_unicode = (GraalPyUnicodeObject *)pointer_to_stub(unicode); @@ -1421,27 +1418,35 @@ PyObject * PyUnicode_New(Py_ssize_t size, Py_UCS4 maxchar) { // GraalPy change: different implementation - if (size < 0) { - PyErr_SetString(PyExc_SystemError, - "Negative size passed to PyUnicode_New"); - return NULL; - } + int kind; + int is_ascii; if (maxchar < 128) { - return GraalPyPrivate_Unicode_New(size, PyUnicode_1BYTE_KIND, 1); + kind = PyUnicode_1BYTE_KIND; + is_ascii = 1; } else if (maxchar < 256) { - return GraalPyPrivate_Unicode_New(size, PyUnicode_1BYTE_KIND, 0); + kind = PyUnicode_1BYTE_KIND; + is_ascii = 0; } else if (maxchar < 65536) { - return GraalPyPrivate_Unicode_New(size, PyUnicode_2BYTE_KIND, 0); + kind = PyUnicode_2BYTE_KIND; + is_ascii = 0; } else { if (maxchar > MAX_UNICODE) { PyErr_SetString(PyExc_SystemError, "invalid maximum character passed to PyUnicode_New"); return NULL; } - return GraalPyPrivate_Unicode_New(size, PyUnicode_4BYTE_KIND, 0); + kind = PyUnicode_4BYTE_KIND; + is_ascii = 0; } - /* should never be reached */ - return NULL; + + /* Ensure we won't overflow the size. */ + if (size < 0) { + PyErr_SetString(PyExc_SystemError, + "Negative size passed to PyUnicode_New"); + return NULL; + } + + return GraalPyPrivate_Unicode_New(size, kind, is_ascii); } #if SIZEOF_WCHAR_T == 2 @@ -15384,6 +15389,32 @@ GraalPyUnicodeObject_GetKind(GraalPyUnicodeObject *unicode) return GraalPyUnicodeObject_GetKindFromState(unicode->state); } +/* Keep in sync with CApiTransitions.isGraalPyUnicodeObjectCompactFromState. */ +static inline unsigned int +GraalPyUnicodeObject_IsCompactFromState(uint64_t state) +{ + return (state & GRAALPY_UNICODE_IS_COMPACT_FLAG) != 0; +} + +/* Keep in sync with CApiTransitions.isGraalPyUnicodeObjectCompactFromState. */ +static inline unsigned int +GraalPyUnicodeObject_IsCompact(GraalPyUnicodeObject *unicode) +{ + return GraalPyUnicodeObject_IsCompactFromState(unicode->state); +} + +static inline GraalPyUnicodeObject * +GraalpyUnicodeObject_EnsureNativeData(PyObject *op) { + GraalPyUnicodeObject *raw = (GraalPyUnicodeObject *) pointer_to_stub(op); + /* 'kind == 0' is not a valid kind for any unicode object. We use it to indicate that + * the native data was not yet initialized. */ + if (GraalPyUnicodeObject_GetKind(raw) == 0) { + GraalPyPrivate_Unicode_FillNativeData(op); + } + return raw; +} + + unsigned int GraalPyUnicode_CHECK_INTERNED(PyObject *op) { if (points_to_py_handle_space(op)) { GraalPyUnicodeObject *unicode = (GraalPyUnicodeObject *) pointer_to_stub(op); @@ -15399,6 +15430,7 @@ unsigned int GraalPyUnicode_CHECK_INTERNED(PyObject *op) { Py_ssize_t GraalPyUnicode_GET_LENGTH(PyObject* op) { if (points_to_py_handle_space(op)) { + /* The length is initialized eagerly when the managed object gets its native stub. */ return ((GraalPyUnicodeObject *) pointer_to_stub(op))->length; } return _PyASCIIObject_CAST(op)->length; @@ -15406,32 +15438,42 @@ Py_ssize_t GraalPyUnicode_GET_LENGTH(PyObject* op) { unsigned int GraalPyUnicode_IS_ASCII(PyObject* op) { if (points_to_py_handle_space(op)) { - return GraalPyUnicodeObject_IsAscii((GraalPyUnicodeObject *) pointer_to_stub(op)); + return GraalPyUnicodeObject_IsAscii(GraalpyUnicodeObject_EnsureNativeData(op)); } return _PyASCIIObject_CAST(op)->state.ascii; } unsigned int GraalPyUnicode_IS_COMPACT(PyObject* op) { if (points_to_py_handle_space(op)) { - return 0; + return GraalPyUnicodeObject_IsCompact(GraalpyUnicodeObject_EnsureNativeData(op)); } return _PyASCIIObject_CAST(op)->state.compact; } int GraalPyUnicode_KIND(PyObject* op) { if (points_to_py_handle_space(op)) { - return GraalPyUnicodeObject_GetKind((GraalPyUnicodeObject *) pointer_to_stub(op)); + return GraalPyUnicodeObject_GetKind(GraalpyUnicodeObject_EnsureNativeData(op)); } return _PyASCIIObject_CAST(op)->state.kind; } void* GraalPyUnicode_NONCOMPACT_DATA(PyObject* op) { if (points_to_py_handle_space(op)) { - return ((GraalPyUnicodeObject *) pointer_to_stub(op))->data; + return GraalpyUnicodeObject_EnsureNativeData(op)->data; } return _PyUnicodeObject_CAST(op)->data.any; } +void* GraalPyUnicode_COMPACT_DATA(PyObject* op) { + if (points_to_py_handle_space(op)) { + return _Py_STATIC_CAST(void*, GraalpyUnicodeObject_EnsureNativeData(op) + 1); + } + if (PyUnicode_IS_ASCII(op)) { + return _Py_STATIC_CAST(void*, (_PyASCIIObject_CAST(op) + 1)); + } + return _Py_STATIC_CAST(void*, (_PyCompactUnicodeObject_CAST(op) + 1)); +} + #ifdef __cplusplus } #endif diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_unicode.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_unicode.py index f4fba4baed..8844947f88 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_unicode.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_unicode.py @@ -1191,6 +1191,87 @@ def test_intern(self): assert tester.check_is_same_str_ptr(s2) + def test_intern_state_preserved_during_lazy_initialization(self): + TestLazyIntern = CPyExtType( + "TestLazyIntern", + ''' + static PyObject* check_intern_state_after_data(PyObject* Py_UNUSED(self), PyObject* str) { + Py_INCREF(str); + PyUnicode_InternInPlace(&str); + if (str == NULL) { + return NULL; + } + + unsigned int before = PyUnicode_CHECK_INTERNED(str); + void *data = PyUnicode_DATA(str); + unsigned int after = PyUnicode_CHECK_INTERNED(str); + int preserved = data != NULL && before != SSTATE_NOT_INTERNED && after == before; + Py_DECREF(str); + return PyBool_FromLong(preserved); + } + ''', + tp_methods=''' + {"check_intern_state_after_data", (PyCFunction)check_intern_state_after_data, METH_O, ""} + ''', + ) + tester = TestLazyIntern() + string = b'lazy intern state'.decode('ascii') + assert tester.check_intern_state_after_data(string) + + + def test_unicode_data(self): + TestUnicodeData = CPyExtType( + "TestUnicodeData", + ''' + #define NCHARS ((Py_ssize_t) 5) + static PyObject* create_compact(void) { + PyObject* obj = PyUnicode_New(NCHARS, (Py_UCS4) 128); + if (obj == NULL) { + return NULL; + } + void* data = PyUnicode_DATA(obj); + memcpy(data, "hello", NCHARS); + PyUnicode_READY(obj); + return obj; + } + + static PyObject* compact_unicode_as_bytes(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(arg)) { + // PyUnicode_New always returns a compact string (on GraalPy and CPython) + PyObject *compact = create_compact(); + if (!PyUnicode_IS_COMPACT(compact)) { + PyErr_SetString(PyExc_TypeError, "expected compact unicode object"); + return NULL; + } + void *data = PyUnicode_DATA(compact); + Py_ssize_t n = PyUnicode_GET_LENGTH(compact); + return PyBytes_FromStringAndSize(data, n); + } + + static PyObject* noncompact_unicode_as_bytes(PyObject *Py_UNUSED(self), PyObject *arg) { + if (PyUnicode_IS_COMPACT(arg)) { + PyErr_SetString(PyExc_TypeError, "expected non-compact unicode object"); + return NULL; + } + void *data = PyUnicode_DATA(arg); + Py_ssize_t n = PyUnicode_GET_LENGTH(arg); + return PyBytes_FromStringAndSize(data, n); + } + ''', + tp_methods=''' + {"compact_unicode_as_bytes", (PyCFunction)compact_unicode_as_bytes, METH_NOARGS, ""}, + {"noncompact_unicode_as_bytes", (PyCFunction)noncompact_unicode_as_bytes, METH_O, ""} + ''', + ) + tester = TestUnicodeData() + + # on CPython: unicode subclasses are always non-compact + # on GraalPy: managed unicode objects are always non-compact + noncompact_unicode = CustomString("hello") + + assert tester.compact_unicode_as_bytes() == b'hello' + assert tester.noncompact_unicode_as_bytes(noncompact_unicode) == b'hello' + + class TestNativeUnicodeSubclass(unittest.TestCase): def test_builtins(self): s = UnicodeSubclass("asdf") diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java index c539855181..ca04c555ad 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextUnicodeBuiltins.java @@ -68,9 +68,11 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectConstPtr; 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.builtins.objects.cext.capi.transitions.ArgDescriptor.Void; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor._PY_ERROR_HANDLER; -import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.GRAALPY_UNICODE_INTERN_STATE_INTERNED; -import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil.GRAALPY_UNICODE_INTERN_STATE_INTERNED; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil.GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; +import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil.GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED; import static com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.getByteArray; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeLongField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writePtrField; @@ -118,12 +120,12 @@ import com.oracle.graal.python.builtins.objects.cext.capi.PySequenceArrayWrapper; import com.oracle.graal.python.builtins.objects.cext.capi.UnicodeObjectNodes.UnicodeAsWideCharNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; -import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.AllocateNativeObjectStubNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.FirstToNativeNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.HandlePointerConverter; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.EncodeNativeStringNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ReadUnicodeArrayNode; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; @@ -145,6 +147,7 @@ import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PySliceNew; import com.oracle.graal.python.lib.PyTupleGetItem; +import com.oracle.graal.python.lib.PyUnicodeCheckNode; import com.oracle.graal.python.lib.PyUnicodeFSDecoderNode; import com.oracle.graal.python.lib.PyUnicodeFromEncodedObject; import com.oracle.graal.python.lib.RichCmpOp; @@ -336,7 +339,7 @@ static long GraalPyPrivate_Unicode_LookupAndIntern(long objPtr) { str.intern(); if (str.isNative()) { long ptr = HandlePointerConverter.pointerToStub(str.getNativePointer()); - CApiTransitions.setGraalPyUnicodeObjectInterned(ptr, GRAALPY_UNICODE_INTERN_STATE_INTERNED); + GraalPyUnicodeObjectUtil.setInterned(ptr, GRAALPY_UNICODE_INTERN_STATE_INTERNED); } /* * TODO this is not integrated with str.intern, pointer comparisons of two @@ -871,7 +874,8 @@ static long GraalPyPrivate_Unicode_New(long nChars, int charSize, int isAscii) { long taggedPointer = AllocateNativeObjectStubNode.executeUncached(s, PythonBuiltinClassType.PString, CStructs.GraalPyUnicodeObject, initialRefCount, false, extraSize); s.setNativePointer(taggedPointer); long realPointer = HandlePointerConverter.pointerToStub(taggedPointer); - CApiTransitions.initializeGraalPyUnicodeObject(realPointer, nChars, size, charSize, isAscii != 0, GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED); + long data = realPointer + CStructs.GraalPyUnicodeObject.size(); + GraalPyUnicodeObjectUtil.initializeGraalPyUnicodeObject(realPointer, data, nChars, size, charSize, isAscii != 0, GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED, true); return taggedPointer; } @@ -1253,18 +1257,11 @@ static Object doError(Object s, Object sizePtr, } } - @CApiBuiltin(ret = Int, args = {PyObject}, call = Ignored) - abstract static class GraalPyPrivate_Unicode_IsMaterialized extends CApiUnaryBuiltinNode { - - @Specialization - static int pstring(PString s) { - return s.isMaterialized() ? 1 : 0; - } - - @Fallback - static Object other(@SuppressWarnings("unused") Object s) { - return 1; - } + @CApiBuiltin(ret = Int, args = {PyObject}, call = Ignored, acquireGil = false, canRaise = false) + static int GraalPyPrivate_Unicode_IsMaterialized(long objPtr) { + Object obj = NativeToPythonInternalNode.executeUncached(objPtr, false); + assert PyUnicodeCheckNode.executeUncached(obj); + return (!(obj instanceof PString s) || s.isMaterialized()) ? 1 : 0; } // TODO(native-access) Remove or fix and add test for GraalPyPrivate_Unicode_FillUnicode @@ -1375,4 +1372,58 @@ long count(Object string, Object sub, long start, long end, return countNode.execute(string, sub, castLong(start), castLong(end)); } } + + @CApiBuiltin(ret = Void, args = {PyObject}, call = Ignored) + abstract static class GraalPyPrivate_Unicode_FillNativeData extends CApiUnaryBuiltinNode { + @Specialization + static Object doUnicode(PString stringObject, + @Cached TruffleString.GetCodeRangeNode getCodeRangeNode, + @Cached TruffleString.SwitchEncodingNode switchEncodingNode, + @Cached CStructAccess.WriteTruffleStringNode writeTruffleStringNode) { + assert stringObject.isNative(); + if (!stringObject.isMaterialized()) { + throw CompilerDirectives.shouldNotReachHere("unmaterialized PString should already have a native unicode stub"); + } + int unicodeCharSize; + boolean unicodeIsAscii = false; + long unicodeByteLength; + TruffleString unicodeString = stringObject.getMaterialized(); + TruffleString.Encoding unicodeEncoding; + + TruffleString.CodeRange range = getCodeRangeNode.execute(unicodeString, PythonUtils.TS_ENCODING); + if (range == TruffleString.CodeRange.ASCII) { + unicodeIsAscii = true; + unicodeCharSize = 1; + unicodeEncoding = TruffleString.Encoding.US_ASCII; + } else if (range.isSubsetOf(TruffleString.CodeRange.LATIN_1)) { + unicodeCharSize = 1; + unicodeEncoding = TruffleString.Encoding.ISO_8859_1; + } else if (range.isSubsetOf(TruffleString.CodeRange.BMP)) { + unicodeCharSize = 2; + unicodeEncoding = TruffleString.Encoding.UTF_16; + } else { + unicodeCharSize = 4; + unicodeEncoding = TruffleString.Encoding.UTF_32; + } + unicodeString = switchEncodingNode.execute(unicodeString, unicodeEncoding); + unicodeByteLength = unicodeString.byteLength(unicodeEncoding); + long dataSize = unicodeByteLength + unicodeCharSize; + + long taggedPointer = stringObject.getNativePointer(); + assert HandlePointerConverter.pointsToPyHandleSpace(taggedPointer); + long rawPointer = HandlePointerConverter.pointerToStub(taggedPointer); + long data = NativeMemory.malloc(dataSize); + + // unicode object may have been interned already + int interned = GraalPyUnicodeObjectUtil.getInterned(rawPointer); + if (interned == GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED) { + interned = GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; + } + + assert !GraalPyUnicodeObjectUtil.isCompact(rawPointer); + GraalPyUnicodeObjectUtil.initializeGraalPyUnicodeObject(rawPointer, data, unicodeByteLength / unicodeCharSize, unicodeByteLength, unicodeCharSize, unicodeIsAscii, interned, false); + writeTruffleStringNode.write(data, unicodeString, unicodeEncoding); + return PNone.NO_VALUE; + } + } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java index 93a5d225f9..9dbbb0cecf 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/ExternalFunctionNodes.java @@ -118,6 +118,7 @@ import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonReturnNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ConvertPIntToPrimitiveNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.GetIndexNode; import com.oracle.graal.python.builtins.objects.cext.common.CExtCommonNodes.ReadAndClearNativeException; @@ -2339,7 +2340,7 @@ static long doGeneric(PythonContext context, Object[] args, assert EnsurePythonObjectNode.doesNotNeedPromotion(promoted); args[i] = promoted; long nativeString = pythonToNativeNode.executeNewRef(inliningTarget, promoted); - CApiTransitions.setGraalPyUnicodeObjectInterned(HandlePointerConverter.pointerToStub(nativeString), CApiTransitions.GRAALPY_UNICODE_INTERN_STATE_INTERNED); + GraalPyUnicodeObjectUtil.setInterned(HandlePointerConverter.pointerToStub(nativeString), GraalPyUnicodeObjectUtil.GRAALPY_UNICODE_INTERN_STATE_INTERNED); writePtrArrayElement(obItem, i, nativeString); } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/CApiTransitions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/CApiTransitions.java index bc1ec478ed..9a4a6229f3 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/CApiTransitions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/CApiTransitions.java @@ -47,7 +47,6 @@ import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PollingState.RQ_READY; import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PollingState.RQ_UNINITIALIZED; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readIntField; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readLongField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeDoubleField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeIntField; import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeLongField; @@ -73,7 +72,6 @@ import java.util.logging.Level; import com.oracle.graal.python.PythonLanguage; -import com.oracle.graal.python.annotations.CApiConstant; import com.oracle.graal.python.builtins.PythonBuiltinClassType; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PythonAbstractObject; @@ -170,21 +168,6 @@ public abstract class CApiTransitions { private static final TruffleLogger LOGGER = CApiContext.getLogger(CApiTransitions.class); - @CApiConstant // - public static final int GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED = 0; - @CApiConstant // - public static final int GRAALPY_UNICODE_INTERN_STATE_INTERNED = 1; - @CApiConstant // - public static final int GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED = 2; - @CApiConstant // - private static final int GRAALPY_UNICODE_KIND_MASK = 0x7; - @CApiConstant // - private static final long GRAALPY_UNICODE_IS_ASCII_FLAG = 1L << 3; - @CApiConstant // - private static final int GRAALPY_UNICODE_INTERN_STATE_SHIFT = 4; - @CApiConstant // - private static final long GRAALPY_UNICODE_INTERN_STATE_MASK = 0x3L << GRAALPY_UNICODE_INTERN_STATE_SHIFT; - enum PollingState { /** startup barrier not finished yet, polling must not run */ RQ_UNINITIALIZED, @@ -458,68 +441,6 @@ public static void registerNativeSequenceStorage(NativeSequenceStorage storage) handleContext.nativeStorageReferences.add(ref); } - public static long initializeGraalPyUnicodeObject(long rawPointer, long elements, long byteLength, int charSize, boolean isAscii, int interned) { - assert charSize == 1 || charSize == 2 || charSize == 4; - assert byteLength == elements * charSize; - assert interned == GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED || interned == GRAALPY_UNICODE_INTERN_STATE_INTERNED || interned == GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; - long data = rawPointer + CStructs.GraalPyUnicodeObject.size(); - writeLongField(rawPointer, CFields.GraalPyUnicodeObject__length, elements); - writeLongField(rawPointer, CFields.GraalPyUnicodeObject__byte_length, byteLength); - writeLongField(rawPointer, CFields.GraalPyUnicodeObject__hash, -1); - writeLongField(rawPointer, CFields.GraalPyUnicodeObject__state, createGraalPyUnicodeObjectState(charSize, isAscii, interned)); - writePtrField(rawPointer, CFields.GraalPyUnicodeObject__data, data); - NativeMemory.memset(data + byteLength, (byte) 0, charSize); - return data; - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_CreateState. - private static long createGraalPyUnicodeObjectState(int charSize, boolean isAscii, int interned) { - assert (charSize & ~GRAALPY_UNICODE_KIND_MASK) == 0; - return charSize | encodeGraalPyUnicodeObjectAscii(isAscii) | encodeGraalPyUnicodeObjectInterned(interned); - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_EncodeAscii. - private static long encodeGraalPyUnicodeObjectAscii(boolean isAscii) { - return isAscii ? GRAALPY_UNICODE_IS_ASCII_FLAG : 0; - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_EncodeInterned. - private static long encodeGraalPyUnicodeObjectInterned(int interned) { - return (long) interned << GRAALPY_UNICODE_INTERN_STATE_SHIFT; - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_GetInternedFromState. - private static int getGraalPyUnicodeObjectInternedFromState(long state) { - return (int) ((state & GRAALPY_UNICODE_INTERN_STATE_MASK) >> GRAALPY_UNICODE_INTERN_STATE_SHIFT); - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_IsAsciiFromState. - private static boolean isGraalPyUnicodeObjectAsciiFromState(long state) { - return (state & GRAALPY_UNICODE_IS_ASCII_FLAG) != 0; - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_GetKindFromState. - private static int getGraalPyUnicodeObjectKindFromState(long state) { - return (int) (state & GRAALPY_UNICODE_KIND_MASK); - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_UpdateInterned. - private static long updateGraalPyUnicodeObjectInterned(long state, int interned) { - return (state & ~GRAALPY_UNICODE_INTERN_STATE_MASK) | encodeGraalPyUnicodeObjectInterned(interned); - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_GetKind. - public static int getGraalPyUnicodeObjectKind(long rawPointer) { - return getGraalPyUnicodeObjectKindFromState(readLongField(rawPointer, CFields.GraalPyUnicodeObject__state)); - } - - // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_SetInterned. - public static void setGraalPyUnicodeObjectInterned(long rawPointer, int interned) { - assert interned == GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED || interned == GRAALPY_UNICODE_INTERN_STATE_INTERNED || interned == GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; - long state = readLongField(rawPointer, CFields.GraalPyUnicodeObject__state); - writeLongField(rawPointer, CFields.GraalPyUnicodeObject__state, updateGraalPyUnicodeObjectInterned(state, interned)); - } - public static final class PyCapsuleReference extends IdReference { private final PyCapsule.CapsuleData data; @@ -901,11 +822,28 @@ private static void freeNativeStub(long pointer, boolean gc) { assert HandlePointerConverter.pointsToPyHandleSpace(pointer); assert !HandlePointerConverter.pointsToPyIntHandle(pointer); assert !HandlePointerConverter.pointsToPyFloatHandle(pointer); + boolean isLoggable = LOGGER.isLoggable(Level.FINE); + long rawPointer = HandlePointerConverter.pointerToStub(pointer); + if (GraalPyUnicodeObjectUtil.isNonCompactGraalPyUnicodeObject(rawPointer)) { + if (isLoggable) { + LOGGER.fine(PythonUtils.formatJString("releasing native non-compact GraalPyUnicodeObject stub 0x%x", rawPointer)); + } + long nonCompactDataPointer = GraalPyUnicodeObjectUtil.getNonCompactDataPointer(rawPointer); + /* + * If 'rawPointer + sizeof(GraalPyUnicodeObject) == nonCompactDataPointer', this indicates that the compact flag is incorrect. However, this is + * not guaranteed because the allocated memory of the GraalPyUnicodeObject and the data could, by accident, be consecutive. So, we cannot assert this. + */ + if (isLoggable && rawPointer + CStructs.GraalPyUnicodeObject.size() == nonCompactDataPointer) { + LOGGER.fine(PythonUtils.formatJString("Non-compact data pointer looks like compact data (GraalPyUnicodeObject=0x%x, data=0x%x)", rawPointer, nonCompactDataPointer)); + } + free(nonCompactDataPointer); + } if (gc) { PyObjectGCDelNode.executeUncached(pointer); } else { - long rawPointer = HandlePointerConverter.pointerToStub(pointer); - LOGGER.fine(() -> PythonUtils.formatJString("releasing native object stub 0x%x", rawPointer)); + if (isLoggable) { + LOGGER.fine(PythonUtils.formatJString("releasing native object stub 0x%x", rawPointer)); + } free(rawPointer); } } @@ -1385,9 +1323,7 @@ static long doOther(Node inliningTarget, PythonObject pythonObject, long initial @Exclusive @Cached InlinedConditionProfile isStringObjectProfile, @Cached GetPythonObjectClassNode getClassNode, @Cached(inline = false) GetTypeFlagsNode getTypeFlagsNode, - @Cached TruffleString.GetCodeRangeNode getCodeRangeNode, - @Cached TruffleString.SwitchEncodingNode switchEncodingNode, - @Cached CStructAccess.WriteTruffleStringNode writeTruffleStringNode, + @Cached TruffleString.CodePointLengthNode codePointLengthNode, @Exclusive @Cached AllocateNativeObjectStubNode allocateNativeObjectStubNode) { // for types, we always need to allocate the full PyTypeObject @@ -1399,47 +1335,21 @@ static long doOther(Node inliningTarget, PythonObject pythonObject, long initial Object type = getClassNode.execute(inliningTarget, pythonObject); CStructs ctype; - TruffleString unicodeString = null; - TruffleString.Encoding unicodeEncoding = null; - int unicodeCharSize = 0; - boolean unicodeIsAscii = false; - long unicodeByteLength = 0; - long extraSize = 0; if (isVarObjectProfile.profile(inliningTarget, pythonObject instanceof PTuple)) { ctype = CStructs.GraalPyVarObject; } else if (isFloatObjectProfile.profile(inliningTarget, pythonObject instanceof PFloat)) { ctype = CStructs.GraalPyFloatObject; } else if (isStringObjectProfile.profile(inliningTarget, pythonObject instanceof PString)) { ctype = CStructs.GraalPyUnicodeObject; - PString stringObject = (PString) pythonObject; - if (!stringObject.isMaterialized()) { + if (!((PString) pythonObject).isMaterialized()) { throw CompilerDirectives.shouldNotReachHere("unmaterialized PString should already have a native unicode stub"); } - unicodeString = stringObject.getMaterialized(); - TruffleString.CodeRange range = getCodeRangeNode.execute(unicodeString, PythonUtils.TS_ENCODING); - if (range == TruffleString.CodeRange.ASCII) { - unicodeIsAscii = true; - unicodeCharSize = 1; - unicodeEncoding = TruffleString.Encoding.US_ASCII; - } else if (range.isSubsetOf(TruffleString.CodeRange.LATIN_1)) { - unicodeCharSize = 1; - unicodeEncoding = TruffleString.Encoding.ISO_8859_1; - } else if (range.isSubsetOf(TruffleString.CodeRange.BMP)) { - unicodeCharSize = 2; - unicodeEncoding = TruffleString.Encoding.UTF_16; - } else { - unicodeCharSize = 4; - unicodeEncoding = TruffleString.Encoding.UTF_32; - } - unicodeString = switchEncodingNode.execute(unicodeString, unicodeEncoding); - unicodeByteLength = unicodeString.byteLength(unicodeEncoding); - extraSize = unicodeByteLength + unicodeCharSize; } else { ctype = CStructs.GraalPyObject; } boolean gc = isGcProfile.profile(inliningTarget, (getTypeFlagsNode.execute(type) & TypeFlags.HAVE_GC) != 0); - long taggedPointer = allocateNativeObjectStubNode.execute(inliningTarget, pythonObject, type, ctype, initialRefCount, gc, extraSize); + long taggedPointer = allocateNativeObjectStubNode.execute(inliningTarget, pythonObject, type, ctype, initialRefCount, gc, 0); // allocate a native stub object (C type: GraalPy*Object) if (ctype == CStructs.GraalPyVarObject) { @@ -1459,9 +1369,10 @@ static long doOther(Node inliningTarget, PythonObject pythonObject, long initial } else if (ctype == CStructs.GraalPyUnicodeObject) { assert pythonObject instanceof PString; long realPointer = HandlePointerConverter.pointerToStub(taggedPointer); - long data = initializeGraalPyUnicodeObject(realPointer, unicodeByteLength / unicodeCharSize, unicodeByteLength, unicodeCharSize, unicodeIsAscii, - GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED); - writeTruffleStringNode.write(data, unicodeString, unicodeEncoding); + PString string = (PString) pythonObject; + writeLongField(realPointer, CFields.GraalPyUnicodeObject__length, codePointLengthNode.execute(string.getMaterialized(), PythonUtils.TS_ENCODING)); + // AllocateNativeObjectStubNode zeroes the whole allocated struct. We therefore expect the state field to be uninitialized. + assert !GraalPyUnicodeObjectUtil.isStateInitialized(realPointer); } return taggedPointer; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/GraalPyUnicodeObjectUtil.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/GraalPyUnicodeObjectUtil.java new file mode 100644 index 0000000000..76fefcc642 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/transitions/GraalPyUnicodeObjectUtil.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026, 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 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.oracle.graal.python.builtins.objects.cext.capi.transitions; + +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyObject__ob_type; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readLongField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeLongField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writePtrField; + +import com.oracle.graal.python.annotations.CApiConstant; +import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.HandlePointerConverter; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonClassInternalNode; +import com.oracle.graal.python.builtins.objects.cext.structs.CFields; +import com.oracle.graal.python.builtins.objects.cext.structs.CStructs; +import com.oracle.graal.python.builtins.objects.type.TypeFlags; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinClassProfile; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; + +public final class GraalPyUnicodeObjectUtil { + + @CApiConstant // + public static final int GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED = 0; + @CApiConstant // + public static final int GRAALPY_UNICODE_INTERN_STATE_INTERNED = 1; + @CApiConstant // + public static final int GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED = 2; + @CApiConstant // + private static final int GRAALPY_UNICODE_KIND_MASK = 0x7; + @CApiConstant // + private static final long GRAALPY_UNICODE_IS_ASCII_FLAG = 1L << 3; + @CApiConstant // + private static final int GRAALPY_UNICODE_INTERN_STATE_SHIFT = 4; + @CApiConstant // + private static final long GRAALPY_UNICODE_INTERN_STATE_MASK = 0x3L << GRAALPY_UNICODE_INTERN_STATE_SHIFT; + @CApiConstant // + private static final long GRAALPY_UNICODE_IS_COMPACT_FLAG = 1L << 6; + + private GraalPyUnicodeObjectUtil() { + } + + public static void initializeGraalPyUnicodeObject(long rawPointer, long data, long length, long byteLength, int charSize, boolean isAscii, int interned, boolean compact) { + assert charSize == 1 || charSize == 2 || charSize == 4; + assert byteLength == length * charSize; + assert interned == GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED || interned == GRAALPY_UNICODE_INTERN_STATE_INTERNED || interned == GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; + // If compact, the GraalPyUnicodeObject struct was over-allocated and the data bytes are after the last field. + assert !compact || (data == rawPointer + CStructs.GraalPyUnicodeObject.size()); + + writeLongField(rawPointer, CFields.GraalPyUnicodeObject__length, length); + writeLongField(rawPointer, CFields.GraalPyUnicodeObject__byte_length, byteLength); + writeLongField(rawPointer, CFields.GraalPyUnicodeObject__hash, -1); + writeLongField(rawPointer, CFields.GraalPyUnicodeObject__state, createState(charSize, isAscii, interned, compact)); + writePtrField(rawPointer, CFields.GraalPyUnicodeObject__data, data); + assert isStateInitialized(rawPointer); + // Unicode data must be followed by one kind-sized NUL code unit. + NativeMemory.memset(data + byteLength, (byte) 0, charSize); + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_CreateState. + private static long createState(int charSize, boolean isAscii, int interned, boolean compact) { + assert (charSize & ~GRAALPY_UNICODE_KIND_MASK) == 0; + return charSize | encodeAscii(isAscii) | encodeInterned(interned) | encodeCompact(compact); + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_EncodeAscii. + private static long encodeAscii(boolean isAscii) { + return isAscii ? GRAALPY_UNICODE_IS_ASCII_FLAG : 0; + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_EncodeInterned. + private static long encodeInterned(int interned) { + return (long) interned << GRAALPY_UNICODE_INTERN_STATE_SHIFT; + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_EncodeCompact. + private static long encodeCompact(boolean compact) { + return compact ? GRAALPY_UNICODE_IS_COMPACT_FLAG : 0; + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_GetInterned. + public static int getInterned(long rawPointer) { + long state = readLongField(rawPointer, CFields.GraalPyUnicodeObject__state); + return (int) ((state & GRAALPY_UNICODE_INTERN_STATE_MASK) >> GRAALPY_UNICODE_INTERN_STATE_SHIFT); + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_GetKindFromState. + private static int getKindFromState(long state) { + return (int) (state & GRAALPY_UNICODE_KIND_MASK); + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_UpdateInterned. + private static long updateInterned(long state, int interned) { + return (state & ~GRAALPY_UNICODE_INTERN_STATE_MASK) | encodeInterned(interned); + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_GetKind. + public static int getKind(long rawPointer) { + return getKindFromState(readLongField(rawPointer, CFields.GraalPyUnicodeObject__state)); + } + + public static void setInterned(long rawPointer, int interned) { + assert interned == GRAALPY_UNICODE_INTERN_STATE_UNDETERMINED || interned == GRAALPY_UNICODE_INTERN_STATE_INTERNED || interned == GRAALPY_UNICODE_INTERN_STATE_NOT_INTERNED; + long state = readLongField(rawPointer, CFields.GraalPyUnicodeObject__state); + writeLongField(rawPointer, CFields.GraalPyUnicodeObject__state, updateInterned(state, interned)); + } + + // Keep in sync with unicodeobject.c:GraalPyUnicodeObject_IsCompact. + public static boolean isCompact(long rawPointer) { + assert !HandlePointerConverter.pointsToPyHandleSpace(rawPointer); + return (readLongField(rawPointer, CFields.GraalPyUnicodeObject__state) & GRAALPY_UNICODE_IS_COMPACT_FLAG) != 0; + } + + public static boolean isStateInitialized(long rawPointer) { + return getKind(rawPointer) != 0; + } + + /** + * Given the raw (untagged) pointer to a {@code GraalPyObject}, this method checks if the object is a unicode object with non-compact data. + */ + public static boolean isNonCompactGraalPyUnicodeObject(long rawPointer) { + assert !HandlePointerConverter.pointsToPyHandleSpace(rawPointer); + long obType = readPtrField(rawPointer, PyObject__ob_type); + boolean isUnicodeSubclass = (readLongField(obType, CFields.PyTypeObject__tp_flags) & TypeFlags.UNICODE_SUBCLASS) != 0L; + assert !HandlePointerConverter.pointsToPyHandleSpace(obType); + // During finalization, the native reference for obType may already have been freed, so it cannot be converted back to a managed class. + assert PythonContext.get(null).isFinalizing() || IsBuiltinClassProfile.profileClassSlowPath(NativeToPythonClassInternalNode.executeUncached(obType), + PythonBuiltinClassType.PString) == isUnicodeSubclass; + return isUnicodeSubclass && !GraalPyUnicodeObjectUtil.isCompact(rawPointer); + } + + /** Similar to {@code unicodeobject.h:_PyUnicode_NONCOMPACT_DATA} */ + public static long getNonCompactDataPointer(long rawPointer) { + assert isNonCompactGraalPyUnicodeObject(rawPointer); + return readPtrField(rawPointer, CFields.GraalPyUnicodeObject__data); + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringNodes.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringNodes.java index 7da4308958..747c173241 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringNodes.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/str/StringNodes.java @@ -58,9 +58,9 @@ import com.oracle.graal.python.builtins.objects.cext.capi.ExternalFunctionInvoker; import com.oracle.graal.python.builtins.objects.cext.capi.NativeCAPISymbol; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTiming; -import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.HandlePointerConverter; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.GraalPyUnicodeObjectUtil; import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess; import com.oracle.graal.python.builtins.objects.common.SequenceNodes; @@ -149,7 +149,7 @@ static TruffleString doNative(PString x, boolean copyNativeData, } catch (OverflowException e) { throw CompilerDirectives.shouldNotReachHere(e); } - int kind = CApiTransitions.getGraalPyUnicodeObjectKind(ptr); + int kind = GraalPyUnicodeObjectUtil.getKind(ptr); TruffleString.CompactionLevel compactionLevel = switch (kind) { case 1 -> TruffleString.CompactionLevel.S1; case 2 -> TruffleString.CompactionLevel.S2; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyUnicodeCheckNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyUnicodeCheckNode.java index a8dda5fefc..b1d40b9487 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyUnicodeCheckNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyUnicodeCheckNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -40,10 +40,18 @@ */ package com.oracle.graal.python.lib; +import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyObject__ob_type; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readLongField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.readPtrField; + import com.oracle.graal.python.builtins.PythonBuiltinClassType; +import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject; +import com.oracle.graal.python.builtins.objects.cext.structs.CFields; import com.oracle.graal.python.builtins.objects.str.PString; +import com.oracle.graal.python.builtins.objects.type.TypeFlags; import com.oracle.graal.python.nodes.PNodeWithContext; import com.oracle.graal.python.nodes.classes.IsSubtypeNode; +import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile; import com.oracle.graal.python.nodes.object.GetClassNode; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Fallback; @@ -78,6 +86,14 @@ static boolean doPString(@SuppressWarnings("unused") PString object) { return true; } + @Specialization + public static boolean doNative(PythonAbstractNativeObject nativeObject) { + long obType = readPtrField(nativeObject.pointer, PyObject__ob_type); + boolean isUnicodeSubclass = (readLongField(obType, CFields.PyTypeObject__tp_flags) & TypeFlags.UNICODE_SUBCLASS) != 0L; + assert IsBuiltinObjectProfile.profileObjectUncached(nativeObject, PythonBuiltinClassType.PString) == isUnicodeSubclass; + return isUnicodeSubclass; + } + @Fallback static boolean doOther(Node inliningTarget, Object object, @Cached GetClassNode getClass,