Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ typedef struct {
PyObject *tuple_empty;
PyObject *bytes_empty;
PyObject **bytes_characters;
PyObject **unicode_characters;
} GraalPySingletons;

typedef struct _stack_chunk {
Expand Down
18 changes: 8 additions & 10 deletions graalpython/com.oracle.graal.python.cext/src/capi.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ 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) {
if (tstate == NULL || GraalPyPrivate_Tuple_Empty == NULL ||
GraalPyPrivate_Bytes_Empty == NULL ||
GraalPyPrivate_InitCharacterSingletons == NULL) {
return;
}
if (tstate->singletons.tuple_empty == NULL) {
Expand All @@ -183,16 +185,12 @@ static inline void graalpy_initialize_thread_state_singletons(PyThreadState *tst
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.bytes_characters == NULL || tstate->singletons.unicode_characters == NULL) {
GraalPyPrivate_InitCharacterSingletons(&tstate->singletons.bytes_characters, &tstate->singletons.unicode_characters);
}
if (tstate->singletons.tuple_empty == NULL || tstate->singletons.bytes_empty == NULL || tstate->singletons.bytes_characters == NULL || tstate->singletons.bytes_characters[0] == NULL) {
if (tstate->singletons.tuple_empty == NULL || tstate->singletons.bytes_empty == NULL ||
tstate->singletons.bytes_characters == NULL || tstate->singletons.bytes_characters[0] == NULL ||
tstate->singletons.unicode_characters == NULL || tstate->singletons.unicode_characters[0] == NULL) {
Py_FatalError("failed to initialize GraalPy thread-state singletons");
}
}
Expand Down
13 changes: 9 additions & 4 deletions graalpython/com.oracle.graal.python.cext/src/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -529,10 +529,8 @@ const unsigned char _Py_ascii_whitespace[] = {
0, 0, 0, 0, 0, 0, 0, 0
};

#if 0 // GraalPy change
/* forward */
static PyObject* get_latin1_char(unsigned char ch);
#endif // GraalPy change
static int unicode_modifiable(PyObject *unicode);


Expand Down Expand Up @@ -2020,12 +2018,12 @@ unicode_write_cstr(PyObject *unicode, Py_ssize_t index,
Py_UNREACHABLE();
}
}
#endif // GraalPy change

static PyObject*
get_latin1_char(Py_UCS1 ch)
{
PyObject *o = LATIN1(ch);
return o;
return Py_NewRef(PyThreadState_Get()->singletons.unicode_characters[ch]);
}

static PyObject*
Expand All @@ -2038,7 +2036,10 @@ unicode_char(Py_UCS4 ch)
if (ch < 256) {
return get_latin1_char(ch);
}
// GraalPy change
return GraalPyPrivate_Unicode_FromOrdinal(ch);

#if 0 // GraalPy change
unicode = PyUnicode_New(1, ch);
if (unicode == NULL)
return NULL;
Expand All @@ -2052,8 +2053,10 @@ unicode_char(Py_UCS4 ch)
}
assert(_PyUnicode_CheckConsistency(unicode, 1));
return unicode;
#endif // GraalPy change
}

#if 0 // GraalPy change
PyObject *
PyUnicode_FromWideChar(const wchar_t *u, Py_ssize_t size)
{
Expand Down Expand Up @@ -3321,6 +3324,7 @@ _PyUnicode_WideCharString_Opt_Converter(PyObject *obj, void *ptr)
Py_TYPE(obj)->tp_name);
return 0;
}
#endif // GraalPy change

PyObject *
PyUnicode_FromOrdinal(int ordinal)
Expand All @@ -3334,6 +3338,7 @@ PyUnicode_FromOrdinal(int ordinal)
return unicode_char((Py_UCS4)ordinal);
}

#if 0 // GraalPy change
PyObject *
PyUnicode_FromObject(PyObject *obj)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1218,6 +1218,109 @@ class TestPyUnicode(CPyExtTestCase):


class TestUnicodeObject(unittest.TestCase):
def test_fromordinal_latin1_cache(self):
TestOrdinalCache = CPyExtType(
"TestOrdinalCache",
r'''
static PyObject* ordinal_value(PyObject* self, PyObject* ordinal_obj) {
long ordinal = PyLong_AsLong(ordinal_obj);
if (ordinal == -1 && PyErr_Occurred()) {
return NULL;
}
return PyUnicode_FromOrdinal((int)ordinal);
}

static PyObject* ordinal_pointer(PyObject* self, PyObject* ordinal_obj) {
PyObject* value = ordinal_value(self, ordinal_obj);
if (value == NULL) {
return NULL;
}
PyObject* result = PyLong_FromVoidPtr(value);
Py_DECREF(value);
return result;
}

static PyObject* interned_a_pointer(PyObject* self, PyObject* unused) {
PyObject* value = PyUnicode_InternFromString("A");
if (value == NULL) {
return NULL;
}
PyObject* result = PyLong_FromVoidPtr(value);
Py_DECREF(value);
return result;
}
''',
tp_methods='''
{"ordinal_value", (PyCFunction)ordinal_value, METH_O, ""},
{"ordinal_pointer", (PyCFunction)ordinal_pointer, METH_O, ""},
{"interned_a_pointer", (PyCFunction)interned_a_pointer, METH_NOARGS, ""}
''',
)
tester = TestOrdinalCache()
pointers = [tester.ordinal_pointer(i) for i in range(256)]
assert len(set(pointers)) == 256
assert tester.interned_a_pointer() == pointers[ord("A")]
for i, pointer in enumerate(pointers):
assert tester.ordinal_value(i) == chr(i)
assert tester.ordinal_pointer(i) == pointer
for _ in range(10):
assert tester.ordinal_pointer(i) == pointer
if GRAALPYTHON:
TestOrdinalMetadata = CPyExtType(
"TestOrdinalMetadata",
r'''
static PyObject* ordinal_metadata(PyObject* self, PyObject* ordinal_obj) {
long ordinal = PyLong_AsLong(ordinal_obj);
if (ordinal == -1 && PyErr_Occurred()) {
return NULL;
}
PyObject* value = PyUnicode_FromOrdinal((int)ordinal);
if (value == NULL) {
return NULL;
}
(void)PyUnicode_DATA(value);
Py_hash_t hash = PyObject_Hash(value);
if (hash == -1) {
Py_DECREF(value);
return NULL;
}
PyObject* result = Py_BuildValue("(nnn)",
(Py_ssize_t)Py_REFCNT(value),
(Py_ssize_t)PyUnicode_CHECK_INTERNED(value),
(Py_ssize_t)hash);
Py_DECREF(value);
return result;
}
''',
tp_methods='''
{"ordinal_metadata", (PyCFunction)ordinal_metadata, METH_O, ""}
''',
)
metadata_tester = TestOrdinalMetadata()
for i in range(256):
refcnt, interned, object_hash = metadata_tester.ordinal_metadata(i)
assert refcnt == 0xFFFFFFFF
assert interned != 0
assert object_hash == hash(chr(i))

non_latin1_pointer = tester.ordinal_pointer(256)
assert non_latin1_pointer not in pointers
with self.assertRaises(ValueError):
tester.ordinal_value(-1)
with self.assertRaises(ValueError):
tester.ordinal_value(0x110000)

import threading
worker_pointer = []

def get_worker_pointer():
worker_pointer.append(tester.ordinal_pointer(42))

worker = threading.Thread(target=get_worker_pointer)
worker.start()
worker.join()
assert worker_pointer == [pointers[42]]

def test_intern(self):
TestIntern = CPyExtType(
"TestIntern",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
import com.oracle.graal.python.runtime.exception.ExceptionUtils;
import com.oracle.graal.python.runtime.exception.PException;
import com.oracle.graal.python.runtime.exception.PythonErrorType;
import com.oracle.graal.python.runtime.nativeaccess.NativeMemory;
import com.oracle.graal.python.runtime.nativeaccess.NativeSignature;
import com.oracle.graal.python.runtime.object.PFactory;
import com.oracle.graal.python.runtime.sequence.storage.NativeByteSequenceStorage;
Expand Down Expand Up @@ -1513,6 +1514,21 @@ static void GraalPyPrivate_InitBuiltinTypesAndStructs(long builtinTypesArrayPoin
}
}

/**
* This must be called after native {@code _PyGC_InitState}. Creating the native wrappers for
* these singletons may recursively create GC-tracked native wrappers, which are immediately
* linked into generation 0 and therefore require initialized generation list heads.
*/
@CApiBuiltin(ret = Void, args = {Pointer, Pointer}, call = Ignored)
static void GraalPyPrivate_InitCharacterSingletons(long bytesCharactersAddr, long unicodeCharactersAddr) {
CApiContext cApiContext = PythonContext.get(null).getCApiContext();
assert cApiContext.getGCState() != NULLPTR;
long bytesCharacters = cApiContext.getOrCreateBytesCharacters();
long unicodeCharacters = cApiContext.getOrCreateUnicodeCharacters();
NativeMemory.writePtr(bytesCharactersAddr, bytesCharacters);
NativeMemory.writePtr(unicodeCharactersAddr, unicodeCharacters);
}

private record ClassPtrPair(PythonManagedClass clazz, long ptr, int typeLookupTableIdx) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,15 @@ static boolean isAnyString(Node inliningTarget, Object obj, GetClassNode getClas
return PGuards.isString(obj) || isStringSubtype(inliningTarget, obj, getClassNode, isSubtypeNode);
}

@CApiBuiltin(ret = PyObjectTransfer, args = {Int}, call = Direct)
abstract static class PyUnicode_FromOrdinal extends CApiUnaryBuiltinNode {
@CApiBuiltin(ret = PyObjectTransfer, args = {PY_UCS4}, call = Ignored)
abstract static class GraalPyPrivate_Unicode_FromOrdinal extends CApiUnaryBuiltinNode {
@Specialization
static Object chr(int value,
@Cached ChrNode chrNode) {
return chrNode.execute(null, value);
@Cached TruffleString.FromCodePointNode fromCodePointNode) {
// assertions are guaranteed by the native caller
assert value >= 0;
assert value <= Character.MAX_CODE_POINT;
return fromCodePointNode.execute(value, TS_ENCODING, true);
}
}

Expand Down Expand Up @@ -1460,6 +1463,7 @@ static long doGeneric(PString stringObject, int charSize, boolean isAscii, Truff
long taggedPointer = stringObject.getNativePointer();
assert HandlePointerConverter.pointsToPyHandleSpace(taggedPointer);
long rawPointer = HandlePointerConverter.pointerToStub(taggedPointer);
long hash = CStructAccess.readLongField(rawPointer, CFields.GraalPyUnicodeObject__hash);
long data = NativeMemory.malloc(dataSize);

// unicode object may have been interned already
Expand All @@ -1470,6 +1474,10 @@ static long doGeneric(PString stringObject, int charSize, boolean isAscii, Truff

assert !GraalPyUnicodeObjectUtil.isCompact(rawPointer);
GraalPyUnicodeObjectUtil.initializeGraalPyUnicodeObject(rawPointer, data, byteLength / charSize, byteLength, charSize, isAscii, interned, false);
if (hash != -1) {
// Preserve hashes precomputed for context-local immortal singletons.
writeLongField(rawPointer, CFields.GraalPyUnicodeObject__hash, hash);
}
writeTruffleStringNode.write(data, unicodeString, unicodeEncoding);
return data;
}
Expand Down
Loading
Loading