Skip to content

Commit 66695a7

Browse files
committed
Avoid instantiating and using InteropLibrary in IteratorNodes$GetLength if possible
1 parent 0cd9cbe commit 66695a7

File tree

4 files changed

+278
-146
lines changed

4 files changed

+278
-146
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/iterator/IteratorNodes.java

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@
5959
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
6060
import com.oracle.graal.python.builtins.objects.type.SpecialMethodSlot;
6161
import com.oracle.graal.python.lib.GetNextNode;
62+
import com.oracle.graal.python.lib.GraalPyObjectSizeNode;
6263
import com.oracle.graal.python.lib.PyIndexCheckNode;
6364
import com.oracle.graal.python.lib.PyIterCheckNode;
6465
import com.oracle.graal.python.lib.PyNumberAsSizeNode;
6566
import com.oracle.graal.python.lib.PyObjectGetIter;
66-
import com.oracle.graal.python.lib.PyObjectSizeNode;
6767
import com.oracle.graal.python.nodes.ErrorMessages;
6868
import com.oracle.graal.python.nodes.PGuards;
6969
import com.oracle.graal.python.nodes.PNodeWithContext;
@@ -75,6 +75,7 @@
7575
import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodSlotNode;
7676
import com.oracle.graal.python.nodes.object.BuiltinClassProfiles.IsBuiltinObjectProfile;
7777
import com.oracle.graal.python.nodes.object.InlinedGetClassNode;
78+
import com.oracle.graal.python.nodes.object.IsForeignObjectNode;
7879
import com.oracle.graal.python.nodes.util.CastToTruffleStringNode;
7980
import com.oracle.graal.python.runtime.GilNode;
8081
import com.oracle.graal.python.runtime.exception.PException;
@@ -86,6 +87,7 @@
8687
import com.oracle.truffle.api.dsl.Cached;
8788
import com.oracle.truffle.api.dsl.Cached.Shared;
8889
import com.oracle.truffle.api.dsl.Fallback;
90+
import com.oracle.truffle.api.dsl.GenerateInline;
8991
import com.oracle.truffle.api.dsl.GenerateUncached;
9092
import com.oracle.truffle.api.dsl.ImportStatic;
9193
import com.oracle.truffle.api.dsl.Specialization;
@@ -107,25 +109,22 @@ public abstract class IteratorNodes {
107109
* returned. If either has no viable or defaultvalue implementation then this node returns -1.
108110
*/
109111
@ImportStatic({PGuards.class, SpecialMethodNames.class, SpecialMethodSlot.class})
110-
public abstract static class GetLength extends PNodeWithContext {
112+
public abstract static class GetLength extends GraalPyObjectSizeNode {
111113

112114
public abstract int execute(VirtualFrame frame, Object iterable);
113115

114-
@Specialization(guards = {"isString(iterable)"})
115-
int length(VirtualFrame frame, Object iterable,
116-
@Cached PyObjectSizeNode sizeNode) {
117-
return sizeNode.execute(frame, iterable);
118-
}
116+
// Fast-path specializations for builtins are inherited
119117

120-
@Specialization(guards = {"isNoValue(iterable)"})
121-
static int length(@SuppressWarnings({"unused"}) VirtualFrame frame, @SuppressWarnings({"unused"}) Object iterable) {
118+
@Specialization(guards = "isNoValue(iterable)")
119+
static int length(@SuppressWarnings({"unused"}) VirtualFrame frame, @SuppressWarnings("unused") PNone iterable) {
122120
return -1;
123121
}
124122

125-
@Specialization(guards = {"!isNoValue(iterable)", "!isString(iterable)"}, limit = "4")
123+
@Fallback
126124
int length(VirtualFrame frame, Object iterable,
127125
@Bind("this") Node inliningTarget,
128-
@CachedLibrary("iterable") InteropLibrary iLib,
126+
@Cached IsForeignObjectNode isForeignObjectNode,
127+
@Cached GetLengthForeign getLengthForeign,
129128
@Cached InlinedGetClassNode getClassNode,
130129
@Cached PyIndexCheckNode indexCheckNode,
131130
@Cached PyNumberAsSizeNode asSizeNode,
@@ -135,18 +134,11 @@ int length(VirtualFrame frame, Object iterable,
135134
@Cached IsBuiltinObjectProfile errorProfile,
136135
@Cached InlinedConditionProfile hasLenProfile,
137136
@Cached InlinedConditionProfile hasLengthHintProfile,
138-
@Cached PRaiseNode raiseNode,
139-
@Cached TruffleString.SwitchEncodingNode switchEncodingNode,
140-
@Cached TruffleString.CodePointLengthNode codePointLengthNode,
141-
@Cached GilNode gil) {
142-
if (iLib.isString(iterable)) {
143-
gil.release(true);
144-
try {
145-
return codePointLengthNode.execute(switchEncodingNode.execute(iLib.asTruffleString(iterable), TS_ENCODING), TS_ENCODING);
146-
} catch (UnsupportedMessageException e) {
147-
throw CompilerDirectives.shouldNotReachHere();
148-
} finally {
149-
gil.acquire();
137+
@Cached PRaiseNode raiseNode) {
138+
if (isForeignObjectNode.execute(iterable)) {
139+
int foreignLen = getLengthForeign.execute(iterable);
140+
if (foreignLen != -1) {
141+
return foreignLen;
150142
}
151143
}
152144
Object clazz = getClassNode.execute(inliningTarget, iterable);
@@ -194,6 +186,35 @@ int length(VirtualFrame frame, Object iterable,
194186
}
195187
}
196188

189+
/**
190+
* Handles the special case of foreign Strings. If the input is not a string, returns -1.
191+
*/
192+
@GenerateInline(false) // Intentionally lazy initialized
193+
public abstract static class GetLengthForeign extends PNodeWithContext {
194+
public abstract int execute(Object foreign);
195+
196+
@Specialization
197+
static int doIt(Object foreign,
198+
@Bind("this") Node inliningTarget,
199+
@Cached InlinedConditionProfile isString,
200+
@CachedLibrary(limit = "3") InteropLibrary iLib,
201+
@Cached TruffleString.SwitchEncodingNode switchEncodingNode,
202+
@Cached TruffleString.CodePointLengthNode codePointLengthNode,
203+
@Cached GilNode gil) {
204+
if (isString.profile(inliningTarget, iLib.isString(foreign))) {
205+
gil.release(true);
206+
try {
207+
return codePointLengthNode.execute(switchEncodingNode.execute(iLib.asTruffleString(foreign), TS_ENCODING), TS_ENCODING);
208+
} catch (UnsupportedMessageException e) {
209+
throw CompilerDirectives.shouldNotReachHere();
210+
} finally {
211+
gil.acquire();
212+
}
213+
}
214+
return -1;
215+
}
216+
}
217+
197218
@ImportStatic(PGuards.class)
198219
@GenerateUncached
199220
public abstract static class GetInternalIteratorSequenceStorage extends Node {
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright (c) 2023, 2023, 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.util.PythonUtils.TS_ENCODING;
44+
45+
import com.oracle.graal.python.builtins.objects.bytes.PBytesLike;
46+
import com.oracle.graal.python.builtins.objects.common.HashingStorageNodes.HashingStorageLen;
47+
import com.oracle.graal.python.builtins.objects.dict.PDict;
48+
import com.oracle.graal.python.builtins.objects.list.PList;
49+
import com.oracle.graal.python.builtins.objects.set.PSet;
50+
import com.oracle.graal.python.builtins.objects.str.PString;
51+
import com.oracle.graal.python.builtins.objects.str.StringNodes;
52+
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
53+
import com.oracle.graal.python.nodes.PNodeWithContext;
54+
import com.oracle.graal.python.nodes.object.InlinedGetClassNode.GetPythonObjectClassNode;
55+
import com.oracle.truffle.api.dsl.Bind;
56+
import com.oracle.truffle.api.dsl.Cached;
57+
import com.oracle.truffle.api.dsl.Cached.Shared;
58+
import com.oracle.truffle.api.dsl.GenerateCached;
59+
import com.oracle.truffle.api.dsl.Specialization;
60+
import com.oracle.truffle.api.nodes.Node;
61+
import com.oracle.truffle.api.strings.TruffleString;
62+
63+
/**
64+
* Intended as a base-class for nodes that can use fast-path size (in the sense of
65+
* {@code PyObject_Size} and {@code PyObject_Length}) computation for builtin objects. The
66+
* subclasses are supposed to provide further specializations and a fallback.
67+
*/
68+
@GenerateCached(false)
69+
public abstract class GraalPyObjectSizeNode extends PNodeWithContext {
70+
@Specialization
71+
static int doTruffleString(TruffleString str,
72+
@Cached TruffleString.CodePointLengthNode codePointLengthNode) {
73+
return codePointLengthNode.execute(str, TS_ENCODING);
74+
}
75+
76+
@Specialization(guards = "cannotBeOverridden(object, inliningTarget, getClassNode)", limit = "1")
77+
static int doList(PList object,
78+
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
79+
@Shared("getClass") @SuppressWarnings("unused") @Cached GetPythonObjectClassNode getClassNode) {
80+
return object.getSequenceStorage().length();
81+
}
82+
83+
@Specialization(guards = "cannotBeOverridden(object, inliningTarget, getClassNode)", limit = "1")
84+
static int doTuple(PTuple object,
85+
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
86+
@Shared("getClass") @SuppressWarnings("unused") @Cached GetPythonObjectClassNode getClassNode) {
87+
return object.getSequenceStorage().length();
88+
}
89+
90+
@Specialization(guards = "cannotBeOverridden(object, inliningTarget, getClassNode)", limit = "1")
91+
static int doDict(PDict object,
92+
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
93+
@Shared("getClass") @SuppressWarnings("unused") @Cached GetPythonObjectClassNode getClassNode,
94+
@Shared("hashingStorageLen") @Cached HashingStorageLen lenNode) {
95+
return lenNode.execute(object.getDictStorage());
96+
}
97+
98+
@Specialization(guards = "cannotBeOverridden(object, inliningTarget, getClassNode)", limit = "1")
99+
static int doSet(PSet object,
100+
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
101+
@Shared("getClass") @SuppressWarnings("unused") @Cached GetPythonObjectClassNode getClassNode,
102+
@Shared("hashingStorageLen") @Cached HashingStorageLen lenNode) {
103+
return lenNode.execute(object.getDictStorage());
104+
}
105+
106+
@Specialization(guards = "cannotBeOverridden(object, inliningTarget, getClassNode)", limit = "1")
107+
static int doPString(PString object,
108+
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
109+
@Shared("getClass") @SuppressWarnings("unused") @Cached GetPythonObjectClassNode getClassNode,
110+
@Cached StringNodes.StringLenNode lenNode) {
111+
return lenNode.execute(object);
112+
}
113+
114+
@Specialization(guards = "cannotBeOverridden(object, inliningTarget, getClassNode)", limit = "1")
115+
static int doPBytes(PBytesLike object,
116+
@SuppressWarnings("unused") @Bind("this") Node inliningTarget,
117+
@Shared("getClass") @SuppressWarnings("unused") @Cached GetPythonObjectClassNode getClassNode) {
118+
return object.getSequenceStorage().length();
119+
}
120+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2023, 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.PGuards;
49+
import com.oracle.graal.python.nodes.PRaiseNode;
50+
import com.oracle.graal.python.nodes.call.special.CallUnaryMethodNode;
51+
import com.oracle.graal.python.nodes.call.special.LookupSpecialMethodSlotNode;
52+
import com.oracle.graal.python.nodes.object.InlinedGetClassNode;
53+
import com.oracle.graal.python.nodes.util.CastToJavaIntLossyNode;
54+
import com.oracle.truffle.api.dsl.Bind;
55+
import com.oracle.truffle.api.dsl.Cached;
56+
import com.oracle.truffle.api.dsl.Cached.Shared;
57+
import com.oracle.truffle.api.dsl.GenerateUncached;
58+
import com.oracle.truffle.api.dsl.ImportStatic;
59+
import com.oracle.truffle.api.dsl.Specialization;
60+
import com.oracle.truffle.api.frame.Frame;
61+
import com.oracle.truffle.api.frame.VirtualFrame;
62+
import com.oracle.truffle.api.nodes.Node;
63+
import com.oracle.truffle.api.nodes.UnexpectedResultException;
64+
65+
// Note: this has to be a top-level class because of bug/restriction in Truffle DSL
66+
@GenerateUncached
67+
@ImportStatic(SpecialMethodSlot.class)
68+
abstract class PyObjectSizeGenericNode extends Node {
69+
abstract int execute(Frame frame, Object object);
70+
71+
protected abstract Object executeObject(Frame frame, Object object);
72+
73+
@Specialization(rewriteOn = UnexpectedResultException.class)
74+
static int doInt(VirtualFrame frame, Object object,
75+
@Bind("this") Node inliningTarget,
76+
@Shared("getClass") @Cached InlinedGetClassNode getClassNode,
77+
@Shared("lookupLen") @Cached(parameters = "Len") LookupSpecialMethodSlotNode lookupLen,
78+
@Shared("callLen") @Cached CallUnaryMethodNode callLen,
79+
@Shared("index") @Cached PyNumberIndexNode indexNode,
80+
@Shared("castLossy") @Cached CastToJavaIntLossyNode castLossy,
81+
@Shared("asSize") @Cached PyNumberAsSizeNode asSizeNode,
82+
@Shared("raise") @Cached PRaiseNode raiseNode) throws UnexpectedResultException {
83+
Object lenDescr = lookupLen.execute(frame, getClassNode.execute(inliningTarget, object), object);
84+
if (lenDescr == PNone.NO_VALUE) {
85+
throw raiseNode.raise(TypeError, ErrorMessages.OBJ_HAS_NO_LEN, object);
86+
}
87+
try {
88+
return PyObjectSizeNode.checkLen(raiseNode, PGuards.expectInteger(callLen.executeObject(frame, lenDescr, object)));
89+
} catch (UnexpectedResultException e) {
90+
int len = PyObjectSizeNode.convertAndCheckLen(frame, e.getResult(), indexNode, castLossy, asSizeNode, raiseNode);
91+
throw new UnexpectedResultException(len);
92+
}
93+
}
94+
95+
@Specialization(replaces = "doInt")
96+
static int doObject(VirtualFrame frame, Object object,
97+
@Bind("this") Node inliningTarget,
98+
@Shared("getClass") @Cached InlinedGetClassNode getClassNode,
99+
@Shared("lookupLen") @Cached(parameters = "Len") LookupSpecialMethodSlotNode lookupLen,
100+
@Shared("callLen") @Cached CallUnaryMethodNode callLen,
101+
@Shared("index") @Cached PyNumberIndexNode indexNode,
102+
@Shared("castLossy") @Cached CastToJavaIntLossyNode castLossy,
103+
@Shared("asSize") @Cached PyNumberAsSizeNode asSizeNode,
104+
@Shared("raise") @Cached PRaiseNode raiseNode) {
105+
Object lenDescr = lookupLen.execute(frame, getClassNode.execute(inliningTarget, object), object);
106+
if (lenDescr == PNone.NO_VALUE) {
107+
throw raiseNode.raise(TypeError, ErrorMessages.OBJ_HAS_NO_LEN, object);
108+
}
109+
Object result = callLen.executeObject(frame, lenDescr, object);
110+
return PyObjectSizeNode.convertAndCheckLen(frame, result, indexNode, castLossy, asSizeNode, raiseNode);
111+
}
112+
}

0 commit comments

Comments
 (0)