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
14 changes: 14 additions & 0 deletions graalpython/com.oracle.graal.python.cext/include/cpython/pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
69 changes: 65 additions & 4 deletions graalpython/com.oracle.graal.python.cext/src/bytesobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -25,6 +26,29 @@

#include <stddef.h>

// 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]*/
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 *
Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 2 additions & 0 deletions graalpython/com.oracle.graal.python.cext/src/capi.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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 */
Expand Down
24 changes: 24 additions & 0 deletions graalpython/com.oracle.graal.python.cext/src/capi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions graalpython/com.oracle.graal.python.cext/src/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions graalpython/com.oracle.graal.python.cext/src/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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"),
Expand Down
Loading
Loading