Skip to content

Commit 48a8296

Browse files
committed
[GR-63200] Use a control flow exception to communicate iterator exhaustion
PullRequest: graalpython/3724
2 parents 6c7551e + b356385 commit 48a8296

File tree

80 files changed

+767
-574
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+767
-574
lines changed

graalpython/com.oracle.graal.python.test/src/com/oracle/graal/python/test/datatype/PRangeTests.java

+13-10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.oracle.graal.python.PythonLanguage;
3434
import com.oracle.graal.python.builtins.objects.range.PIntRange;
3535
import com.oracle.graal.python.builtins.objects.range.PRange;
36+
import com.oracle.graal.python.lib.IteratorExhausted;
3637
import com.oracle.graal.python.lib.PyIterNextNode;
3738
import com.oracle.graal.python.lib.PyObjectGetIter;
3839
import com.oracle.graal.python.nodes.PGuards;
@@ -69,13 +70,14 @@ public void loopWithOnlyStop() throws UnexpectedResultException {
6970
Object iter = PyObjectGetIter.executeUncached(range);
7071

7172
while (true) {
72-
Object next = PyIterNextNode.executeUncached(iter);
73-
if (PyIterNextNode.isExhausted(next)) {
73+
try {
74+
Object next = PyIterNextNode.executeUncached(iter);
75+
int item = PGuards.expectInteger(next);
76+
assertEquals(index, item);
77+
index++;
78+
} catch (IteratorExhausted e) {
7479
break;
7580
}
76-
int item = PGuards.expectInteger(next);
77-
assertEquals(index, item);
78-
index++;
7981
}
8082
} finally {
8183
PythonTests.closeContext();
@@ -92,13 +94,14 @@ public void loopWithStep() throws UnexpectedResultException {
9294
Object iter = PyObjectGetIter.executeUncached(range);
9395

9496
while (true) {
95-
Object next = PyIterNextNode.executeUncached(iter);
96-
if (PyIterNextNode.isExhausted(next)) {
97+
try {
98+
Object next = PyIterNextNode.executeUncached(iter);
99+
int item = PGuards.expectInteger(next);
100+
assertEquals(index, item);
101+
index += 2;
102+
} catch (IteratorExhausted e) {
97103
break;
98104
}
99-
int item = PGuards.expectInteger(next);
100-
assertEquals(index, item);
101-
index += 2;
102105
}
103106
} finally {
104107
PythonTests.closeContext();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/ArrayModuleBuiltins.java

+12-10
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import com.oracle.graal.python.builtins.objects.range.PIntRange;
6262
import com.oracle.graal.python.builtins.objects.str.StringNodes.CastToTruffleStringCheckedNode;
6363
import com.oracle.graal.python.builtins.objects.type.TypeNodes;
64+
import com.oracle.graal.python.lib.IteratorExhausted;
6465
import com.oracle.graal.python.lib.PyIterNextNode;
6566
import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs;
6667
import com.oracle.graal.python.lib.PyObjectGetIter;
@@ -295,18 +296,19 @@ static PArray arrayIteratorInitializer(VirtualFrame frame, Node inliningTarget,
295296

296297
int length = 0;
297298
while (true) {
298-
Object nextValue = nextNode.execute(frame, inliningTarget, iter);
299-
if (PyIterNextNode.isExhausted(nextValue)) {
300-
break;
301-
}
302299
try {
303-
length = PythonUtils.addExact(length, 1);
304-
ensureCapacityNode.execute(inliningTarget, array, length);
305-
} catch (OverflowException e) {
306-
CompilerDirectives.transferToInterpreterAndInvalidate();
307-
throw PRaiseNode.raiseStatic(inliningTarget, MemoryError);
300+
Object nextValue = nextNode.execute(frame, inliningTarget, iter);
301+
try {
302+
length = PythonUtils.addExact(length, 1);
303+
ensureCapacityNode.execute(inliningTarget, array, length);
304+
} catch (OverflowException e) {
305+
CompilerDirectives.transferToInterpreterAndInvalidate();
306+
throw PRaiseNode.raiseStatic(inliningTarget, MemoryError);
307+
}
308+
putValueNode.execute(frame, inliningTarget, array, length - 1, nextValue);
309+
} catch (IteratorExhausted e) {
310+
break;
308311
}
309-
putValueNode.execute(frame, inliningTarget, array, length - 1, nextValue);
310312
}
311313

312314
setLengthNode.execute(inliningTarget, array, length);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/BuiltinFunctions.java

+92-65
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@
169169
import com.oracle.graal.python.lib.RichCmpOp;
170170
import com.oracle.graal.python.compiler.Compiler;
171171
import com.oracle.graal.python.compiler.RaisePythonExceptionErrorCallback;
172+
import com.oracle.graal.python.lib.IteratorExhausted;
172173
import com.oracle.graal.python.lib.PyCallableCheckNode;
173174
import com.oracle.graal.python.lib.PyEvalGetGlobals;
174175
import com.oracle.graal.python.lib.PyEvalGetLocals;
@@ -486,13 +487,12 @@ static boolean doObject(VirtualFrame frame, Object object,
486487
while (true) {
487488
try {
488489
Object next = nextNode.execute(frame, inliningTarget, iterator);
489-
if (PyIterNextNode.isExhausted(next)) {
490-
break;
491-
}
492490
nbrIter++;
493491
if (!isTrueNode.execute(frame, next)) {
494492
return false;
495493
}
494+
} catch (IteratorExhausted e) {
495+
break;
496496
} finally {
497497
LoopNode.reportLoopCount(inliningTarget, nbrIter);
498498
}
@@ -538,13 +538,12 @@ static boolean doObject(VirtualFrame frame, Object object,
538538
while (true) {
539539
try {
540540
Object next = nextNode.execute(frame, inliningTarget, iterator);
541-
if (PyIterNextNode.isExhausted(next)) {
542-
break;
543-
}
544541
nbrIter++;
545542
if (isTrueNode.execute(frame, next)) {
546543
return true;
547544
}
545+
} catch (IteratorExhausted e) {
546+
break;
548547
} finally {
549548
LoopNode.reportLoopCount(inliningTarget, nbrIter);
550549
}
@@ -1584,8 +1583,10 @@ static Object minmaxSequenceWithKey(VirtualFrame frame, Node inliningTarget, Obj
15841583
Object keywordArg = kwArgsAreNone ? null : keywordArgIn;
15851584

15861585
Object iterator = getIter.execute(frame, inliningTarget, arg1);
1587-
Object currentValue = nextNode.execute(frame, inliningTarget, iterator);
1588-
if (PyIterNextNode.isExhausted(currentValue)) {
1586+
Object currentValue;
1587+
try {
1588+
currentValue = nextNode.execute(frame, inliningTarget, iterator);
1589+
} catch (IteratorExhausted e) {
15891590
if (hasDefaultProfile.profile(inliningTarget, isNoValue(defaultVal))) {
15901591
throw raiseNode.raise(inliningTarget, PythonErrorType.ValueError, ErrorMessages.ARG_IS_EMPTY_SEQ, name);
15911592
} else {
@@ -1594,23 +1595,23 @@ static Object minmaxSequenceWithKey(VirtualFrame frame, Node inliningTarget, Obj
15941595
}
15951596
Object currentKey = applyKeyFunction(frame, inliningTarget, keywordArg, keyCall, currentValue);
15961597
int loopCount = 0;
1597-
try {
1598-
while (true) {
1598+
while (true) {
1599+
try {
15991600
Object nextValue = nextNode.execute(frame, inliningTarget, iterator);
1600-
if (PyIterNextNode.isExhausted(nextValue)) {
1601-
break;
1602-
}
16031601
Object nextKey = applyKeyFunction(frame, inliningTarget, keywordArg, keyCall, nextValue);
16041602
boolean isTrue = compareNode.execute(frame, inliningTarget, nextKey, currentKey, op);
16051603
if (isTrue) {
16061604
currentKey = nextKey;
16071605
currentValue = nextValue;
16081606
}
16091607
loopCount++;
1608+
} catch (IteratorExhausted e) {
1609+
break;
1610+
} finally {
1611+
LoopNode.reportLoopCount(inliningTarget, loopCount < 0 ? Integer.MAX_VALUE : loopCount);
16101612
}
1611-
} finally {
1612-
LoopNode.reportLoopCount(inliningTarget, loopCount < 0 ? Integer.MAX_VALUE : loopCount);
16131613
}
1614+
16141615
return currentValue;
16151616
}
16161617

@@ -1709,29 +1710,26 @@ static Object next(VirtualFrame frame, Object iterator, Object defaultObject,
17091710
if (!PyIterCheckNode.checkSlots(slots)) {
17101711
throw raiseTypeError.raise(inliningTarget, TypeError, ErrorMessages.OBJ_ISNT_ITERATOR, iterator);
17111712
}
1712-
Object result;
17131713
try {
1714-
result = callIterNext.execute(frame, inliningTarget, slots.tp_iternext(), iterator);
1715-
} catch (PException e) {
1714+
return callIterNext.execute(frame, inliningTarget, slots.tp_iternext(), iterator);
1715+
} catch (IteratorExhausted e) {
17161716
if (defaultIsNoValue.profile(inliningTarget, defaultObject == NO_VALUE)) {
1717-
throw e;
1717+
throw raiseStopIteration.raise(inliningTarget, StopIteration);
17181718
} else {
1719-
e.expectStopIteration(inliningTarget, stopIterationProfile);
17201719
return defaultObject;
17211720
}
1722-
}
1723-
if (PyIterNextNode.isExhausted(result)) {
1721+
} catch (PException e) {
17241722
if (defaultIsNoValue.profile(inliningTarget, defaultObject == NO_VALUE)) {
1725-
throw raiseStopIteration.raise(inliningTarget, StopIteration);
1723+
throw e;
17261724
} else {
1725+
e.expectStopIteration(inliningTarget, stopIterationProfile);
17271726
return defaultObject;
17281727
}
17291728
}
1730-
return result;
17311729
}
17321730
}
17331731

1734-
// ord(c)
1732+
// ord(c)
17351733
@Builtin(name = J_ORD, minNumOfPositionalArgs = 1)
17361734
@GenerateNodeFactory
17371735
@ImportStatic(PGuards.class)
@@ -1777,7 +1775,7 @@ static Object ord(@SuppressWarnings("unused") Object obj,
17771775
}
17781776
}
17791777

1780-
// print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
1778+
// print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
17811779
@Builtin(name = J_PRINT, takesVarArgs = true, keywordOnlyNames = {"sep", "end", "file", "flush"}, doc = "\n" +
17821780
"print(value, ..., sep=' ', end='\\n', file=sys.stdout, flush=False)\n" +
17831781
"\n" +
@@ -2191,61 +2189,90 @@ static Object sumGeneric(VirtualFrame frame, Node inliningTarget, Object iterato
21912189
@Cached PyIterNextNode nextNode,
21922190
@Shared @Cached PyNumberAddNode addNode,
21932191
@Shared @Cached InlinedConditionProfile resultFitsInInt,
2192+
@Exclusive @Cached InlinedBranchProfile seenObject,
21942193
@Exclusive @Cached InlinedBranchProfile seenInt,
21952194
@Exclusive @Cached InlinedBranchProfile seenDouble,
2196-
@Exclusive @Cached InlinedBranchProfile seenObject) {
2195+
@Exclusive @Cached InlinedBranchProfile genericBranch) {
21972196
/*
21982197
* Peel the first iteration to see what's the type.
21992198
*/
2200-
Object next = nextNode.execute(frame, inliningTarget, iterator);
2201-
if (!PyIterNextNode.isExhausted(next)) {
2202-
Object acc = addNode.execute(frame, inliningTarget, start, next);
2203-
/*
2204-
* We try to process integers/longs/doubles as long as we can. Then we always
2205-
* fall through to the generic path. `next` and `acc` are always properly set so
2206-
* that the generic path can check if there are remaining items and resume if
2207-
* necessary.
2208-
*/
2209-
if (acc instanceof Integer || acc instanceof Long) {
2210-
seenInt.enter(inliningTarget);
2211-
long longAcc = acc instanceof Integer ? (int) acc : (long) acc;
2212-
while (loopProfilePrimitive.profile(inliningTarget, !PyIterNextNode.isExhausted(next = nextNode.execute(frame, inliningTarget, iterator)))) {
2213-
try {
2214-
if (next instanceof Integer nextInt) {
2215-
longAcc = PythonUtils.addExact(longAcc, nextInt);
2216-
} else if (next instanceof Long nextLong) {
2217-
longAcc = PythonUtils.addExact(longAcc, nextLong);
2218-
} else {
2219-
break;
2220-
}
2221-
} catch (OverflowException e) {
2222-
break;
2199+
Object next;
2200+
try {
2201+
next = nextNode.execute(frame, inliningTarget, iterator);
2202+
} catch (IteratorExhausted e) {
2203+
return start;
2204+
}
2205+
Object acc = addNode.execute(frame, inliningTarget, start, next);
2206+
/*
2207+
* We try to process integers/longs/doubles as long as we can. Then we always fall
2208+
* through to the generic path. `next` and `acc` are always properly set so that the
2209+
* generic path can check if there are remaining items and resume if necessary.
2210+
*/
2211+
if (acc instanceof Integer || acc instanceof Long) {
2212+
seenInt.enter(inliningTarget);
2213+
long longAcc = acc instanceof Integer ? (int) acc : (long) acc;
2214+
boolean exitLoop = false, exhausted = false;
2215+
while (loopProfilePrimitive.profile(inliningTarget, !exitLoop)) {
2216+
try {
2217+
next = nextNode.execute(frame, inliningTarget, iterator);
2218+
if (next instanceof Integer nextInt) {
2219+
longAcc = PythonUtils.addExact(longAcc, nextInt);
2220+
} else if (next instanceof Long nextLong) {
2221+
longAcc = PythonUtils.addExact(longAcc, nextLong);
2222+
} else {
2223+
exitLoop = true;
22232224
}
2225+
} catch (OverflowException e) {
2226+
exitLoop = true;
2227+
} catch (IteratorExhausted e) {
2228+
exitLoop = true;
2229+
exhausted = true;
22242230
}
2225-
acc = maybeInt(inliningTarget, resultFitsInInt, longAcc);
2226-
} else if (acc instanceof Double doubleAcc) {
2227-
seenDouble.enter(inliningTarget);
2228-
while (loopProfilePrimitive.profile(inliningTarget, !PyIterNextNode.isExhausted(next = nextNode.execute(frame, inliningTarget, iterator)))) {
2231+
}
2232+
if (exhausted) {
2233+
return maybeInt(inliningTarget, resultFitsInInt, longAcc);
2234+
}
2235+
genericBranch.enter(inliningTarget);
2236+
acc = longAcc;
2237+
} else if (acc instanceof Double doubleAcc) {
2238+
seenDouble.enter(inliningTarget);
2239+
boolean exitLoop = false, exhausted = false;
2240+
while (loopProfilePrimitive.profile(inliningTarget, !exitLoop)) {
2241+
try {
2242+
next = nextNode.execute(frame, inliningTarget, iterator);
22292243
if (next instanceof Double nextDouble) {
22302244
doubleAcc += nextDouble;
22312245
} else {
2232-
break;
2246+
exitLoop = true;
22332247
}
2248+
} catch (IteratorExhausted e) {
2249+
exitLoop = true;
2250+
exhausted = true;
22342251
}
2235-
acc = doubleAcc;
2236-
} else {
2237-
next = nextNode.execute(frame, inliningTarget, iterator);
22382252
}
2239-
if (!PyIterNextNode.isExhausted(next)) {
2240-
seenObject.enter(inliningTarget);
2241-
do {
2242-
acc = addNode.execute(frame, inliningTarget, acc, next);
2243-
} while (loopProfileGeneric.profile(inliningTarget, !PyIterNextNode.isExhausted(next = nextNode.execute(frame, inliningTarget, iterator))));
2253+
if (exhausted) {
2254+
return doubleAcc;
22442255
}
2245-
return acc;
2256+
genericBranch.enter(inliningTarget);
2257+
acc = doubleAcc;
22462258
} else {
2247-
return start;
2259+
seenObject.enter(inliningTarget);
2260+
try {
2261+
next = nextNode.execute(frame, inliningTarget, iterator);
2262+
} catch (IteratorExhausted e) {
2263+
return acc;
2264+
}
22482265
}
2266+
boolean exhausted = false;
2267+
do {
2268+
acc = addNode.execute(frame, inliningTarget, acc, next);
2269+
try {
2270+
next = nextNode.execute(frame, inliningTarget, iterator);
2271+
} catch (IteratorExhausted e) {
2272+
exhausted = true;
2273+
}
2274+
} while (loopProfileGeneric.profile(inliningTarget, !exhausted));
2275+
return acc;
22492276
}
22502277

22512278
private static long maybeInt(Node inliningTarget, InlinedConditionProfile resultFitsInInt, long result) {

0 commit comments

Comments
 (0)