Skip to content

Commit 623adaf

Browse files
committed
Specialize tuples/list storage if possible when created from Bytecode DSL loop
1 parent 2f6549f commit 623adaf

File tree

4 files changed

+286
-49
lines changed

4 files changed

+286
-49
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/list/PList.java

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2025, Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2026, Oracle and/or its affiliates.
33
* Copyright (c) 2013, Regents of the University of California
44
*
55
* All rights reserved.
@@ -47,13 +47,11 @@
4747
import com.oracle.truffle.api.dsl.Cached.Exclusive;
4848
import com.oracle.truffle.api.interop.InteropLibrary;
4949
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
50-
import com.oracle.truffle.api.interop.UnsupportedMessageException;
5150
import com.oracle.truffle.api.library.ExportLibrary;
5251
import com.oracle.truffle.api.library.ExportMessage;
5352
import com.oracle.truffle.api.nodes.Node;
5453
import com.oracle.truffle.api.object.Shape;
5554
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
56-
import com.oracle.truffle.api.source.SourceSection;
5755

5856
@SuppressWarnings("truffle-abstract-export")
5957
@ExportLibrary(InteropLibrary.class)
@@ -82,29 +80,6 @@ public ListOrigin getOrigin() {
8280
return origin;
8381
}
8482

85-
@ExportMessage
86-
public SourceSection getSourceLocation(@Exclusive @Cached GilNode gil) throws UnsupportedMessageException {
87-
boolean mustRelease = gil.acquire();
88-
try {
89-
ListOrigin node = getOrigin();
90-
SourceSection result = null;
91-
if (node != null) {
92-
result = node.getSourceSection();
93-
}
94-
if (result == null) {
95-
throw UnsupportedMessageException.create();
96-
}
97-
return result;
98-
} finally {
99-
gil.release(mustRelease);
100-
}
101-
}
102-
103-
@ExportMessage
104-
public boolean hasSourceLocation() {
105-
return getOrigin() != null && getOrigin().getSourceSection() != null;
106-
}
107-
10883
@ExportMessage
10984
public boolean isArrayElementModifiable(long index,
11085
@Exclusive @Cached IndexNodes.NormalizeIndexCustomMessageNode normalize,
@@ -235,7 +210,5 @@ public int updateFrom(int newSizeEstimate) {
235210
}
236211

237212
void reportUpdatedCapacity(ArrayBasedSequenceStorage newStore);
238-
239-
SourceSection getSourceSection();
240213
}
241214
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode/SequenceFromStackNode.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -255,11 +255,6 @@ protected int getCapacityEstimate() {
255255
return initialCapacity.estimate();
256256
}
257257

258-
@Override
259-
public SourceSection getSourceSection() {
260-
return null;
261-
}
262-
263258
@Override
264259
public void reportUpdatedCapacity(ArrayBasedSequenceStorage newStore) {
265260
if (CompilerDirectives.inInterpreter()) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/bytecode_dsl/PBytecodeDSLRootNode.java

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,6 @@
237237
import com.oracle.graal.python.runtime.sequence.PTupleListBase;
238238
import com.oracle.graal.python.runtime.sequence.storage.BoolSequenceStorage;
239239
import com.oracle.graal.python.runtime.sequence.storage.DoubleSequenceStorage;
240-
import com.oracle.graal.python.runtime.sequence.storage.EmptySequenceStorage;
241240
import com.oracle.graal.python.runtime.sequence.storage.IntSequenceStorage;
242241
import com.oracle.graal.python.runtime.sequence.storage.LongSequenceStorage;
243242
import com.oracle.graal.python.runtime.sequence.storage.ObjectSequenceStorage;
@@ -1732,19 +1731,11 @@ public static Object perform(VirtualFrame frame,
17321731

17331732
@Operation(storeBytecodeIndex = false)
17341733
public static final class MakeList {
1735-
@Specialization(guards = "elements.length == 0")
1736-
public static PList doEmpty(@Variadic Object[] elements,
1737-
@Bind PBytecodeDSLRootNode rootNode) {
1738-
// Common pattern is to create an empty list and then add items.
1739-
// We need to start from empty storage, so that we can specialize to, say, int storage
1740-
// if only ints are appended to this list
1741-
return PFactory.createList(rootNode.getLanguage(), EmptySequenceStorage.INSTANCE);
1742-
}
1743-
1744-
@Specialization(guards = "elements.length > 0")
1734+
@Specialization
17451735
public static PList perform(@Variadic Object[] elements,
1746-
@Bind PBytecodeDSLRootNode rootNode) {
1747-
return PFactory.createList(rootNode.getLanguage(), elements);
1736+
@Bind PBytecodeDSLRootNode rootNode,
1737+
@Cached SequenceFromArrayNode.ListFromArrayNode listFromArrayNode) {
1738+
return listFromArrayNode.execute(rootNode.getLanguage(), elements);
17481739
}
17491740
}
17501741

@@ -1791,8 +1782,9 @@ public static PFrozenSet doNonEmpty(VirtualFrame frame, @Variadic Object[] eleme
17911782
public static final class MakeTuple {
17921783
@Specialization
17931784
public static Object perform(@Variadic Object[] elements,
1794-
@Bind PBytecodeDSLRootNode rootNode) {
1795-
return PFactory.createTuple(rootNode.getLanguage(), elements);
1785+
@Bind PBytecodeDSLRootNode rootNode,
1786+
@Cached SequenceFromArrayNode.TupleFromArrayNode tupleFromArrayNode) {
1787+
return tupleFromArrayNode.execute(rootNode.getLanguage(), elements);
17961788
}
17971789
}
17981790

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/*
2+
* Copyright (c) 2022, 2026, 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.nodes.bytecode_dsl;
42+
43+
import com.oracle.graal.python.PythonLanguage;
44+
import com.oracle.graal.python.builtins.objects.list.PList;
45+
import com.oracle.graal.python.builtins.objects.list.PList.ListOrigin;
46+
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
47+
import com.oracle.graal.python.runtime.PythonContext;
48+
import com.oracle.graal.python.runtime.PythonOptions;
49+
import com.oracle.graal.python.runtime.object.PFactory;
50+
import com.oracle.graal.python.runtime.sequence.storage.ArrayBasedSequenceStorage;
51+
import com.oracle.graal.python.runtime.sequence.storage.BoolSequenceStorage;
52+
import com.oracle.graal.python.runtime.sequence.storage.DoubleSequenceStorage;
53+
import com.oracle.graal.python.runtime.sequence.storage.EmptySequenceStorage;
54+
import com.oracle.graal.python.runtime.sequence.storage.IntSequenceStorage;
55+
import com.oracle.graal.python.runtime.sequence.storage.LongSequenceStorage;
56+
import com.oracle.graal.python.runtime.sequence.storage.ObjectSequenceStorage;
57+
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage;
58+
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorage.StorageType;
59+
import com.oracle.graal.python.runtime.sequence.storage.SequenceStorageFactory;
60+
import com.oracle.truffle.api.CompilerDirectives;
61+
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
62+
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
63+
import com.oracle.truffle.api.TruffleLogger;
64+
import com.oracle.truffle.api.dsl.Bind;
65+
import com.oracle.truffle.api.dsl.Cached;
66+
import com.oracle.truffle.api.dsl.Specialization;
67+
import com.oracle.truffle.api.nodes.Node;
68+
import com.oracle.truffle.api.nodes.SlowPathException;
69+
import com.oracle.truffle.api.profiles.InlinedIntValueProfile;
70+
import com.oracle.truffle.api.source.SourceSection;
71+
72+
abstract class SequenceFromArrayNode extends Node {
73+
private static final SlowPathException SLOW_PATH_EXCEPTION = new SlowPathException();
74+
@CompilationFinal protected SequenceStorage.StorageType type = StorageType.Uninitialized;
75+
76+
SequenceStorage createSequenceStorage(Object[] objectElements, int length) {
77+
SequenceStorage storage;
78+
if (type == SequenceStorage.StorageType.Uninitialized) {
79+
CompilerDirectives.transferToInterpreterAndInvalidate();
80+
storage = initialize(objectElements);
81+
} else {
82+
try {
83+
switch (type) {
84+
// Ugh. We want to use primitive arrays during unpacking, so
85+
// we cannot dispatch generically here.
86+
case Empty: {
87+
if (length != 0) {
88+
throw SLOW_PATH_EXCEPTION;
89+
}
90+
storage = EmptySequenceStorage.INSTANCE;
91+
break;
92+
}
93+
case Boolean: {
94+
boolean[] elements = new boolean[getCapacityEstimate(length)];
95+
for (int i = 0; i < length; i++) {
96+
elements[i] = castBoolean(objectElements[i]);
97+
}
98+
storage = new BoolSequenceStorage(elements, length);
99+
break;
100+
}
101+
case Int: {
102+
int[] elements = new int[getCapacityEstimate(length)];
103+
for (int i = 0; i < length; i++) {
104+
elements[i] = castInt(objectElements[i]);
105+
}
106+
storage = new IntSequenceStorage(elements, length);
107+
break;
108+
}
109+
case Long: {
110+
long[] elements = new long[getCapacityEstimate(length)];
111+
for (int i = 0; i < length; i++) {
112+
elements[i] = castLong(objectElements[i]);
113+
}
114+
storage = new LongSequenceStorage(elements, length);
115+
break;
116+
}
117+
case Double: {
118+
double[] elements = new double[getCapacityEstimate(length)];
119+
for (int i = 0; i < length; i++) {
120+
elements[i] = castDouble(objectElements[i]);
121+
}
122+
storage = new DoubleSequenceStorage(elements, length);
123+
break;
124+
}
125+
case Generic: {
126+
storage = new ObjectSequenceStorage(objectElements, length);
127+
break;
128+
}
129+
default:
130+
CompilerDirectives.transferToInterpreterAndInvalidate();
131+
throw new RuntimeException("unexpected state");
132+
}
133+
} catch (SlowPathException e) {
134+
CompilerDirectives.transferToInterpreterAndInvalidate();
135+
type = SequenceStorage.StorageType.Generic;
136+
storage = new ObjectSequenceStorage(objectElements, length);
137+
}
138+
}
139+
return storage;
140+
}
141+
142+
@InliningCutoff
143+
private SequenceStorage initialize(Object[] objectElements) {
144+
SequenceStorage storage;
145+
try {
146+
storage = SequenceStorageFactory.createStorage(objectElements);
147+
type = storage.getElementType();
148+
} catch (Throwable t) {
149+
// we do not want to repeatedly deopt if a value execution
150+
// always raises, for example
151+
type = SequenceStorage.StorageType.Generic;
152+
throw t;
153+
}
154+
return storage;
155+
}
156+
157+
private static int castInt(Object o) throws SlowPathException {
158+
if (o instanceof Integer) {
159+
return (int) o;
160+
}
161+
throw SLOW_PATH_EXCEPTION;
162+
}
163+
164+
private static long castLong(Object o) throws SlowPathException {
165+
if (o instanceof Long) {
166+
return (long) o;
167+
}
168+
throw SLOW_PATH_EXCEPTION;
169+
}
170+
171+
private static double castDouble(Object o) throws SlowPathException {
172+
if (o instanceof Double) {
173+
return (double) o;
174+
}
175+
throw SLOW_PATH_EXCEPTION;
176+
}
177+
178+
private static boolean castBoolean(Object o) throws SlowPathException {
179+
if (o instanceof Boolean) {
180+
return (boolean) o;
181+
}
182+
throw SLOW_PATH_EXCEPTION;
183+
}
184+
185+
protected abstract int getCapacityEstimate(int length);
186+
187+
public abstract static class ListFromArrayNode extends SequenceFromArrayNode implements ListOrigin {
188+
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(ListFromArrayNode.class);
189+
private static final ListFromArrayNode UNCACHED = new ListFromArrayNode() {
190+
@Override
191+
public PList execute(PythonLanguage language, Object[] elements) {
192+
return PFactory.createList(language, elements);
193+
}
194+
};
195+
196+
public static ListFromArrayNode getUncached(int ignored) {
197+
return UNCACHED;
198+
}
199+
200+
@CompilationFinal private SizeEstimate initialCapacity;
201+
202+
public abstract PList execute(PythonLanguage language, Object[] elements);
203+
204+
@Specialization
205+
PList doIt(PythonLanguage language, Object[] elements,
206+
@Bind Node inliningTarget,
207+
@Cached InlinedIntValueProfile lengthProfile) {
208+
SequenceStorage storage = createSequenceStorage(elements, lengthProfile.profile(inliningTarget, elements.length));
209+
return PFactory.createList(language, storage, initialCapacity != null ? this : null);
210+
}
211+
212+
@Override
213+
protected int getCapacityEstimate(int length) {
214+
assert isAdoptable();
215+
if (initialCapacity == null) {
216+
CompilerDirectives.transferToInterpreterAndInvalidate();
217+
initialCapacity = new SizeEstimate(length);
218+
}
219+
return Math.max(initialCapacity.estimate(), length);
220+
}
221+
222+
@Override
223+
public void reportUpdatedCapacity(ArrayBasedSequenceStorage newStore) {
224+
if (CompilerDirectives.inInterpreter()) {
225+
if (PythonContext.get(this).getOption(PythonOptions.OverallocateLiteralLists)) {
226+
if (newStore.getCapacity() > initialCapacity.estimate()) {
227+
initialCapacity.updateFrom(newStore.getCapacity());
228+
LOGGER.finest(() -> {
229+
SourceSection encapsulatingSourceSection = getEncapsulatingSourceSection();
230+
String sourceSection = encapsulatingSourceSection == null ? "<unavailable source>" : encapsulatingSourceSection.toString();
231+
return String.format("Updating list size estimate at %s. Observed capacity: %d, new estimate: %d", sourceSection, newStore.getCapacity(),
232+
initialCapacity.estimate());
233+
});
234+
}
235+
if (newStore.getElementType().generalizesFrom(type)) {
236+
type = newStore.getElementType();
237+
LOGGER.finest(() -> {
238+
SourceSection encapsulatingSourceSection = getEncapsulatingSourceSection();
239+
String sourceSection = encapsulatingSourceSection == null ? "<unavailable source>" : encapsulatingSourceSection.toString();
240+
return String.format("Updating list type estimate at %s. New type: %s", sourceSection, type.name());
241+
});
242+
}
243+
}
244+
}
245+
// n.b.: it's ok that this races when the code is already being compiled
246+
// or if we're running on multiple threads. if the update isn't seen, we
247+
// are not incorrect, we just don't benefit from the optimization
248+
}
249+
}
250+
251+
public abstract static class TupleFromArrayNode extends SequenceFromArrayNode {
252+
private static final TupleFromArrayNode UNCACHED = new TupleFromArrayNode() {
253+
@Override
254+
public PTuple execute(PythonLanguage language, Object[] elements) {
255+
return PFactory.createTuple(language, elements);
256+
}
257+
};
258+
259+
public static TupleFromArrayNode getUncached() {
260+
return UNCACHED;
261+
}
262+
263+
public abstract PTuple execute(PythonLanguage language, Object[] elements);
264+
265+
@Specialization
266+
PTuple doIt(PythonLanguage language, Object[] elements,
267+
@Bind Node inliningTarget,
268+
@Cached InlinedIntValueProfile lengthProfile) {
269+
return PFactory.createTuple(language, createSequenceStorage(elements, lengthProfile.profile(inliningTarget, elements.length)));
270+
}
271+
272+
@Override
273+
protected int getCapacityEstimate(int length) {
274+
return length;
275+
}
276+
}
277+
}

0 commit comments

Comments
 (0)