Skip to content

Commit 8a8c3f0

Browse files
committed
[GR-33179] Small interpreter performance improvs and additional lib nodes
PullRequest: graalpython/1940
2 parents 6e89e72 + d6964ba commit 8a8c3f0

File tree

8 files changed

+298
-6
lines changed

8 files changed

+298
-6
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonBuiltinClass.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public void setAttributeUnsafe(Object name, Object value) {
8383
super.setAttribute(name, value);
8484
}
8585

86-
public PythonBuiltinClassType getType() {
86+
public final PythonBuiltinClassType getType() {
8787
return type;
8888
}
8989

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/type/PythonManagedClass.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public abstract class PythonManagedClass extends PythonObject implements PythonA
7474

7575
/**
7676
* Access using methods in {@link SpecialMethodSlot}.
77-
*
77+
*
7878
* @see SpecialMethodSlot
7979
*/
8080
Object[] specialMethodSlots;
@@ -350,7 +350,7 @@ public String toString() {
350350
return String.format("<class '%s'>", qualName);
351351
}
352352

353-
public PythonAbstractClass[] getBaseClasses() {
353+
public final PythonAbstractClass[] getBaseClasses() {
354354
return baseClasses;
355355
}
356356

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectGetAttr.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
4444
import com.oracle.graal.python.builtins.objects.PNone;
4545
import com.oracle.graal.python.builtins.objects.type.SpecialMethodSlot;
46+
import com.oracle.graal.python.nodes.ErrorMessages;
47+
import com.oracle.graal.python.nodes.PRaiseNode;
4648
import com.oracle.graal.python.nodes.attributes.GetAttributeNode.GetFixedAttributeNode;
4749
import com.oracle.graal.python.nodes.call.special.CallBinaryMethodNode;
4850
import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodSlotNode;
@@ -82,6 +84,16 @@ static Object getDynamicAttr(Frame frame, Object receiver, Object name,
8284
@Cached IsBuiltinClassProfile errorProfile) {
8385
Object type = getClass.execute(receiver);
8486
Object getattribute = lookupGetattribute.execute(frame, type, receiver);
87+
if (!getClass.isAdoptable()) {
88+
// It pays to try this in the uncached case, avoiding a full call to __getattribute__
89+
Object result = PyObjectLookupAttr.readAttributeQuickly(type, getattribute, receiver, name);
90+
if (result != null) {
91+
if (result == PNone.NO_VALUE) {
92+
throw PRaiseNode.getUncached().raise(PythonBuiltinClassType.AttributeError, ErrorMessages.OBJ_P_HAS_NO_ATTR_S, receiver, name);
93+
}
94+
return result;
95+
}
96+
}
8597
try {
8698
return callGetattribute.executeObject(frame, getattribute, receiver, name);
8799
} catch (PException e) {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (c) 2021, 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.lib;
42+
43+
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError;
44+
45+
import com.oracle.graal.python.builtins.objects.PNone;
46+
import com.oracle.graal.python.builtins.objects.dict.PDict;
47+
import com.oracle.graal.python.builtins.objects.type.SpecialMethodSlot;
48+
import com.oracle.graal.python.nodes.ErrorMessages;
49+
import com.oracle.graal.python.nodes.PRaiseNode;
50+
import com.oracle.graal.python.nodes.attributes.LookupCallableSlotInMRONode;
51+
import com.oracle.graal.python.nodes.call.special.CallUnaryMethodNode;
52+
import com.oracle.graal.python.nodes.object.GetClassNode;
53+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
54+
import com.oracle.truffle.api.dsl.Cached;
55+
import com.oracle.truffle.api.dsl.GenerateUncached;
56+
import com.oracle.truffle.api.dsl.ImportStatic;
57+
import com.oracle.truffle.api.dsl.Specialization;
58+
import com.oracle.truffle.api.frame.Frame;
59+
import com.oracle.truffle.api.nodes.Node;
60+
61+
/**
62+
* Equivalent PyObject_GetIter
63+
*/
64+
@GenerateUncached
65+
@ImportStatic(SpecialMethodSlot.class)
66+
public abstract class PyObjectGetIter extends Node {
67+
public abstract Object execute(Frame frame, Object receiver);
68+
69+
@Specialization
70+
static Object getIter(Frame frame, Object receiver,
71+
@Cached GetClassNode getReceiverClass,
72+
@Cached GetClassNode getResultClass,
73+
@Cached(parameters = "Iter") LookupCallableSlotInMRONode lookupIter,
74+
@Cached(parameters = "Next") LookupCallableSlotInMRONode lookupIternext,
75+
@Cached(parameters = "GetItem") LookupCallableSlotInMRONode lookupGetItem,
76+
@Cached PythonObjectFactory factory,
77+
@Cached PRaiseNode raise,
78+
@Cached CallUnaryMethodNode callIter) {
79+
Object type = getReceiverClass.execute(receiver);
80+
Object iterMethod = lookupIter.execute(type);
81+
if (iterMethod instanceof PNone) {
82+
if (pySequenceCheck(receiver, type, lookupGetItem)) {
83+
return factory.createSequenceIterator(receiver);
84+
}
85+
} else {
86+
Object result = callIter.executeObject(frame, iterMethod, receiver);
87+
Object resultType = getResultClass.execute(result);
88+
if (!pyIterCheck(resultType, lookupIternext)) {
89+
throw raise.raise(TypeError, ErrorMessages.RETURNED_NONITER, result);
90+
}
91+
return result;
92+
}
93+
throw raise.raise(TypeError, ErrorMessages.OBJ_NOT_ITERABLE, receiver);
94+
}
95+
96+
private static boolean pySequenceCheck(Object receiver, Object type, LookupCallableSlotInMRONode lookupGetitem) {
97+
if (receiver instanceof PDict) {
98+
return false;
99+
}
100+
Object getItemMethod = lookupGetitem.execute(type);
101+
return !(getItemMethod instanceof PNone);
102+
}
103+
104+
private static boolean pyIterCheck(Object type, LookupCallableSlotInMRONode lookupIternext) {
105+
return !(lookupIternext.execute(type) instanceof PNone);
106+
}
107+
108+
public static PyObjectGetIter getUncached() {
109+
return PyObjectGetIterNodeGen.getUncached();
110+
}
111+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/lib/PyObjectLookupAttr.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import com.oracle.graal.python.builtins.objects.function.BuiltinMethodDescriptor;
4646
import com.oracle.graal.python.builtins.objects.module.ModuleBuiltinsFactory;
4747
import com.oracle.graal.python.builtins.objects.object.ObjectBuiltinsFactory;
48+
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
49+
import com.oracle.graal.python.builtins.objects.type.PythonBuiltinClass;
4850
import com.oracle.graal.python.builtins.objects.type.PythonManagedClass;
4951
import com.oracle.graal.python.builtins.objects.type.SpecialMethodSlot;
5052
import com.oracle.graal.python.builtins.objects.type.TypeBuiltinsFactory;
@@ -214,6 +216,13 @@ static Object getDynamicAttr(Frame frame, Object receiver, Object name,
214216
@Shared("errorProfile") @Cached IsBuiltinClassProfile errorProfile) {
215217
Object type = getClass.execute(receiver);
216218
Object getattribute = lookupGetattribute.execute(frame, type, receiver);
219+
if (!getClass.isAdoptable()) {
220+
// It pays to try this in the uncached case, avoiding a full call to __getattribute__
221+
Object result = readAttributeQuickly(type, getattribute, receiver, name);
222+
if (result != null) {
223+
return result;
224+
}
225+
}
217226
try {
218227
return callGetattribute.executeObject(frame, getattribute, receiver, name);
219228
} catch (PException e) {
@@ -233,4 +242,60 @@ static Object getDynamicAttr(Frame frame, Object receiver, Object name,
233242
public static PyObjectLookupAttr getUncached() {
234243
return PyObjectLookupAttrNodeGen.getUncached();
235244
}
245+
246+
/**
247+
* We try to improve the performance of this in the interpreter and uncached case for a simple
248+
* class of cases. The reason is that in the uncached case, we would do a full call to the
249+
* __getattribute__ method and that raises an exception, which is expensive and may not be
250+
* needed. This actually always helps in interpreted mode even in the cached case, but we cannot
251+
* really use it then, because when we only use it in the interpreter, the compiled code would
252+
* skip this and immediately deopt, if the code after was never run and initialized. And anyway,
253+
* the hope is that in the cached case, we just stay in the above specializations
254+
* {@link #doBuiltinObject}, {@link #doBuiltinModule}, or {@link #doBuiltinType} and get the
255+
* fast path through them.
256+
*
257+
* This inlines parts of the logic of the {@code ObjectBuiltins.GetAttributeNode} and {@code
258+
* ModuleBuiltins.GetAttributeNode}. This method returns {@code PNone.NO_VALUE} when the
259+
* attribute is not found and the original would've raised an AttributeError. It returns {@code
260+
* null} when no shortcut was applicable. If {@code PNone.NO_VALUE} was returned, name is
261+
* guaranteed to be a {@code java.lang.String}.
262+
*/
263+
static final Object readAttributeQuickly(Object type, Object getattribute, Object receiver, Object name) {
264+
if (name instanceof String) {
265+
if (getattribute == OBJ_GET_ATTRIBUTE && type instanceof PythonManagedClass) {
266+
String stringName = (String) name;
267+
PythonAbstractClass[] bases = ((PythonManagedClass) type).getBaseClasses();
268+
if (bases.length == 1) {
269+
PythonAbstractClass base = bases[0];
270+
if (base instanceof PythonBuiltinClass &&
271+
((PythonBuiltinClass) base).getType() == PythonBuiltinClassType.PythonObject) {
272+
if (!(stringName.charAt(0) == '_' && stringName.charAt(1) == '_')) {
273+
// not a special name, so this attribute cannot be inherited, and can
274+
// only be on the type or the object. If it's on the type, return to
275+
// the generic code.
276+
ReadAttributeFromObjectNode readUncached = ReadAttributeFromObjectNode.getUncached();
277+
Object descr = readUncached.execute(type, stringName);
278+
if (descr == PNone.NO_VALUE) {
279+
return readUncached.execute(receiver, stringName);
280+
}
281+
}
282+
}
283+
}
284+
} else if (getattribute == MODULE_GET_ATTRIBUTE && type == PythonBuiltinClassType.PythonModule) {
285+
// this is slightly simpler than the previous case, since we don't need to check
286+
// the type. There may be a module-level __getattr__, however. Since that would be
287+
// a call anyway, we return to the generic code in that case
288+
String stringName = (String) name;
289+
if (!(stringName.charAt(0) == '_' && stringName.charAt(1) == '_')) {
290+
// not a special name, so this attribute cannot be on the module class
291+
ReadAttributeFromObjectNode readUncached = ReadAttributeFromObjectNode.getUncached();
292+
Object result = readUncached.execute(receiver, stringName);
293+
if (result != PNone.NO_VALUE) {
294+
return result;
295+
}
296+
}
297+
}
298+
}
299+
return null;
300+
}
236301
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright (c) 2021, 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.lib;
42+
43+
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError;
44+
45+
import com.oracle.graal.python.builtins.objects.PNone;
46+
import com.oracle.graal.python.builtins.objects.type.SpecialMethodSlot;
47+
import com.oracle.graal.python.nodes.ErrorMessages;
48+
import com.oracle.graal.python.nodes.PRaiseNode;
49+
import com.oracle.graal.python.nodes.attributes.LookupCallableSlotInMRONode;
50+
import com.oracle.graal.python.nodes.call.special.CallTernaryMethodNode;
51+
import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodSlotNode;
52+
import com.oracle.graal.python.nodes.object.GetClassNode;
53+
import com.oracle.truffle.api.dsl.Cached;
54+
import com.oracle.truffle.api.dsl.GenerateUncached;
55+
import com.oracle.truffle.api.dsl.ImportStatic;
56+
import com.oracle.truffle.api.dsl.Specialization;
57+
import com.oracle.truffle.api.frame.Frame;
58+
import com.oracle.truffle.api.frame.VirtualFrame;
59+
import com.oracle.truffle.api.nodes.Node;
60+
61+
/**
62+
* Equivalent to use for PyObject_SetItem.
63+
*/
64+
@GenerateUncached
65+
@ImportStatic(SpecialMethodSlot.class)
66+
public abstract class PyObjectSetItem extends Node {
67+
public abstract void execute(Frame frame, Object container, Object index, Object item);
68+
69+
@Specialization
70+
void doWithFrame(VirtualFrame frame, Object primary, Object index, Object value,
71+
@Cached GetClassNode getClassNode,
72+
@Cached("create(SetItem)") LookupSpecialMethodSlotNode lookupSetitem,
73+
@Cached PRaiseNode raise,
74+
@Cached CallTernaryMethodNode callSetitem) {
75+
Object setitem = lookupSetitem.execute(frame, getClassNode.execute(primary), primary);
76+
if (setitem == PNone.NO_VALUE) {
77+
throw raise.raise(TypeError, ErrorMessages.P_OBJ_DOES_NOT_SUPPORT_ITEM_ASSIGMENT, primary);
78+
}
79+
callSetitem.execute(frame, setitem, primary, index, value);
80+
}
81+
82+
@Specialization(replaces = "doWithFrame")
83+
void doGeneric(Object primary, Object index, Object value,
84+
@Cached GetClassNode getClassNode,
85+
@Cached(parameters = "SetItem") LookupCallableSlotInMRONode lookupSetitem,
86+
@Cached PRaiseNode raise,
87+
@Cached CallTernaryMethodNode callSetitem) {
88+
Object setitem = lookupSetitem.execute(getClassNode.execute(primary));
89+
if (setitem == PNone.NO_VALUE) {
90+
throw raise.raise(TypeError, ErrorMessages.P_OBJ_DOES_NOT_SUPPORT_ITEM_ASSIGMENT, primary);
91+
}
92+
callSetitem.execute(null, setitem, primary, index, value);
93+
}
94+
95+
public static PyObjectSetItem getUncached() {
96+
return PyObjectSetItemNodeGen.getUncached();
97+
}
98+
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ public abstract class ErrorMessages {
516516
public static final String RETURNED_NON_STRING = "%s returned non-string (type %p)";
517517
public static final String P_S_RETURNED_NON_STRING = "%p.%s returned non-string (type %p)";
518518
public static final String RETURNED_NONBYTES = "%s returned non-bytes (type %p)";
519+
public static final String RETURNED_NONITER = "iter() returned non-iterator of type %p";
519520
public static final String RETURNED_NULL_WO_SETTING_ERROR = "%s returned NULL without setting an error";
520521
public static final String RETURNED_RESULT_WITH_ERROR_SET = "%s returned a result with an error set";
521522
public static final String RETURNED_UNEXPECTE_RET_CODE_EXPECTED_INT_BUT_WAS_S = "%s returned an unexpected return code; expected 'int' but was %s";

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/statement/AbstractImportNode.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,19 @@ abstract static class PyModuleIsInitializing extends Node {
342342

343343
@Specialization
344344
static boolean isInitializing(VirtualFrame frame, Object mod,
345-
@Cached PyObjectGetAttr getSpecNode,
345+
@Cached ConditionProfile hasSpec,
346+
@Cached PyObjectLookupAttr getSpecNode,
346347
@Cached PyObjectLookupAttr getInitNode,
347348
// CPython uses PyObject_GetAttr, but ignores the exception here
348349
@Cached PyObjectIsTrueNode isTrue) {
349350
try {
350351
Object spec = getSpecNode.execute(frame, mod, SpecialAttributeNames.__SPEC__);
351-
Object initializing = getInitNode.execute(frame, spec, "_initializing");
352-
return isTrue.execute(frame, initializing);
352+
if (hasSpec.profile(spec != PNone.NO_VALUE)) {
353+
Object initializing = getInitNode.execute(frame, spec, "_initializing");
354+
return isTrue.execute(frame, initializing);
355+
} else {
356+
return false;
357+
}
353358
} catch (PException e) {
354359
// _PyModuleSpec_IsInitializing clears any error that happens during getting the
355360
// __spec__ or _initializing attributes

0 commit comments

Comments
 (0)