Skip to content

Commit 4a5efed

Browse files
committed
[GR-50282] Add support for native PickleBuffer as_buffer
PullRequest: graalpython/3254
2 parents 70ef9c9 + 260d106 commit 4a5efed

File tree

10 files changed

+217
-6
lines changed

10 files changed

+217
-6
lines changed

graalpython/com.oracle.graal.python.cext/src/capi.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ typedef struct {
9696
PyObject *mapping;
9797
} mappingproxyobject;
9898

99+
typedef struct {
100+
PyObject_HEAD
101+
/* The view exported by the original object */
102+
Py_buffer view;
103+
PyObject *weakreflist;
104+
} PyPickleBufferObject;
105+
106+
99107
static void object_dealloc(PyObject *self) {
100108
Py_TYPE(self)->tp_free(self);
101109
}
@@ -278,6 +286,16 @@ void bytearray_releasebuffer(PyByteArrayObject *obj, Py_buffer *view);
278286
int memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags);
279287
void memory_releasebuf(PyMemoryViewObject *self, Py_buffer *view);
280288

289+
/* PICKLEBUFFER */
290+
static int
291+
picklebuf_getbuf(PyPickleBufferObject *self, Py_buffer *view, int flags)
292+
{
293+
PyObject *self_view_obj = GraalPyTruffle_PickleBuffer_viewobj(self);
294+
return PyObject_GetBuffer(self_view_obj, view, flags);
295+
}
296+
297+
static void empty_releasebuf(PyObject *self, Py_buffer *view) {}
298+
281299
static void initialize_bufferprocs() {
282300
static PyBufferProcs bytes_as_buffer = {
283301
(getbufferproc)bytes_buffer_getbuffer, /* bf_getbuffer */
@@ -305,6 +323,12 @@ static void initialize_bufferprocs() {
305323
array_as_buffer.bf_releasebuffer = GraalPyTruffle_Array_releasebuffer,
306324
Arraytype.tp_as_buffer = &array_as_buffer;
307325
GraalPy_set_PyTypeObject_tp_as_buffer(&Arraytype, &array_as_buffer);
326+
327+
static PyBufferProcs picklebuf_as_buffer;
328+
picklebuf_as_buffer.bf_getbuffer = (getbufferproc)picklebuf_getbuf,
329+
picklebuf_as_buffer.bf_releasebuffer = empty_releasebuf,
330+
PyPickleBuffer_Type.tp_as_buffer = &picklebuf_as_buffer;
331+
GraalPy_set_PyTypeObject_tp_as_buffer(&PyPickleBuffer_Type, &picklebuf_as_buffer);
308332
}
309333

310334
int is_builtin_type(PyTypeObject *tp) {

graalpython/com.oracle.graal.python.cext/src/capi.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ PY_TRUFFLE_TYPE(UnionType_Type, "_ctypes.UnionType", &PyType_Type, sizeof(PyO
342342
PY_TRUFFLE_TYPE(PyCPointerType_Type, "PyCPointerType", &PyType_Type, sizeof(PyObject)) \
343343
PY_TRUFFLE_TYPE(PyCArrayType_Type, "PyCArrayType", &PyType_Type, sizeof(PyObject)) \
344344
PY_TRUFFLE_TYPE(PyCoro_Type, "coroutine", &PyType_Type, sizeof(PyCoroObject)) \
345+
/* PyPickleBufferObject (PyObject_HEAD + Py_buffer + PyObject*) is defined within Objects/picklebufobject.c, so its not exposed. */ \
346+
PY_TRUFFLE_TYPE(PyPickleBuffer_Type, "_pickle.PickleBuffer", &PyType_Type, sizeof(PyPickleBufferObject)) \
345347
PY_TRUFFLE_TYPE_UNIMPLEMENTED(_PyAIterWrapper_Type) \
346348
PY_TRUFFLE_TYPE_UNIMPLEMENTED(_PyAsyncGenASend_Type) \
347349
PY_TRUFFLE_TYPE_UNIMPLEMENTED(_PyAsyncGenAThrow_Type) \
@@ -385,7 +387,6 @@ PY_TRUFFLE_TYPE_UNIMPLEMENTED(PyODictItems_Type) \
385387
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PyODictIter_Type) \
386388
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PyODictKeys_Type) \
387389
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PyODictValues_Type) \
388-
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PyPickleBuffer_Type) \
389390
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PyRangeIter_Type) \
390391
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PySetIter_Type) \
391392
PY_TRUFFLE_TYPE_UNIMPLEMENTED(PySortWrapper_Type) \

graalpython/com.oracle.graal.python.cext/src/obmalloc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -110,7 +110,7 @@ int PyTruffle_AllocMemory(size_t size) {
110110

111111
void PyTruffle_FreeMemory(size_t size) {
112112
if (PyTruffle_AllocatedMemory < size) {
113-
PyTruffle_Log(PY_TRUFFLE_LOG_INFO, "PyTruffle_FreeMemory: freed memory size (%lu) is larger than allocated memory size (%lu)\n", size, PyTruffle_AllocMemory);
113+
PyTruffle_Log(PY_TRUFFLE_LOG_INFO, "PyTruffle_FreeMemory: freed memory size (%lu) is larger than allocated memory size (%lu)\n", size, PyTruffle_AllocatedMemory);
114114
PyTruffle_AllocatedMemory = size;
115115
}
116116
PyTruffle_AllocatedMemory -= size;

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_bytes.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,45 @@ def test_create_from_buffer_exception(self):
582582
self.assertRaises(ValueError, bytes, TestType())
583583
self.assertRaises(ValueError, bytearray, TestType())
584584

585+
def test_tp_as_buffer_pickle(self):
586+
TestAsBufferPickle = CPyExtType(
587+
"TestAsBufferPickle",
588+
"""
589+
static PyObject* get_bytes(PyObject* self, PyObject *obj) {
590+
Py_buffer view;
591+
if (PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO) != 0)
592+
return NULL;
593+
PyObject* bytes = PyBytes_FromStringAndSize(view.buf, view.len);
594+
PyBuffer_Release(&view);
595+
return bytes;
596+
}
597+
""",
598+
tp_methods='{"get_bytes", (PyCFunction)get_bytes, METH_O | METH_CLASS, ""}',
599+
)
600+
TestType = CPyExtType(
601+
"TestMemoryViewBufferPickle",
602+
"""
603+
char buf[] = {1,2,3,4};
604+
int getbuffer(TestMemoryViewBufferPickleObject *self, Py_buffer *view, int flags) {
605+
return PyBuffer_FillInfo(view, (PyObject*)self, buf, 4, 1, flags);
606+
}
607+
void releasebuffer(TestMemoryViewBufferPickleObject *self, Py_buffer *view) {}
608+
609+
static PyBufferProcs as_buffer = {
610+
(getbufferproc)getbuffer,
611+
(releasebufferproc)releasebuffer,
612+
};
613+
""",
614+
tp_as_buffer='&as_buffer',
615+
)
616+
obj = TestType()
617+
b = bytes([1,2,3,4]) # same as `buf[] = {1,2,3,4};`
618+
import pickle
619+
b2 = TestAsBufferPickle.get_bytes(pickle.PickleBuffer(memoryview(obj)))
620+
b1 = TestAsBufferPickle.get_bytes(pickle.PickleBuffer(bytearray([1,2,3,4])))
621+
assert b == b1
622+
assert b == b2
623+
585624

586625
class TestNativeSubclass(unittest.TestCase):
587626
def test_builtins(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.graal.python.builtins.modules.cext;
42+
43+
import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored;
44+
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObject;
45+
import static com.oracle.graal.python.builtins.objects.cext.capi.transitions.ArgDescriptor.PyObjectBorrowed;
46+
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError;
47+
48+
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin;
49+
import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode;
50+
import com.oracle.graal.python.builtins.modules.pickle.PPickleBuffer;
51+
import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary;
52+
import com.oracle.graal.python.nodes.ErrorMessages;
53+
import com.oracle.graal.python.nodes.PRaiseNode;
54+
import com.oracle.truffle.api.dsl.Bind;
55+
import com.oracle.truffle.api.dsl.Cached;
56+
import com.oracle.truffle.api.dsl.Specialization;
57+
import com.oracle.truffle.api.library.CachedLibrary;
58+
import com.oracle.truffle.api.nodes.Node;
59+
60+
public final class PythonCextPickleBufferBuiltins {
61+
62+
@CApiBuiltin(ret = PyObjectBorrowed, args = {PyObject}, call = Ignored)
63+
abstract static class PyTruffle_PickleBuffer_viewobj extends CApiUnaryBuiltinNode {
64+
@Specialization
65+
static Object getviewobj(PPickleBuffer object,
66+
@Bind("this") Node inliningTarget,
67+
@CachedLibrary(limit = "3") PythonBufferAccessLibrary bufferLib,
68+
@Cached PRaiseNode.Lazy raiseNode) {
69+
Object owner = null;
70+
if (object.getView() != null) {
71+
owner = bufferLib.getOwner(object.getView());
72+
}
73+
if (owner == null) {
74+
throw raiseNode.get(inliningTarget).raise(ValueError, ErrorMessages.OP_FORBIDDEN_ON_OBJECT, "PickleBuffer");
75+
}
76+
return owner;
77+
}
78+
79+
}
80+
}

graalpython/lib-graalpython/patches/Cython/Cython-0.29.32.patch

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,19 @@ index b2c67dc..ad80ca4 100644
202202
}
203203
#endif
204204
else if (PyMethod_Check(method)) {
205+
diff --git a/Cython/Utility/Exceptions.c b/Cython/Utility/Exceptions.c
206+
index 2cd4b60..2c92132 100644
207+
--- a/Cython/Utility/Exceptions.c
208+
+++ b/Cython/Utility/Exceptions.c
209+
@@ -661,7 +661,7 @@ static int __Pyx_CLineForTraceback(CYTHON_NCP_UNUSED PyThreadState *tstate, int
210+
211+
__Pyx_ErrFetchInState(tstate, &ptype, &pvalue, &ptraceback);
212+
213+
-#if CYTHON_COMPILING_IN_CPYTHON
214+
+#if 0
215+
cython_runtime_dict = _PyObject_GetDictPtr(${cython_runtime_cname});
216+
if (likely(cython_runtime_dict)) {
217+
__PYX_PY_DICT_LOOKUP_IF_MODIFIED(
205218
diff --git a/Cython/Utility/ModuleSetupCode.c b/Cython/Utility/ModuleSetupCode.c
206219
index f8bf885..94d0ca6 100644
207220
--- a/Cython/Utility/ModuleSetupCode.c
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[[rules]]
2+
version = '== 1.0.8'
3+
patch = 'msgpack-1.0.8.patch'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
diff --git a/pyproject.toml b/pyproject.toml
2+
index f9af967..1f5a002 100644
3+
--- a/pyproject.toml
4+
+++ b/pyproject.toml
5+
@@ -2,7 +2,7 @@
6+
requires = [
7+
# Also declared in requirements.txt, if updating here please also update
8+
# there
9+
- "Cython~=3.0.8",
10+
+ "Cython < 3",
11+
"setuptools >= 35.0.2",
12+
]
13+
build-backend = "setuptools.build_meta"

graalpython/lib-graalpython/patches/ray/ray-2.9.1.patch

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,36 @@ index bee0ae6..ee0e3ea 100644
123123
"actor_method_names": json.dumps(list(actor_method_names)),
124124
}
125125

126+
diff --git a/python/ray/_private/resource_spec.py b/python/ray/_private/resource_spec.py
127+
index 69780aade9..3be8cbbc4c 100644
128+
--- a/python/ray/_private/resource_spec.py
129+
+++ b/python/ray/_private/resource_spec.py
130+
@@ -160,9 +160,22 @@ class ResourceSpec(
131+
if is_head:
132+
resources[HEAD_NODE_RESOURCE_NAME] = 1.0
133+
134+
- num_cpus = self.num_cpus
135+
- if num_cpus is None:
136+
- num_cpus = ray._private.utils.get_num_cpus()
137+
+ try:
138+
+ system_num_cpus = __graalpython__.get_max_process_count()
139+
+ factor = min(system_num_cpus, 4)
140+
+ num_cpus = system_num_cpus // factor
141+
+ if self.num_cpus:
142+
+ num_cpus = min(self.num_cpus, num_cpus)
143+
+ if num_cpus != self.num_cpus:
144+
+ logger.warning(
145+
+ f"GraalPy: user requested {self.num_cpus} for number"
146+
+ "of CPUs, but GraalPy can only support up to {num_cpus} CPUs"
147+
+ )
148+
+ except:
149+
+ # We are not running on GraalPy
150+
+ num_cpus = self.num_cpus
151+
+ if num_cpus is None:
152+
+ num_cpus = ray._private.utils.get_num_cpus()
153+
154+
num_gpus = 0
155+
for (
126156
diff --git a/python/ray/_private/services.py b/python/ray/_private/services.py
127157
index 867b748..40b3fb2 100644
128158
--- a/python/ray/_private/services.py
@@ -272,10 +302,10 @@ index a34a39c..51a1a17 100644
272302
if BAZEL_ARGS:
273303
diff --git a/ray_build_backend.py b/ray_build_backend.py
274304
new file mode 100644
275-
index 0000000..9e7f3fd
305+
index 0000000000..10f532ae9e
276306
--- /dev/null
277307
+++ b/ray_build_backend.py
278-
@@ -0,0 +1,117 @@
308+
@@ -0,0 +1,125 @@
279309
+# We need a whole custom build backend just because the setup.py is in a subdir
280310
+import os
281311
+import re
@@ -385,6 +415,14 @@ index 0000000..9e7f3fd
385415
+ env = os.environ.copy()
386416
+ env['SKIP_THIRDPARTY_INSTALL'] = 'true'
387417
+ try:
418+
+ system_num_cpus = __graalpython__.get_max_process_count()
419+
+ env['BAZEL_LIMIT_CPUS'] = '%d' % system_num_cpus
420+
+ except:
421+
+ # do not limit bazel
422+
+ pass
423+
+ try:
424+
+ if os.path.isdir(Path('dist')):
425+
+ shutil.rmtree(Path('dist'))
388426
+ subprocess.run([sys.executable, 'setup.py', 'bdist_wheel'], env=env, check=True)
389427
+ wheels = list(Path('dist').glob('*.whl'))
390428
+ assert len(wheels) == 1, f"Expected 1 wheel, found {len(wheels)}"

mx.graalpython/mx_graalpython.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2128,7 +2128,7 @@ def _python_checkpatchfiles():
21282128
# Puts whole license into the field. It's BSD 3-Clause
21292129
'pythran-0.12.0.patch',
21302130
'pythran-0.13.patch',
2131-
# Puts whole license into the field. It's BSD 3-Clause
2131+
'pyzmq.patch',
21322132
'jupyter_server.patch',
21332133
# Empty license field. It's BSD-3-Clause
21342134
'prompt_toolkit.patch',

0 commit comments

Comments
 (0)