diff --git a/graalpython/com.oracle.graal.python.cext/src/memoryobject.c b/graalpython/com.oracle.graal.python.cext/src/memoryobject.c index a51e7346b7..605a3c8a36 100644 --- a/graalpython/com.oracle.graal.python.cext/src/memoryobject.c +++ b/graalpython/com.oracle.graal.python.cext/src/memoryobject.c @@ -186,9 +186,9 @@ PyTypeObject _PyManagedBuffer_Type = { /* In the process of breaking reference cycles mbuf_release() can be called before memory_release(). */ -// GraalPy change: don't use the manager +// GraalPy change: upcall #define BASE_INACCESSIBLE(mv) \ - (((PyMemoryViewObject *)mv)->flags&_Py_MEMORYVIEW_RELEASED) + GraalPyPrivate_MemoryViewIsReleased((PyObject *)(mv)) #define CHECK_RELEASED(mv) \ if (BASE_INACCESSIBLE(mv)) { \ @@ -1513,88 +1513,13 @@ memoryview_toreadonly_impl(PyMemoryViewObject *self) Py_LOCAL_SYMBOL int // GraalPy change: hidden for capi.c memory_getbuf(PyMemoryViewObject *self, Py_buffer *view, int flags) { - Py_buffer *base = &self->view; - int baseflags = self->flags; - - CHECK_RELEASED_INT(self); - CHECK_RESTRICTED_INT(self); - - /* start with complete information */ - *view = *base; - view->obj = NULL; - - if (REQ_WRITABLE(flags) && base->readonly) { - PyErr_SetString(PyExc_BufferError, - "memoryview: underlying buffer is not writable"); - return -1; - } - if (!REQ_FORMAT(flags)) { - /* NULL indicates that the buffer's data type has been cast to 'B'. - view->itemsize is the _previous_ itemsize. If shape is present, - the equality product(shape) * itemsize = len still holds at this - point. The equality calcsize(format) = itemsize does _not_ hold - from here on! */ - view->format = NULL; - } - - if (REQ_C_CONTIGUOUS(flags) && !MV_C_CONTIGUOUS(baseflags)) { - PyErr_SetString(PyExc_BufferError, - "memoryview: underlying buffer is not C-contiguous"); - return -1; - } - if (REQ_F_CONTIGUOUS(flags) && !MV_F_CONTIGUOUS(baseflags)) { - PyErr_SetString(PyExc_BufferError, - "memoryview: underlying buffer is not Fortran contiguous"); - return -1; - } - if (REQ_ANY_CONTIGUOUS(flags) && !MV_ANY_CONTIGUOUS(baseflags)) { - PyErr_SetString(PyExc_BufferError, - "memoryview: underlying buffer is not contiguous"); - return -1; - } - if (!REQ_INDIRECT(flags) && (baseflags & _Py_MEMORYVIEW_PIL)) { - PyErr_SetString(PyExc_BufferError, - "memoryview: underlying buffer requires suboffsets"); - return -1; - } - if (!REQ_STRIDES(flags)) { - if (!MV_C_CONTIGUOUS(baseflags)) { - PyErr_SetString(PyExc_BufferError, - "memoryview: underlying buffer is not C-contiguous"); - return -1; - } - view->strides = NULL; - } - if (!REQ_SHAPE(flags)) { - /* PyBUF_SIMPLE or PyBUF_WRITABLE: at this point buf is C-contiguous, - so base->buf = ndbuf->data. */ - if (view->format != NULL) { - /* PyBUF_SIMPLE|PyBUF_FORMAT and PyBUF_WRITABLE|PyBUF_FORMAT do - not make sense. */ - PyErr_Format(PyExc_BufferError, - "memoryview: cannot cast to unsigned bytes if the format flag " - "is present"); - return -1; - } - /* product(shape) * itemsize = len and calcsize(format) = itemsize - do _not_ hold from here on! */ - view->ndim = 1; - view->shape = NULL; - } - - - view->obj = Py_NewRef(self); - self->exports++; - - return 0; + return GraalPyPrivate_MemoryViewGetBuffer((PyObject *)self, view, flags); } Py_LOCAL_SYMBOL void // GraalPy change: hidden for capi.c memory_releasebuf(PyMemoryViewObject *self, Py_buffer *view) { - self->exports--; - return; - /* PyBuffer_Release() decrements view->obj after this function returns. */ + GraalPyPrivate_MemoryViewReleaseBuffer((PyObject *)self, view); } #if 0 // GraalPy change @@ -3525,11 +3450,11 @@ GraalPyPrivate_ReleaseBuffer(Py_buffer* buffer) Py_buffer * GraalPyMemoryView_GET_BUFFER(PyObject *op) { - return &_PyMemoryView_CAST(op)->view; + return GraalPyPrivate_MemoryViewBuffer(op); } PyObject * GraalPyMemoryView_GET_BASE(PyObject *op) { - return _PyMemoryView_CAST(op)->view.obj; + return GraalPyPrivate_MemoryViewBase(op); } diff --git a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/UnavailableFrameLocationTest.java b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/UnavailableFrameLocationTest.java index d3c8ce7bc9..cca52d37db 100644 --- a/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/UnavailableFrameLocationTest.java +++ b/graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/UnavailableFrameLocationTest.java @@ -108,14 +108,14 @@ assert list(proxy) == [] ReadFrameNode.AllPythonFramesSelector.INSTANCE, 0, CallerFlags.ALL_FRAME_FLAGS); assertNotNull(result); MaterializeFrameNode materialize = MaterializeFrameNode.getUncached(); - assertThrows(AssertionError.class, () -> materialize.execute(null, false, false, result.frame())); + assertThrows(AssertionError.class, () -> materialize.execute(null, false, 0, result.frame())); BytecodeFrame captured = null; if (captureFirst) { - captured = materialize.execute(result.callNode(), true, true, result.frame()).getBytecodeFrame(); + captured = materialize.execute(result.callNode(), true, CallerFlags.NEEDS_LOCALS, result.frame()).getBytecodeFrame(); assertNotNull(captured); } Node unavailable = PythonLanguage.get(null).unavailableSafepointLocation; - PFrame pyFrame = materialize.execute(unavailable, true, true, result.frame()); + PFrame pyFrame = materialize.execute(unavailable, true, CallerFlags.NEEDS_LOCALS, result.frame()); assertNull(pyFrame.getBytecodeNode()); assertEquals(-1, pyFrame.getBci()); assertEquals(-1, pyFrame.getLine()); @@ -126,7 +126,7 @@ assert list(proxy) == [] checkLocals.execute(context.asValue(pyFrame), captureFirst); // A later observation with a valid location must resume normal locals capture. - assertSame(pyFrame, materialize.execute(result.callNode(), true, true, result.frame())); + assertSame(pyFrame, materialize.execute(result.callNode(), true, CallerFlags.NEEDS_LOCALS, result.frame())); assertNotNull(pyFrame.getBytecodeNode()); assertNotNull(pyFrame.getBytecodeFrame()); assertTrue(pyFrame.hasMaterializedFrame() || pyFrame.syncsLocals()); diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_interop.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_interop.py new file mode 100644 index 0000000000..637e29e10f --- /dev/null +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_interop.py @@ -0,0 +1,82 @@ +# Copyright (c) 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. + +from unittest import TestCase, skipUnless + +from . import CPyExtType, GRAALPYTHON + +if GRAALPYTHON: + import java + from __graalpython__ import is_native +else: + is_native = False + + +@skipUnless(GRAALPYTHON and not is_native, "requires GraalPy Java interop") +class TestInteropBuffer(TestCase): + @classmethod + def setUpClass(cls): + cls.reader = CPyExtType( + "InteropBufferReader", + """ + static PyObject* read_buffer(PyObject* self, PyObject* obj) { + Py_buffer view; + if (PyObject_GetBuffer(obj, &view, PyBUF_SIMPLE) < 0) { + return NULL; + } + PyObject* result = PyBytes_FromStringAndSize((const char*)view.buf, view.len); + PyBuffer_Release(&view); + return result; + } + """, + tp_methods='{"read_buffer", read_buffer, METH_O, ""}', + )() + + def setUp(self): + self.buffer = java.type("java.nio.ByteBuffer").allocate(4) + for i, value in enumerate(b"test"): + self.buffer.put(i, value) + + def test_getbuffer(self): + self.assertRaises(TypeError, self.reader.read_buffer, self.buffer) + + def test_getbuffer_memoryview(self): + with memoryview(self.buffer) as view: + # memoryview supports interop objects, but sending them to native causes a copy with a warning + self.assertEqual(self.reader.read_buffer(view), b"test") diff --git a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_memoryview.py b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_memoryview.py index 89f59def6a..6eb0c52691 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_memoryview.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_memoryview.py @@ -466,6 +466,79 @@ class TestPyMemoryView(CPyExtTestCase): class TestObject(unittest.TestCase): + def test_memoryview_native_exports(self): + TestType = CPyExtType( + "TestMemoryViewNativeExports", + """ + static PyObject *acquire(PyObject *self, PyObject *args) { + PyObject *obj; + int flags; + if (!PyArg_ParseTuple(args, "Oi", &obj, &flags)) { + return NULL; + } + TestMemoryViewNativeExportsObject *holder = (TestMemoryViewNativeExportsObject *)self; + if (holder->view.obj != NULL) { + PyErr_SetString(PyExc_RuntimeError, "buffer already acquired"); + return NULL; + } + if (PyObject_GetBuffer(obj, &holder->view, flags) < 0) { + return NULL; + } + Py_buffer *v = &holder->view; + return Py_BuildValue("nniiii", v->len, v->itemsize, v->ndim, + v->format != NULL, v->shape != NULL, v->strides != NULL); + } + static PyObject *drop(PyObject *self, PyObject *unused) { + PyBuffer_Release(&((TestMemoryViewNativeExportsObject *)self)->view); + Py_RETURN_NONE; + } + static PyObject *read_buffer(PyObject *self, PyObject *unused) { + Py_buffer *v = &((TestMemoryViewNativeExportsObject *)self)->view; + PyObject *result = PyBytes_FromStringAndSize(NULL, v->len); + if (result != NULL && PyBuffer_ToContiguous(PyBytes_AS_STRING(result), v, v->len, 'C') < 0) { + Py_CLEAR(result); + } + return result; + } + """, + cmembers='Py_buffer view;', + tp_methods=''' + {"acquire", acquire, METH_VARARGS, ""}, + {"drop", drop, METH_NOARGS, ""}, + {"read", read_buffer, METH_NOARGS, ""} + ''', + ) + first, second = TestType(), TestType() + mv = memoryview(bytearray(b'abcdefgh'))[1:7].cast('H', shape=[1, 3]) + assert first.acquire(mv, 0x11c) == (6, 2, 2, 1, 1, 1) # PyBUF_FULL_RO + assert second.acquire(mv, 0) == (6, 2, 1, 0, 0, 0) # PyBUF_SIMPLE + assert first.read() == b'bcdefg' + with self.assertRaises(BufferError): + mv.release() + first.drop() + with self.assertRaises(BufferError): + mv.release() + second.drop() + mv.release() + with self.assertRaises(ValueError): + first.acquire(mv, 0x11c) + + for mv, flags in [(memoryview(b'abc'), 1), # PyBUF_WRITABLE + (memoryview(b'abcdef')[::2], 0), + (memoryview(b'abcdef')[::2], 0x38), # PyBUF_C_CONTIGUOUS + (memoryview(b'abcdef')[::2], 0x58), # PyBUF_F_CONTIGUOUS + (memoryview(b'abcdef')[::2], 0x98), # PyBUF_ANY_CONTIGUOUS + (memoryview(b'abc'), 4)]: # PyBUF_SIMPLE | PyBUF_FORMAT + with self.assertRaises(BufferError): + first.acquire(mv, flags) + mv.release() # Failed acquisition must not leave an export behind. + + mv = memoryview(bytearray(b'abcdef'))[::2] + assert first.acquire(mv, 0x11c) == (3, 1, 1, 1, 1, 1) + assert first.read() == b'ace' + first.drop() + mv.release() + def test_memoryview_fromobject_multidim(self): TestType = CPyExtType( "TestMemoryViewMultidim", diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java index 20854b20ef..2dc0e77be1 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/PythonLanguage.java @@ -101,6 +101,7 @@ import com.oracle.graal.python.pegparser.sst.StmtTy; import com.oracle.graal.python.pegparser.sst.TypeParamTy; import com.oracle.graal.python.pegparser.tokenizer.SourceRange; +import com.oracle.graal.python.runtime.CallerFlags; import com.oracle.graal.python.runtime.GilNode; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; import com.oracle.graal.python.runtime.IndirectCallData.InteropCallData; @@ -752,7 +753,7 @@ public ExecutableNode parse(InlineParsingRequest request) { @Override public Object execute(VirtualFrame frame) { Object[] arguments = PArguments.create(); - PFrame pFrame = materializeFrameNode.execute(this, false, true, frame); + PFrame pFrame = materializeFrameNode.execute(this, false, CallerFlags.NEEDS_LOCALS, frame); Object pLocals = getFrameLocalsNode.executeCached(frame, pFrame, true); PArguments.setSpecialArgument(arguments, pLocals); PArguments.setCodeObject(arguments, PFactory.createCode(getLanguage(PythonLanguage.class), callTarget)); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMemoryViewBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMemoryViewBuiltins.java index aa99f522b7..2f32e5d531 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMemoryViewBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/cext/PythonCextMemoryViewBuiltins.java @@ -42,27 +42,41 @@ import static com.oracle.graal.python.annotations.ArgDescriptor.CHAR; import static com.oracle.graal.python.annotations.ArgDescriptor.Int; +import static com.oracle.graal.python.annotations.ArgDescriptor.PY_BUFFER_PTR; import static com.oracle.graal.python.annotations.ArgDescriptor.PyObject; +import static com.oracle.graal.python.annotations.ArgDescriptor.Void; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.BufferError; import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Direct; +import static com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiCallPath.Ignored; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeIntField; +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writePtrField; import static com.oracle.graal.python.nodes.ErrorMessages.UNDERLYING_BUFFER_IS_NOT_WRITABLE; import static com.oracle.graal.python.nodes.ErrorMessages.WRITABLE_CONTIGUES_FOR_NON_CONTIGUOUS; +import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; import static com.oracle.graal.python.util.BufferFormat.T_UINT_8_TYPE_CODE; import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING; +import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBinaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiBuiltin; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiTernaryBuiltinNode; import com.oracle.graal.python.builtins.modules.cext.PythonCextBuiltins.CApiUnaryBuiltinNode; import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.buffer.BufferFlags; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.NativeToPythonNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNewRefNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeNode; +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.memoryview.MemoryViewBuiltins.CastNode; import com.oracle.graal.python.builtins.objects.memoryview.MemoryViewBuiltins.ContiguousNode; import com.oracle.graal.python.builtins.objects.memoryview.MemoryViewBuiltins.ToBytesNode; import com.oracle.graal.python.builtins.objects.memoryview.MemoryViewNodes.ReleaseNode; import com.oracle.graal.python.builtins.objects.memoryview.PMemoryView; import com.oracle.graal.python.lib.PyMemoryViewFromObject; +import com.oracle.graal.python.lib.PyObjectGetBuffer; import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; import com.oracle.truffle.api.dsl.Specialization; @@ -74,6 +88,71 @@ public final class PythonCextMemoryViewBuiltins { public static final int PY_BUF_READ = 0x100; public static final int PY_BUF_WRITE = 0x200; + @CApiBuiltin(ret = PY_BUFFER_PTR, args = {PyObject}, call = Ignored, retConversion = long.class, argConversions = {NativeToPythonNode.class}) + abstract static class GraalPyPrivate_MemoryViewBuffer extends CApiUnaryBuiltinNode { + @Specialization + static long get(PMemoryView self) { + return self.getNativeBuffer(); + } + } + + @CApiBuiltin(ret = PyObject, args = {PyObject}, call = Ignored, retConversion = PythonToNativeNode.class, argConversions = {NativeToPythonNode.class}) + abstract static class GraalPyPrivate_MemoryViewBase extends CApiUnaryBuiltinNode { + @Specialization + static Object get(PMemoryView self) { + return self.getOwner() == null ? PNone.NO_VALUE : self.getOwner(); + } + } + + @CApiBuiltin(ret = Int, args = {PyObject}, call = Ignored, retConversion = int.class, argConversions = {NativeToPythonNode.class}) + abstract static class GraalPyPrivate_MemoryViewIsReleased extends CApiUnaryBuiltinNode { + @Specialization + static int get(PMemoryView self) { + return self.isReleased() ? 1 : 0; + } + } + + @CApiBuiltin(ret = Int, args = {PyObject, PY_BUFFER_PTR, Int}, call = Ignored, retConversion = int.class, argConversions = {NativeToPythonNode.class, long.class, int.class}) + abstract static class GraalPyPrivate_MemoryViewGetBuffer extends CApiTernaryBuiltinNode { + @Specialization + static int get(PMemoryView self, long view, int flags, + @Cached PyObjectGetBuffer getBuffer) { + getBuffer.execute(null, self, flags); + boolean success = false; + try { + NativeMemory.memcpy(view, self.getNativeBuffer(), CStructs.Py_buffer.size()); + writePtrField(view, CFields.Py_buffer__obj, NULLPTR); + if (!BufferFlags.requestsFormat(flags)) { + writePtrField(view, CFields.Py_buffer__format, NULLPTR); + } + if (!BufferFlags.requestsStrides(flags)) { + writePtrField(view, CFields.Py_buffer__strides, NULLPTR); + } + if (!BufferFlags.requestsShape(flags)) { + writeIntField(view, CFields.Py_buffer__ndim, 1); + writePtrField(view, CFields.Py_buffer__shape, NULLPTR); + } + writePtrField(view, CFields.Py_buffer__obj, PythonToNativeInternalNode.executeNewRefUncached(self)); + success = true; + return 0; + } finally { + if (!success) { + self.getExports().decrementAndGet(); + } + } + } + } + + @CApiBuiltin(ret = Void, args = {PyObject, PY_BUFFER_PTR}, call = Ignored, retConversion = void.class, argConversions = {NativeToPythonNode.class, long.class}) + abstract static class GraalPyPrivate_MemoryViewReleaseBuffer extends CApiBinaryBuiltinNode { + @Specialization + static Object release(PMemoryView self, @SuppressWarnings("unused") long view) { + long exports = self.getExports().decrementAndGet(); + assert exports >= 0; + return PNone.NO_VALUE; + } + } + @CApiBuiltin(ret = PyObject, args = {PyObject, Int, CHAR}, call = Direct, retConversion = PythonToNativeNewRefNode.class, argConversions = {NativeToPythonNode.class, int.class, byte.class}) abstract static class PyMemoryView_GetContiguous extends CApiTernaryBuiltinNode { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMemoryViewWrapper.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMemoryViewWrapper.java deleted file mode 100644 index 6fcad6cfb3..0000000000 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyMemoryViewWrapper.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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 - * - * 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; - -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMemoryViewObject__exports; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyMemoryViewObject__flags; -import static com.oracle.graal.python.builtins.objects.cext.structs.CFields.PyObject__ob_refcnt; -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.getFieldPtr; -import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeIntField; -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 static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; -import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.calloc; -import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.mallocLongArray; -import static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.writeLongArrayElement; -import static com.oracle.truffle.api.CompilerDirectives.shouldNotReachHere; - -import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary; -import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; -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.ints.PInt; -import com.oracle.graal.python.builtins.objects.memoryview.PMemoryView; -import com.oracle.graal.python.builtins.objects.object.PythonObject; -import com.oracle.graal.python.builtins.objects.type.TypeFlags; -import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetTypeFlagsNode; -import com.oracle.graal.python.nodes.object.GetClassNode.GetPythonObjectClassNode; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; - -/** - * Wrapper object for {@code PMemoryView}. - */ -public abstract class PyMemoryViewWrapper { - - private PyMemoryViewWrapper() { - } - - private static long intArrayToNativePySSizeArray(int[] intArray) { - long mem = mallocLongArray(intArray.length); - for (int i = 0; i < intArray.length; i++) { - writeLongArrayElement(mem, i, intArray[i]); - } - return mem; - } - - @TruffleBoundary - public static long allocate(PMemoryView object) { - CExtNodes.AsCharPointerNode asCharPointerNode = CExtNodes.AsCharPointerNode.getUncached(); - - Object type = GetPythonObjectClassNode.executeUncached(object); - boolean gc = (GetTypeFlagsNode.executeUncached(type) & TypeFlags.HAVE_GC) != 0; - long presize = gc ? CStructs.PyGC_Head.size() : 0; - long memWithHead = calloc(CStructs.PyMemoryViewObject.size() + presize); - long mem = memWithHead + presize; - - writePtrField(mem, PyObject__ob_type, PythonToNativeInternalNode.executeNewRefUncached(type)); - writeLongField(mem, PyObject__ob_refcnt, PythonObject.IMMORTAL_REFCNT); - writeIntField(mem, PyMemoryViewObject__flags, object.getFlags()); - writeLongField(mem, PyMemoryViewObject__exports, object.getExports().get()); - // TODO: ignoring mbuf, hash and weakreflist for now - - long view = getFieldPtr(mem, CFields.PyMemoryViewObject__view); - - if (object.getBuffer() != null) { - long buf = object.getBufferPointer(); - if (buf == NULLPTR) { - buf = PythonBufferAccessLibrary.getUncached().getNativePointer(object.getBuffer()); - if (buf == NULLPTR) { - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw shouldNotReachHere("Cannot convert managed object to native storage: " + object.getBuffer().getClass().getSimpleName()); - } - } - if (object.getOffset() != 0) { - buf = buf + object.getOffset(); - } - writePtrField(view, CFields.Py_buffer__buf, buf); - } - - if (object.getOwner() != null) { - writePtrField(view, CFields.Py_buffer__obj, PythonToNativeInternalNode.executeNewRefUncached(object.getOwner())); - } - writeLongField(view, CFields.Py_buffer__len, object.getLength()); - writeLongField(view, CFields.Py_buffer__itemsize, object.getItemSize()); - writeIntField(view, CFields.Py_buffer__readonly, PInt.intValue(object.isReadOnly())); - writeIntField(view, CFields.Py_buffer__ndim, object.getDimensions()); - if (object.getFormatString() != null) { - writePtrField(view, CFields.Py_buffer__format, asCharPointerNode.execute(object.getFormatString())); - } - if (object.getBufferShape() != null) { - writePtrField(view, CFields.Py_buffer__shape, intArrayToNativePySSizeArray(object.getBufferShape())); - } - if (object.getBufferStrides() != null) { - writePtrField(view, CFields.Py_buffer__strides, intArrayToNativePySSizeArray(object.getBufferStrides())); - } - if (object.getBufferSuboffsets() != null) { - writePtrField(view, CFields.Py_buffer__suboffsets, intArrayToNativePySSizeArray(object.getBufferSuboffsets())); - } - return mem; - } -} 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 00c10dc088..f159d6111a 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 @@ -89,7 +89,6 @@ import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.FromCharPointerNode; 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.PyMemoryViewWrapper; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitionsFactory.AllocateNativeObjectStubNodeGen; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitionsFactory.FirstToNativeNodeGen; import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitionsFactory.NativeToPythonClassInternalNodeGen; @@ -111,7 +110,6 @@ import com.oracle.graal.python.builtins.objects.floats.PFloat; import com.oracle.graal.python.builtins.objects.getsetdescriptor.DescriptorDeleteMarker; import com.oracle.graal.python.builtins.objects.ints.PInt; -import com.oracle.graal.python.builtins.objects.memoryview.PMemoryView; import com.oracle.graal.python.builtins.objects.object.PythonObject; import com.oracle.graal.python.builtins.objects.str.PString; import com.oracle.graal.python.builtins.objects.str.StringNodes.StringMaterializeNode; @@ -1411,16 +1409,6 @@ static long doPythonManagedClass(@SuppressWarnings("unused") Node inliningTarget return ptr; } - @Specialization - @TruffleBoundary - static long doMemoryView(@SuppressWarnings("unused") Node inliningTarget, PMemoryView mv, long initialRefCount) { - assert !mv.isNative(); - assert initialRefCount == IMMORTAL_REFCNT; - long ptr = PyMemoryViewWrapper.allocate(mv); - CApiTransitions.createReference(PythonContext.get(null), mv, ptr); - return ptr; - } - @Specialization(guards = "isSpecialSingleton(singletonObject)") @TruffleBoundary static long doSpecialSingleton(@SuppressWarnings("unused") Node inliningTarget, PythonAbstractObject singletonObject, long initialRefCount) { @@ -1433,7 +1421,7 @@ static long doSpecialSingleton(@SuppressWarnings("unused") Node inliningTarget, return AllocateNativeObjectStubNodeGen.getUncached().execute(inliningTarget, singletonObject, type, CStructs.GraalPyObject, IMMORTAL_REFCNT, false, 0); } - @Specialization(guards = {"!isManagedClass(pythonObject)", "!isMemoryView(pythonObject)"}) + @Specialization(guards = "!isManagedClass(pythonObject)") static long doOther(Node inliningTarget, PythonObject pythonObject, long initialRefCount, @Exclusive @Cached InlinedConditionProfile isVarObjectProfile, @Exclusive @Cached InlinedConditionProfile isGcProfile, @@ -1446,8 +1434,6 @@ static long doOther(Node inliningTarget, PythonObject pythonObject, long initial // for types, we always need to allocate the full PyTypeObject assert !(pythonObject instanceof PythonManagedClass); - // for memoryview, we always need to allocate the full PyMemoryViewObject - assert !(pythonObject instanceof PMemoryView); assert !CApiContext.isSpecialSingleton(pythonObject); Object type = getClassNode.execute(inliningTarget, pythonObject); @@ -2063,12 +2049,12 @@ public static boolean isImmortal(PythonContext context, Object object) { } /** - * PMemoryView, static native classes, built-in classes, and singletons True/False are + * Static native classes, built-in classes, and singletons True/False are * immortal. */ private static boolean isImmortalPythonObject(PythonContext context, PythonObject pythonObject) { assert !CApiContext.isSpecialSingleton(pythonObject); - return pythonObject instanceof PythonBuiltinClass || context.getTrue() == pythonObject || context.getFalse() == pythonObject || pythonObject instanceof PMemoryView; + return pythonObject instanceof PythonBuiltinClass || context.getTrue() == pythonObject || context.getFalse() == pythonObject; } /** diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/NativeMemoryViewBuffer.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/NativeMemoryViewBuffer.java new file mode 100644 index 0000000000..721d9970c4 --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/NativeMemoryViewBuffer.java @@ -0,0 +1,152 @@ +/* + * 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 + * + * 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.memoryview; + +import static com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.writeIntField; +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 static com.oracle.graal.python.runtime.nativeaccess.NativeMemory.NULLPTR; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary; +import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.EnsurePythonObjectNode; +import com.oracle.graal.python.builtins.objects.cext.capi.transitions.CApiTransitions.PythonToNativeInternalNode; +import com.oracle.graal.python.builtins.objects.cext.structs.CFields; +import com.oracle.graal.python.builtins.objects.cext.structs.CStructAccess.WriteTruffleStringNode; +import com.oracle.graal.python.builtins.objects.cext.structs.CStructs; +import com.oracle.graal.python.runtime.AsyncHandler; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.nativeaccess.NativeMemory; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.api.strings.TruffleString; + +/** Native buffer metadata, owned by the memoryview, without a native replacement object. */ +final class NativeMemoryViewBuffer extends AsyncHandler.SharedFinalizer.FinalizableReference { + private static final TruffleLogger LOGGER = PythonLanguage.getLogger(NativeMemoryViewBuffer.class); + + // Keep the promoted owner alive while Py_buffer.obj holds a borrowed native reference. + @SuppressWarnings("unused") private final Object ownerWrapper; + + NativeMemoryViewBuffer(PMemoryView memoryView, long pointer, Object ownerWrapper) { + super(memoryView, pointer, PythonContext.get(null).getSharedFinalizer()); + this.ownerWrapper = ownerWrapper; + } + + long getPointer() { + return (long) getReference(); + } + + @Override + public AsyncHandler.AsyncAction release() { + NativeMemory.free(getPointer()); + return null; + } + + @TruffleBoundary + static NativeMemoryViewBuffer allocate(PMemoryView object) { + TruffleString format = object.getFormatString(); + TruffleString.Encoding encoding = TruffleString.Encoding.US_ASCII; + if (format != null) { + format = format.switchEncodingUncached(encoding); + } + long buf = object.getBufferPointer(); + byte[] bufferCopy = null; + if (object.getBuffer() != null) { + if (buf == NULLPTR) { + PythonBufferAccessLibrary bufferLib = PythonBufferAccessLibrary.getUncached(); + buf = bufferLib.getNativePointer(object.getBuffer()); + if (buf == NULLPTR) { + /* + * It would be more Pythonic to raise an exception rather than to copy, but because this is invoked from macros, there's no practical way of doing it + */ + LOGGER.warning(() -> "Cannot obtain a native buffer pointer for interop buffer; allocating a copy. Writes to the copy will not update the original buffer."); + bufferCopy = bufferLib.getCopiedByteArray(object.getBuffer()); + } + } + } + Object ownerWrapper = object.getOwner() == null ? null : EnsurePythonObjectNode.executeUncached(PythonContext.get(null), object.getOwner(), false); + long owner = ownerWrapper == null ? NULLPTR : PythonToNativeInternalNode.executeUncached(ownerWrapper, false); + int ndim = object.getDimensions(); + long bufferSize = CStructs.Py_buffer.size(); + long arraySize = (long) ndim * Long.BYTES; + long formatSize = format == null ? 0 : format.byteLength(encoding) + 1L; + // Space for the Py_buffer, shape, strides, suboffsets, null-terminated format, and any copy. + long metadataSize = bufferSize + 3 * arraySize + formatSize; + long view = NativeMemory.calloc(metadataSize + (bufferCopy == null ? 0 : bufferCopy.length)); + if (bufferCopy != null) { + buf = view + metadataSize; + NativeMemory.writeByteArrayElements(buf, 0, bufferCopy, 0, bufferCopy.length); + } + if (object.getBuffer() != null) { + buf += object.getOffset(); + } + long array = view + CStructs.Py_buffer.size(); + writePtrField(view, CFields.Py_buffer__buf, buf); + // Borrowed, like CPython's embedded view.obj. This allocation retains the promoted owner. + writePtrField(view, CFields.Py_buffer__obj, owner); + writeLongField(view, CFields.Py_buffer__len, object.getLength()); + writeLongField(view, CFields.Py_buffer__itemsize, object.getItemSize()); + writeIntField(view, CFields.Py_buffer__readonly, object.isReadOnly() ? 1 : 0); + writeIntField(view, CFields.Py_buffer__ndim, ndim); + writePtrField(view, CFields.Py_buffer__shape, copyArray(array, object.getBufferShape())); + array += (long) ndim * Long.BYTES; + writePtrField(view, CFields.Py_buffer__strides, copyArray(array, object.getBufferStrides())); + array += (long) ndim * Long.BYTES; + writePtrField(view, CFields.Py_buffer__suboffsets, copyArray(array, object.getBufferSuboffsets())); + array += (long) ndim * Long.BYTES; + if (format != null) { + WriteTruffleStringNode.writeUncached(array, format, encoding); + writePtrField(view, CFields.Py_buffer__format, array); + } + return new NativeMemoryViewBuffer(object, view, ownerWrapper); + } + + private static long copyArray(long pointer, int[] array) { + if (array == null) { + return NULLPTR; + } + for (int i = 0; i < array.length; i++) { + NativeMemory.writeLongArrayElement(pointer, i, array[i]); + } + return pointer; + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/PMemoryView.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/PMemoryView.java index 1d285c8fee..d4a300b1a0 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/PMemoryView.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/memoryview/PMemoryView.java @@ -48,6 +48,8 @@ import java.util.concurrent.atomic.AtomicLong; import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary; +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.object.PythonBuiltinObject; import com.oracle.graal.python.nodes.ErrorMessages; import com.oracle.graal.python.nodes.PRaiseNode; @@ -96,6 +98,7 @@ public final class PMemoryView extends PythonBuiltinObject { // Phantom ref to this object that will decref/release the managed buffer if any private BufferReference reference; private int flags; + private NativeMemoryViewBuffer nativeBuffer; // Cached hash value, required to comply with CPython's semantics private int cachedHash = -1; @@ -217,6 +220,13 @@ public AtomicLong getExports() { return exports; } + public long getNativeBuffer() { + if (nativeBuffer == null) { + nativeBuffer = NativeMemoryViewBuffer.allocate(this); + } + return nativeBuffer.getPointer(); + } + public BufferReference getReference() { return reference; } @@ -238,6 +248,9 @@ public void setReleased() { } buffer = null; owner = null; + if (nativeBuffer != null) { + CStructAccess.writePtrField(nativeBuffer.getPointer(), CFields.Py_buffer__obj, NULLPTR); + } } public void setShouldReleaseImmediately(boolean shouldReleaseImmediately) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java index 4780af4030..8254a8e711 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/traceback/TracebackBuiltins.java @@ -62,6 +62,7 @@ import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; +import com.oracle.graal.python.runtime.CallerFlags; import com.oracle.graal.python.runtime.exception.ExceptionUtils; import com.oracle.graal.python.runtime.exception.PException; import com.oracle.graal.python.runtime.exception.PythonErrorType; @@ -223,7 +224,7 @@ private static PFrame materializeFrame(TruffleStackTraceElement element, Materia rootNode, pException.getTracebackFrameCount(), pException.getTracebackStartIndex(), Arrays.toString(stackTrace.toArray())); // create the PFrame and refresh frame values Frame frame = PGenerator.unwrapDSLGeneratorFrame(element); - return materializeFrameNode.execute(location, true, true, frame); + return materializeFrameNode.execute(location, true, CallerFlags.NEEDS_MATERIALIZED_LOCALS, frame); } } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java index d6a078447e..9bf37a72dd 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java @@ -833,7 +833,7 @@ private TracingNodes getTracingNodes(BytecodeNode location) { } private PFrame ensurePyFrame(VirtualFrame frame, BytecodeNode location) { - return getTracingNodes(location).materializeFrameNode.executeOnStack(frame, location, true, false); + return getTracingNodes(location).materializeFrameNode.executeOnStack(frame, location, true, 0); } @InliningCutoff diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/MaterializeFrameNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/MaterializeFrameNode.java index baac401f35..49bdf6a162 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/MaterializeFrameNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/MaterializeFrameNode.java @@ -95,11 +95,11 @@ public static MaterializeFrameNode getUncached() { } /** - * Like {@link #executeOnStack(boolean, boolean, Frame)}, but the current {@link BytecodeNode} + * Like {@link #executeOnStack(boolean, int, Frame)}, but the current {@link BytecodeNode} * is passed as argument to avoid its lookup. Can be used if this node is uncached. */ - public final PFrame executeOnStack(Frame frameToMaterialize, BytecodeNode bytecodeNode, boolean markAsEscaped, boolean forceSync) { - return execute(bytecodeNode, markAsEscaped, forceSync, frameToMaterialize); + public final PFrame executeOnStack(Frame frameToMaterialize, BytecodeNode bytecodeNode, boolean markAsEscaped, int callerFlags) { + return execute(bytecodeNode, markAsEscaped, callerFlags, frameToMaterialize); } /** @@ -108,14 +108,14 @@ public final PFrame executeOnStack(Frame frameToMaterialize, BytecodeNode byteco * {@link BytecodeNode} by traversing the AST. For the manual interpreter or builtin and other * root nodes, we fetch the root node from the frame descriptor. */ - public final PFrame executeOnStack(boolean markAsEscaped, boolean forceSync, Frame frameToMaterialize) { + public final PFrame executeOnStack(boolean markAsEscaped, int callerFlags, Frame frameToMaterialize) { BytecodeNode bytecodeNode = BytecodeNode.get(this); if (!this.isAdoptable()) { // This can happen in the uncached interpreter, but there the UncachedBytecodeNode // should set itself as encapsulating node before it starts executing its bytecode bytecodeNode = BytecodeNode.get(EncapsulatingNodeReference.getCurrent().get()); } - return execute(bytecodeNode, markAsEscaped, forceSync, frameToMaterialize); + return execute(bytecodeNode, markAsEscaped, callerFlags, frameToMaterialize); } /** @@ -131,7 +131,7 @@ public final PFrame executeOnStack(boolean markAsEscaped, boolean forceSync, Fra * materialization without bytecode information when an async action has no usable * location. */ - public final PFrame execute(Node location, boolean markAsEscaped, boolean forceSync, Frame frameToMaterialize) { + public final PFrame execute(Node location, boolean markAsEscaped, int callerFlags, Frame frameToMaterialize) { assert frameToMaterialize.getArguments().length != 2 : "caller forgot to unwrap continuation frame"; assert !(location instanceof PBytecodeDSLRootNode) : String.format("Materialized frame: location must not be PBytecodeDSLRootNode, was: %s", location); @@ -141,30 +141,30 @@ public final PFrame execute(Node location, boolean markAsEscaped, boolean forceS // from other locations as well when assertions are disabled. assert bytecodeNode != null || !(PArguments.getCurrentFrameInfo(frameToMaterialize).getRootNode() instanceof PBytecodeDSLRootNode) || location == PythonLanguage.get(null).unavailableSafepointLocation : "Missing BytecodeNode when materializing a Python frame"; - return executeImpl(bytecodeNode, markAsEscaped, forceSync, frameToMaterialize); + return executeImpl(bytecodeNode, markAsEscaped, callerFlags, frameToMaterialize); } - abstract PFrame executeImpl(BytecodeNode bytecodeNode, boolean markAsEscaped, boolean forceSync, Frame frameToMaterialize); + abstract PFrame executeImpl(BytecodeNode bytecodeNode, boolean markAsEscaped, int callerFlags, Frame frameToMaterialize); @Specialization(guards = {"getPFrame(frameToMaterialize) == null", "!isGeneratorFrame(frameToMaterialize)", "!hasCustomLocals(frameToMaterialize)"}) - static PFrame freshPFrameCachedFD(BytecodeNode bytecodeNode, boolean markAsEscaped, boolean forceSync, Frame frameToMaterialize, + static PFrame freshPFrameCachedFD(BytecodeNode bytecodeNode, boolean markAsEscaped, int callerFlags, Frame frameToMaterialize, @Bind PythonLanguage language, @Shared("syncValuesNode") @Cached SyncFrameValuesNode syncValuesNode) { PFrame escapedFrame = PFactory.createPFrame(language, PArguments.getCurrentFrameInfo(frameToMaterialize), bytecodeNode, PArguments.getFunctionOrCodeObject(frameToMaterialize), null); - return doEscapeFrame(frameToMaterialize, escapedFrame, markAsEscaped, forceSync, bytecodeNode, syncValuesNode); + return doEscapeFrame(frameToMaterialize, escapedFrame, markAsEscaped, callerFlags, bytecodeNode, syncValuesNode); } @Specialization(guards = {"getPFrame(frameToMaterialize) == null", "!isGeneratorFrame(frameToMaterialize)", "hasCustomLocals(frameToMaterialize)"}) - static PFrame freshPFrameCustomLocals(BytecodeNode bytecodeNode, boolean markAsEscaped, @SuppressWarnings("unused") boolean forceSync, + static PFrame freshPFrameCustomLocals(BytecodeNode bytecodeNode, boolean markAsEscaped, int callerFlags, Frame frameToMaterialize, @Bind PythonLanguage language) { Object customLocals = PArguments.getSpecialArgument(frameToMaterialize); PFrame escapedFrame = PFactory.createPFrame(language, PArguments.getCurrentFrameInfo(frameToMaterialize), bytecodeNode, PArguments.getFunctionOrCodeObject(frameToMaterialize), customLocals); - return doEscapeFrame(frameToMaterialize, escapedFrame, markAsEscaped, false, bytecodeNode, null); + return doEscapeFrame(frameToMaterialize, escapedFrame, markAsEscaped, callerFlags, bytecodeNode, null); } @Specialization(guards = {"getPFrame(frameToMaterialize) == null", "isGeneratorFrame(frameToMaterialize)"}) - static PFrame freshPFrameForGenerator(BytecodeNode bytecodeNode, boolean markAsEscaped, @SuppressWarnings("unused") boolean forceSync, Frame frameToMaterialize, + static PFrame freshPFrameForGenerator(BytecodeNode bytecodeNode, boolean markAsEscaped, int callerFlags, Frame frameToMaterialize, @Bind PythonLanguage language) { MaterializedFrame generatorFrame = PGenerator.getGeneratorFrame(frameToMaterialize); PFrame.Reference frameRef = PArguments.getCurrentFrameInfo(frameToMaterialize); @@ -174,24 +174,16 @@ static PFrame freshPFrameForGenerator(BytecodeNode bytecodeNode, boolean markAsE } else { escapedFrame = PFactory.createPFrame(language, frameRef, null, PArguments.getFunctionObject(frameToMaterialize), null); } - return doEscapeFrame(frameToMaterialize, escapedFrame, markAsEscaped, false, bytecodeNode, null); + return doEscapeFrame(frameToMaterialize, escapedFrame, markAsEscaped, callerFlags, bytecodeNode, null); } @Specialization(guards = "getPFrame(frameToMaterialize) != null") - static PFrame alreadyEscapedFrame(BytecodeNode bytecodeNode, boolean markAsEscaped, boolean forceSync, Frame frameToMaterialize, + static PFrame alreadyEscapedFrame(BytecodeNode bytecodeNode, boolean markAsEscaped, int callerFlags, Frame frameToMaterialize, @Shared("syncValuesNode") @Cached SyncFrameValuesNode syncValuesNode) { PFrame pyFrame = getPFrame(frameToMaterialize); // Restore the node before syncsLocals() is checked if an earlier safepoint had no location. pyFrame.setBytecodeNode(bytecodeNode); - pyFrame.setLastCallerFlags(getCallerFlags(forceSync)); - if (forceSync) { - syncValuesNode.execute(pyFrame, frameToMaterialize, bytecodeNode); - } - if (markAsEscaped) { - pyFrame.getRef().markAsEscaped(); - } - processBytecodeFrame(frameToMaterialize, pyFrame, bytecodeNode); - return pyFrame; + return processFrame(frameToMaterialize, pyFrame, markAsEscaped, callerFlags, bytecodeNode, syncValuesNode, pyFrame.getRef()); } public static PFrame materializeGeneratorFrame(BytecodeNode bytecodeNode, MaterializedFrame generatorFrame, PFunction generatorFunction, PythonObject globals, PFrame.Reference frameRef) { @@ -207,42 +199,40 @@ public static PFrame materializeGeneratorFrame(PythonLanguage language, Bytecode return escapedFrame; } - private static void processBytecodeFrame(Frame frameToMaterialize, PFrame pyFrame, BytecodeNode bytecodeNode) { - Object info = frameToMaterialize.getFrameDescriptor().getInfo(); - pyFrame.setBytecodeNode(bytecodeNode); - if (info == null) { - return; - } - CompilerAsserts.partialEvaluationConstant(bytecodeNode); - if (bytecodeNode != null) { - pyFrame.setBci(bytecodeNode.getBytecodeIndex(frameToMaterialize)); - pyFrame.resetLine(); - } else { - pyFrame.setBci(-1); - pyFrame.resetLine(); - } - } - - private static PFrame doEscapeFrame(Frame frameToMaterialize, PFrame escapedFrame, boolean markAsEscaped, boolean forceSync, + private static PFrame doEscapeFrame(Frame frameToMaterialize, PFrame escapedFrame, boolean markAsEscaped, int callerFlags, BytecodeNode bytecodeNode, SyncFrameValuesNode syncValuesNode) { PFrame.Reference topFrameRef = PArguments.getCurrentFrameInfo(frameToMaterialize); topFrameRef.setPyFrame(escapedFrame); - - // on a freshly created PFrame, we do always sync the arguments escapedFrame.setGlobals(PArguments.getGlobals(frameToMaterialize)); - escapedFrame.setLastCallerFlags(getCallerFlags(forceSync || syncValuesNode == null)); - if (forceSync) { - syncValuesNode.execute(escapedFrame, frameToMaterialize, bytecodeNode); + return processFrame(frameToMaterialize, escapedFrame, markAsEscaped, callerFlags, bytecodeNode, syncValuesNode, topFrameRef); + } + + private static PFrame processFrame(Frame frameToMaterialize, PFrame pFrame, boolean markAsEscaped, int callerFlags, BytecodeNode bytecodeNode, SyncFrameValuesNode syncValuesNode, + PFrame.Reference ref) { + if (CallerFlags.needsMaterializedLocals(callerFlags)) { + if (!pFrame.hasMaterializedFrame() && bytecodeNode != null) { + pFrame.setBytecodeFrame(bytecodeNode.createMaterializedFrame(0, frameToMaterialize.materialize()), true); + } + } else if (syncValuesNode != null && CallerFlags.needsLocals(callerFlags)) { + syncValuesNode.execute(pFrame, frameToMaterialize, bytecodeNode); } if (markAsEscaped) { - topFrameRef.markAsEscaped(); + ref.markAsEscaped(); } - processBytecodeFrame(frameToMaterialize, escapedFrame, bytecodeNode); - return escapedFrame; - } - - private static int getCallerFlags(boolean forceSync) { - return CallerFlags.NEEDS_FRAME_REFERENCE | CallerFlags.NEEDS_PFRAME | CallerFlags.NEEDS_LASTI | (forceSync ? CallerFlags.NEEDS_LOCALS : 0); + Object info = frameToMaterialize.getFrameDescriptor().getInfo(); + pFrame.setBytecodeNode(bytecodeNode); + if (info != null) { + CompilerAsserts.partialEvaluationConstant(bytecodeNode); + if (bytecodeNode != null) { + pFrame.setBci(bytecodeNode.getBytecodeIndex(frameToMaterialize)); + pFrame.resetLine(); + } else { + pFrame.setBci(-1); + pFrame.resetLine(); + } + } + pFrame.setLastCallerFlags(CallerFlags.NEEDS_FRAME_REFERENCE | CallerFlags.NEEDS_PFRAME | CallerFlags.NEEDS_LASTI | callerFlags); + return pFrame; } protected static boolean hasCustomLocals(Frame frame) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java index 492844c98f..1b5f016299 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java @@ -71,7 +71,6 @@ import com.oracle.truffle.api.ThreadLocalAction.Access; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleSafepoint; -import com.oracle.truffle.api.bytecode.BytecodeNode; import com.oracle.truffle.api.bytecode.ContinuationRootNode; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; @@ -83,7 +82,6 @@ import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.FrameInstance.FrameAccess; import com.oracle.truffle.api.frame.FrameInstanceVisitor; -import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.RootNode; @@ -212,12 +210,7 @@ PFrame read(VirtualFrame frame, PFrame.Reference startFrameInfo, FrameSelector s // We found the right reference // Maybe it's for the frame we're in? if (curFrameInfo == executingFrameInfo && frame != null) { - PFrame pFrame = materializeFrameNode.execute(this, false, CallerFlags.needsLocals(callerFlags) && !CallerFlags.needsMaterializedLocals(callerFlags), frame); - if (CallerFlags.needsMaterializedLocals(callerFlags) && !pFrame.hasMaterializedFrame()) { - BytecodeNode bytecodeNode = pFrame.getBytecodeNode(); - pFrame.setBytecodeFrame(bytecodeNode.createMaterializedFrame(0, frame.materialize()), true); - } - return pFrame; + return materializeFrameNode.execute(this, false, callerFlags, frame); } if (curFrameInfo.getPyFrame() != null && !curFrameInfo.getPyFrame().needsRefresh(null, callerFlags)) { return curFrameInfo.getPyFrame(); @@ -339,12 +332,7 @@ private static Thread getFrameThread(PFrame.Reference startFrameInfo, Thread fra private static PFrame processStackWalkResult(MaterializeFrameNode materializeFrameNode, int callerFlags, StackWalkResult callerFrameResult) { if (callerFrameResult != null) { Node location = getMaterializationLocation(callerFrameResult); - PFrame pFrame = materializeFrameNode.execute(location, false, CallerFlags.needsLocals(callerFlags) && !CallerFlags.needsMaterializedLocals(callerFlags), callerFrameResult.frame); - BytecodeNode bytecodeNode = pFrame.getBytecodeNode(); - if (CallerFlags.needsMaterializedLocals(callerFlags) && bytecodeNode != null) { - pFrame.setBytecodeFrame(bytecodeNode.createMaterializedFrame(0, (MaterializedFrame) callerFrameResult.frame), true); - } - return pFrame; + return materializeFrameNode.execute(location, false, callerFlags, callerFrameResult.frame); } return null; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java index 6fc9dab045..c59ef2c86a 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java @@ -216,7 +216,7 @@ protected static void passCallerFrame(VirtualFrame frame, Object[] callArguments if (thisInfo.getPyFrame() != null && !CallerFlags.needsLocals(callerFlags) && !CallerFlags.needsLasti(callerFlags)) { thisInfo.getPyFrame().setLastCallerFlags(callerFlags); } else { - PFrame pyFrame = materialize.executeOnStack(false, CallerFlags.needsLocals(callerFlags), frame); + PFrame pyFrame = materialize.executeOnStack(false, callerFlags, frame); assert thisInfo.getPyFrame() == pyFrame; assert pyFrame.getRef() == thisInfo; } @@ -429,6 +429,7 @@ private void exitEscaped(VirtualFrame frame, Node location, Reference info) { CompilerDirectives.transferToInterpreterAndInvalidate(); everEscaped = true; } + ensureMaterializeNode().execute(location, false, CallerFlags.NEEDS_MATERIALIZED_LOCALS, frame); Reference callerInfo = info.getCallerInfo(); if (callerInfo == null) { // we didn't request the caller frame reference. now we need it. @@ -451,12 +452,6 @@ private void exitEscaped(VirtualFrame frame, Node location, Reference info) { // go to the other branch and setNeedsCallerFrame. This helps to prevent one-off // initializations (importing a module) from invalidating the assumption - PFrame pFrame = ensureMaterializeNode().execute(location, false, false, frame); - BytecodeNode bytecodeNode = pFrame.getBytecodeNode(); - if (bytecodeNode != null) { - pFrame.setBytecodeFrame(bytecodeNode.createMaterializedFrame(0, frame.materialize()), true); - assert !pFrame.outdatedCallerFlags(CallerFlags.ALL_FRAME_FLAGS); - } // if this frame escaped we must ensure that also f_back does callerInfo.markAsEscaped(); info.setCallerInfo(callerInfo); @@ -701,7 +696,7 @@ private static Object enterSlowPath(VirtualFrame frame, IndirectCallDataBase cal info = PArguments.getCurrentFrameInfo(frame); pythonThreadState.setTopFrameInfo(info); if (CallerFlags.needsPFrame(callerFlags)) { - callData.getMaterializeFrameNode().executeOnStack(false, CallerFlags.needsLocals(callerFlags), frame); + callData.getMaterializeFrameNode().executeOnStack(false, callerFlags, frame); } else if (info.getPyFrame() != null) { info.getPyFrame().setLastCallerFlags(callerFlags); }