Skip to content

Commit b5226e5

Browse files
committed
[GR-57192] More fixes for nanobind
PullRequest: graalpython/3444
2 parents 67000c6 + 5d80d95 commit b5226e5

File tree

9 files changed

+166
-43
lines changed

9 files changed

+166
-43
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ GraalPy is ready for production running pure Python code and has experimental su
1919
**Compatible with the Python ecosystem**
2020

2121
* Use almost any standard Python feature, the CPython tests run on every commit and pass ~85%
22+
* See if the packages you need work according to our [Python Compatibility Checker](https://www.graalvm.org/python/compatibility/)
23+
* Support for native extension modules is considered experimental, but you can already install [packages](docs/user/Python-Runtime.md#installing-packages) like *NumPy*, *PyTorch*, or *Tensorflow*; run [Hugging Face](https://huggingface.co/) models like *Stable Diffusion* or *GPT*
2224
![](docs/user/assets/mcd.svg#gh-light-mode-only)![](docs/user/assets/mcd-dark.svg#gh-dark-mode-only)<sup>
2325
We run the tests of the [most depended on PyPI packages](https://libraries.io/pypi) every day.
2426
For 96% of those packages a recent version can be installed on GraalPy and GraalPy passes about 50% of all tests of all packages combined.
2527
We assume that CPython not passing 100% of all tests is due to problems in our infrastructure that may also affect GraalPy.
2628
Packages where CPython fails all tests are marked as "not tested" for both CPython and GraalPy.
2729
</sup>
28-
* See if the packages you need work according to our [Python Compatibility Checker](https://www.graalvm.org/python/compatibility/)
29-
* Support for native extension modules is considered experimental, but you can already install [packages](docs/user/Python-Runtime.md#installing-packages) like *NumPy*, *PyTorch*, or *Tensorflow*; run [Hugging Face](https://huggingface.co/) models like *Stable Diffusion* or *GPT*
3030

3131
**Runs Python code faster**
3232

graalpython/com.oracle.graal.python.cext/include/cpython/unicodeobject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ PyAPI_FUNC(int) _PyTruffleUnicode_KIND(PyObject*);
325325
#define PyUnicode_KIND(op) ((enum PyUnicode_Kind)_PyTruffleUnicode_KIND(_PyObject_CAST(op)))
326326

327327
/* Return a void pointer to the raw unicode buffer. */
328-
static inline void* _PyUnicode_COMPACT_DATA(PyObject *op) {
328+
static inline void* _PyUnicode_COMPACT_DATA(PyObject *Py_UNUSED(op)) {
329329
// strings are never compact in GraalPy
330330
return NULL;
331331
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
import sys
41+
import tempfile
42+
import textwrap
43+
44+
if sys.implementation.name == 'graalpy':
45+
import autopatch_capi
46+
47+
48+
def check_autopatched(source, expected):
49+
source = textwrap.dedent(source).rstrip()
50+
expected = textwrap.dedent(expected).rstrip()
51+
with tempfile.NamedTemporaryFile('r+') as f:
52+
f.write(source)
53+
f.flush()
54+
autopatch_capi.auto_patch(f.name, False)
55+
f.seek(0)
56+
actual = f.read()
57+
assert actual == expected, f"Autopatch didn't make expected changes. Expected:\n{expected}\nActual:{actual}"
58+
59+
60+
def test_replace_field_access():
61+
check_autopatched(
62+
"""
63+
PyObject* type = obj->ob_type;
64+
PyObject* type = ((PyObject*)obj)->ob_type;
65+
const char* name = obj->ob_type->tp_name;
66+
foo = objs[0]->ob_type;
67+
return obj->ob_type;
68+
return (PyObject*)obj->ob_type;
69+
return ((PyObject*)obj)->ob_type;
70+
(PyObject*)function_call(a, b, c(0))->ob_type->ob_base;
71+
""",
72+
"""
73+
PyObject* type = Py_TYPE(obj);
74+
PyObject* type = Py_TYPE(((PyObject*)obj));
75+
const char* name = Py_TYPE(obj)->tp_name;
76+
foo = Py_TYPE(objs[0]);
77+
return Py_TYPE(obj);
78+
return (PyObject*)Py_TYPE(obj);
79+
return Py_TYPE(((PyObject*)obj));
80+
(PyObject*)Py_TYPE(function_call(a, b, c(0)))->ob_base;
81+
"""
82+
)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PythonClassNativeWrapper.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
*/
4141
package com.oracle.graal.python.builtins.objects.cext.capi;
4242

43+
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
4344
import com.oracle.graal.python.builtins.objects.cext.capi.PythonNativeWrapper.PythonAbstractObjectNativeWrapper;
4445
import com.oracle.graal.python.builtins.objects.cext.common.CArrayWrappers.CStringWrapper;
4546
import com.oracle.graal.python.builtins.objects.cext.structs.CFields;
@@ -49,12 +50,14 @@
4950
import com.oracle.graal.python.builtins.objects.type.PythonClass;
5051
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
5152
import com.oracle.graal.python.builtins.objects.type.TypeFlags;
53+
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
5254
import com.oracle.graal.python.builtins.objects.type.TypeNodes.GetTypeFlagsNode;
5355
import com.oracle.graal.python.builtins.objects.type.TypeNodes.SetTypeFlagsNode;
5456
import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.SetBasicSizeNodeGen;
5557
import com.oracle.graal.python.builtins.objects.type.TypeNodesFactory.SetItemSizeNodeGen;
5658
import com.oracle.graal.python.nodes.HiddenAttr;
5759
import com.oracle.graal.python.nodes.PGuards;
60+
import com.oracle.graal.python.nodes.object.GetClassNode;
5861
import com.oracle.graal.python.util.PythonUtils;
5962
import com.oracle.truffle.api.CompilerAsserts;
6063
import com.oracle.truffle.api.CompilerDirectives;
@@ -188,7 +191,15 @@ public Object getReplacement(InteropLibrary lib) {
188191
*/
189192
PythonManagedClass clazz = (PythonManagedClass) getDelegate();
190193
boolean heaptype = (GetTypeFlagsNode.executeUncached(clazz) & TypeFlags.HEAPTYPE) != 0;
191-
Object pointerObject = AllocateNode.allocUncached(heaptype ? CStructs.PyHeapTypeObject : CStructs.PyTypeObject);
194+
long size = CStructs.PyTypeObject.size();
195+
if (heaptype) {
196+
size = CStructs.PyHeapTypeObject.size();
197+
if (GetClassNode.executeUncached(clazz) instanceof PythonAbstractNativeObject nativeMetatype) {
198+
// TODO should call the metatype's tp_alloc
199+
size = TypeNodes.GetBasicSizeNode.executeUncached(nativeMetatype);
200+
}
201+
}
202+
Object pointerObject = AllocateNode.allocUncached(size);
192203
replacement = registerReplacement(pointerObject, true, lib);
193204

194205
ToNativeTypeNode.initializeType(this, pointerObject, heaptype);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/AbstractFunctionBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ static TruffleString getBuiltin(PBuiltinFunction self, @SuppressWarnings("unused
308308

309309
@TruffleBoundary
310310
public static TruffleString signatureToText(Signature signature, boolean skipSelf) {
311-
TruffleString[] keywordNames = signature.getKeywordNames();
311+
TruffleString[] keywordNames = signature.getVisibleKeywordNames();
312312
boolean takesVarArgs = signature.takesVarArgs();
313313
boolean takesVarKeywordArgs = signature.takesVarKeywordArgs();
314314

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/BuiltinFunctionBuiltins.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ public static Object createInspectSignagure(Signature signature, boolean skipSel
172172
Object inspectParameter = PyObjectGetAttr.executeUncached(inspect, tsLiteral("Parameter"));
173173
Object[] parameterKinds = new Object[ParameterKinds.VALUES.length];
174174

175-
TruffleString[] keywordNames = signature.getKeywordNames();
175+
TruffleString[] keywordNames = signature.getVisibleKeywordNames();
176176
boolean takesVarArgs = signature.takesVarArgs();
177177
boolean takesVarKeywordArgs = signature.takesVarKeywordArgs();
178178
TruffleString[] parameterNames = signature.getParameterIds();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/function/Signature.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@
2727

2828
import static com.oracle.graal.python.nodes.BuiltinNames.T_SELF;
2929
import static com.oracle.graal.python.nodes.StringLiterals.T_EMPTY_STRING;
30+
import static com.oracle.graal.python.util.PythonUtils.TS_ENCODING;
31+
32+
import java.util.ArrayList;
33+
import java.util.List;
3034

3135
import com.oracle.graal.python.util.PythonUtils;
3236
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
37+
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
3338
import com.oracle.truffle.api.strings.TruffleString;
3439

3540
public final class Signature {
@@ -127,6 +132,21 @@ public final TruffleString[] getKeywordNames() {
127132
return keywordOnlyNames;
128133
}
129134

135+
@TruffleBoundary
136+
public TruffleString[] getVisibleKeywordNames() {
137+
/*
138+
* C slot wrappers (ab)use keyword defaults for storing the function pointers, we need to
139+
* filter them out. Their names start with a dollar sign.
140+
*/
141+
List<TruffleString> visibleKeywordNames = new ArrayList<>(keywordOnlyNames.length);
142+
for (TruffleString k : keywordOnlyNames) {
143+
if (k.byteLength(TS_ENCODING) > 0 && k.codePointAtByteIndexUncached(0, TS_ENCODING) != '$') {
144+
visibleKeywordNames.add(k);
145+
}
146+
}
147+
return visibleKeywordNames.toArray(TruffleString[]::new);
148+
}
149+
130150
public final boolean takesKeywordArgs() {
131151
return keywordOnlyNames.length > 0 || takesVarKeywordArgs;
132152
}

graalpython/lib-graalpython/modules/autopatch_capi.py

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -81,35 +81,46 @@ def consume_whitespace_forward(idx):
8181

8282
# scan backwards to capture the whole receiver
8383
idx = consume_whitespace_backwards(start - 2)
84-
empty = True
85-
while idx > 1:
84+
85+
def consume_pairwise_backwards(idx, l, r):
86+
level = 1
87+
idx -= 1
88+
while level and idx:
89+
c = contents[idx]
90+
if c == l:
91+
level -= 1
92+
elif c == r:
93+
level += 1
94+
idx -= 1
95+
return idx
96+
97+
def consume_identifier_backwards(idx):
98+
while (contents[idx].isidentifier() or contents[idx].isdigit()) and idx:
99+
idx -= 1
100+
return idx
101+
102+
first = True
103+
while idx:
86104
c = contents[idx]
87-
c2 = contents[idx - 1: idx + 1]
88-
if c == ')' or c == ']' or (c == '>' and c2 != '->'):
89-
if level == 0 and c == ')' and not empty:
105+
if c == ')' and first:
106+
idx = consume_pairwise_backwards(idx, '(', ')')
107+
elif c == ']':
108+
idx = consume_pairwise_backwards(idx, '[', ']')
109+
elif c.isidentifier() or c.isdigit():
110+
id_start = consume_identifier_backwards(idx)
111+
if contents[id_start + 1: idx + 1] == 'return':
90112
idx += 1
91-
break # don't include casts
92-
empty = False
93-
level += 1
113+
break
114+
idx = id_start
115+
elif c == '.':
94116
idx -= 1
95-
elif level > 0 and (c == '(' or c == '[' or c == '<'):
96-
level -= 1
97-
idx = consume_whitespace_backwards(idx - 1)
117+
elif c == '>' and idx > 1 and contents[idx - 1] == '-':
118+
idx -= 2
98119
else:
99-
if level > 0:
100-
idx -= 1
101-
elif c.isidentifier() or c.isdigit():
102-
empty = False
103-
idx -= 1
104-
elif c == '.':
105-
empty = True
106-
idx = consume_whitespace_backwards(idx - 1)
107-
elif c2 == '->':
108-
empty = True
109-
idx = consume_whitespace_backwards(idx - 2)
110-
else:
111-
idx += 1
112-
break
120+
idx += 1
121+
break
122+
first = False
123+
idx = consume_whitespace_backwards(idx)
113124

114125
receiver_start = consume_whitespace_forward(idx)
115126
receiver_string = contents[receiver_start: start - 1]
@@ -122,7 +133,6 @@ def consume_whitespace_forward(idx):
122133
# this looks like an assignment, determine the value
123134
idx = consume_whitespace_forward(idx + 1)
124135
value_start = idx
125-
empty = True
126136
while idx < len(contents) - 2:
127137
c = contents[idx]
128138
c2 = contents[idx: idx + 2]
@@ -134,7 +144,7 @@ def consume_whitespace_forward(idx):
134144
idx += 1
135145
else:
136146
if level == 0 and (c == ')' or c == ';' or c == ','):
137-
break;
147+
break
138148
if c2 == '<=' or c2 == '<<' or c2 == '>='or c2 == '>>' or c2 == '->':
139149
idx += 2
140150
else:

graalpython/lib-graalpython/patches/nanobind/nanobind.patch

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -942,26 +942,26 @@ index c49b7bb..3a5be09 100644
942942
# define NB_TYPE_GET_SLOT_IMPL 1 // Custom implementation of nb::type_get_slot
943943
# else
944944
diff --git a/nanobind/include/nanobind/nb_types.h b/nanobind/include/nanobind/nb_types.h
945-
index 2761a58..b83a651 100644
945+
index b83a651..de16f03 100644
946946
--- a/nanobind/include/nanobind/nb_types.h
947947
+++ b/nanobind/include/nanobind/nb_types.h
948-
@@ -873,14 +873,14 @@ inline detail::dict_iterator dict::end() const { return { }; }
949-
950-
#if !defined(Py_LIMITED_API) && !defined(PYPY_VERSION)
951-
inline detail::fast_iterator tuple::begin() const {
952-
- PySequence_Fast_ITEMS((PyObject*)return ((PyTupleObject *) m_ptr));
953-
+ return PySequence_Fast_ITEMS((PyObject*)((PyTupleObject *) m_ptr));
948+
@@ -877,14 +877,14 @@ inline detail::fast_iterator tuple::begin() const {
954949
}
955950
inline detail::fast_iterator tuple::end() const {
956951
PyTupleObject *v = (PyTupleObject *) m_ptr;
957-
return PySequence_Fast_ITEMS((PyObject*)v) + v->ob_base.ob_size;
952+
- return PySequence_Fast_ITEMS((PyObject*)v) + v->ob_base.ob_size;
953+
+ return PySequence_Fast_ITEMS((PyObject*)v) + Py_SIZE(v);
958954
}
959955
inline detail::fast_iterator list::begin() const {
960-
- PySequence_Fast_ITEMS((PyObject*)return ((PyListObject *) m_ptr));
961-
+ return PySequence_Fast_ITEMS((PyObject*)((PyListObject *) m_ptr));
956+
return PySequence_Fast_ITEMS((PyObject*)((PyListObject *) m_ptr));
962957
}
963958
inline detail::fast_iterator list::end() const {
964959
PyListObject *v = (PyListObject *) m_ptr;
960+
- return PySequence_Fast_ITEMS((PyObject*)v) + v->ob_base.ob_size;
961+
+ return PySequence_Fast_ITEMS((PyObject*)v) + Py_SIZE(v);
962+
}
963+
#endif
964+
965965
diff --git a/nanobind/include/nanobind/stl/detail/chrono.h b/nanobind/include/nanobind/stl/detail/chrono.h
966966
index 39bd5d9..6e9efb0 100644
967967
--- a/nanobind/include/nanobind/stl/detail/chrono.h

0 commit comments

Comments
 (0)