Skip to content

Commit 4655f17

Browse files
committed
Implement throw for yield from in bytecode interpreter
1 parent 1b18050 commit 4655f17

File tree

7 files changed

+188
-10
lines changed

7 files changed

+188
-10
lines changed

graalpython/com.oracle.graal.python.test/testData/goldenFiles/CompilerTests/testCoroutine.co

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ Flags: CO_COROUTINE
99
000028 0 LOAD_FAST 0 (a)
1010
000022 2 GET_AWAITABLE
1111
000022 3 LOAD_NONE
12-
000022 >> 4 SEND 6 (to 10)
12+
000022 >> 4 SEND 10 (to 14)
1313
000022 6 YIELD_VALUE
1414
000022 7 RESUME_YIELD
1515
000022 8 JUMP_BACKWARD 4 (to 4)
16-
000022 >> 10 POP_TOP
17-
000000 11 LOAD_NONE
18-
000000 12 RETURN_VALUE
16+
000022 10 THROW 4 (exc handler 7 - 10; stack: 1)
17+
000022 12 JUMP_BACKWARD 8 (to 4)
18+
000022 >> 14 POP_TOP
19+
000000 15 LOAD_NONE
20+
000000 16 RETURN_VALUE

graalpython/com.oracle.graal.python.test/testData/goldenFiles/CompilerTests/testYieldFrom.co

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ Flags: CO_GENERATOR
99
000027 0 LOAD_FAST 0 (a)
1010
000016 2 GET_ITER
1111
000016 3 LOAD_NONE
12-
000016 >> 4 SEND 6 (to 10)
12+
000016 >> 4 SEND 10 (to 14)
1313
000016 6 YIELD_VALUE
1414
000016 7 RESUME_YIELD
1515
000016 8 JUMP_BACKWARD 4 (to 4)
16-
000016 >> 10 POP_TOP
17-
000000 11 LOAD_NONE
18-
000000 12 RETURN_VALUE
16+
000016 10 THROW 4 (exc handler 7 - 10; stack: 1)
17+
000016 12 JUMP_BACKWARD 8 (to 4)
18+
000016 >> 14 POP_TOP
19+
000000 15 LOAD_NONE
20+
000000 16 RETURN_VALUE

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/generator/GeneratorBuiltins.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@
6868
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
6969
import com.oracle.graal.python.nodes.frame.MaterializeFrameNode;
7070
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
71-
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
7271
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
72+
import com.oracle.graal.python.nodes.function.builtins.PythonQuaternaryBuiltinNode;
7373
import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode;
7474
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
7575
import com.oracle.graal.python.runtime.PythonOptions;
@@ -292,7 +292,7 @@ Object send(VirtualFrame frame, PGenerator self, Object value,
292292
// throw(typ[,val[,tb]])
293293
@Builtin(name = "throw", minNumOfPositionalArgs = 2, maxNumOfPositionalArgs = 4)
294294
@GenerateNodeFactory
295-
abstract static class ThrowNode extends PythonBuiltinNode {
295+
public abstract static class ThrowNode extends PythonQuaternaryBuiltinNode {
296296

297297
@Child private MaterializeFrameNode materializeFrameNode;
298298
@Child private GetTracebackNode getTracebackNode;

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/Compiler.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
import static com.oracle.graal.python.compiler.OpCodes.STORE_GLOBAL;
119119
import static com.oracle.graal.python.compiler.OpCodes.STORE_NAME;
120120
import static com.oracle.graal.python.compiler.OpCodes.STORE_SUBSCR;
121+
import static com.oracle.graal.python.compiler.OpCodes.THROW;
121122
import static com.oracle.graal.python.compiler.OpCodes.UNARY_OP;
122123
import static com.oracle.graal.python.compiler.OpCodes.UNPACK_EX;
123124
import static com.oracle.graal.python.compiler.OpCodes.UNPACK_SEQUENCE;
@@ -1452,12 +1453,20 @@ public Void visit(ExprTy.YieldFrom node) {
14521453

14531454
private void addYieldFrom() {
14541455
Block start = new Block();
1456+
Block resume = new Block();
14551457
Block exit = new Block();
1458+
Block exceptionHandler = new Block();
14561459
unit.useNextBlock(start);
14571460
addOp(SEND, exit);
14581461
addOp(YIELD_VALUE);
1462+
unit.pushBlock(new BlockInfo.TryExcept(resume, exceptionHandler));
1463+
unit.useNextBlock(resume);
14591464
addOp(RESUME_YIELD);
14601465
addOp(JUMP_BACKWARD, start);
1466+
unit.popBlock();
1467+
unit.useNextBlock(exceptionHandler);
1468+
addOp(THROW, exit);
1469+
addOp(JUMP_BACKWARD, start);
14611470
unit.useNextBlock(exit);
14621471
}
14631472

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/compiler/OpCodes.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,16 @@ public enum OpCodes {
608608
* Pushes (if jumping): the generator return value
609609
*/
610610
SEND(1, 2, (oparg, followingArgs, withJump) -> withJump ? 1 : 2),
611+
/**
612+
* Exception handler for forwarding {@code throw} calls into {@code yield from}.
613+
*
614+
* Pops: exception, then the generator
615+
*
616+
* Pushes (if not jumping): the generator, then the yielded value
617+
*
618+
* Pushes (if jumping): the generator return value
619+
*/
620+
THROW(1, 2, (oparg, followingArgs, withJump) -> withJump ? 1 : 2),
611621

612622
// with statements
613623
/**

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ public final class PBytecodeRootNode extends PRootNode implements BytecodeOSRNod
249249
private static final NodeSupplier<PyObjectAsciiNode> NODE_ASCII = PyObjectAsciiNode::create;
250250
private static final NodeSupplier<BuiltinFunctions.FormatNode> NODE_FORMAT = BuiltinFunctions.FormatNode::create;
251251
private static final NodeSupplier<SendNode> NODE_SEND = SendNode::create;
252+
private static final NodeSupplier<ThrowNode> NODE_THROW = ThrowNode::create;
252253

253254
private static final WriteGlobalNode UNCACHED_WRITE_GLOBAL = WriteGlobalNode.getUncached();
254255
private static final NodeFunction<String, WriteGlobalNode> NODE_WRITE_GLOBAL = WriteGlobalNode::create;
@@ -1403,6 +1404,25 @@ private Object executeInner(VirtualFrame virtualFrame, boolean resumingAfterOSR,
14031404
continue;
14041405
}
14051406
}
1407+
case OpCodesConstants.THROW: {
1408+
Object exception = localFrame.getObject(stackTop);
1409+
if (!(exception instanceof PException)) {
1410+
throw CompilerDirectives.shouldNotReachHere("interop exceptions not supported in throw");
1411+
}
1412+
Object obj = localFrame.getObject(stackTop - 1);
1413+
ThrowNode throwNode = insertChildNode(localNodes, beginBci, NODE_THROW);
1414+
boolean returned = throwNode.execute(virtualFrame, stackTop, localFrame, obj, (PException) exception);
1415+
if (!returned) {
1416+
bci++;
1417+
break;
1418+
} else {
1419+
stackTop--;
1420+
oparg |= Byte.toUnsignedInt(localBC[bci + 1]);
1421+
bci += oparg;
1422+
oparg = 0;
1423+
continue;
1424+
}
1425+
}
14061426
case OpCodesConstants.EXTENDED_ARG: {
14071427
oparg |= Byte.toUnsignedInt(localBC[++bci]);
14081428
oparg <<= 8;
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright (c) 2022, 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;
42+
43+
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.GeneratorExit;
44+
45+
import com.oracle.graal.python.builtins.objects.PNone;
46+
import com.oracle.graal.python.builtins.objects.exception.StopIterationBuiltins;
47+
import com.oracle.graal.python.builtins.objects.generator.GeneratorBuiltins;
48+
import com.oracle.graal.python.builtins.objects.generator.PGenerator;
49+
import com.oracle.graal.python.lib.PyObjectLookupAttr;
50+
import com.oracle.graal.python.nodes.PNodeWithContext;
51+
import com.oracle.graal.python.nodes.WriteUnraisableNode;
52+
import com.oracle.graal.python.nodes.call.CallNode;
53+
import com.oracle.graal.python.nodes.object.IsBuiltinClassProfile;
54+
import com.oracle.graal.python.runtime.exception.PException;
55+
import com.oracle.truffle.api.dsl.Cached;
56+
import com.oracle.truffle.api.dsl.Cached.Shared;
57+
import com.oracle.truffle.api.dsl.Fallback;
58+
import com.oracle.truffle.api.dsl.Specialization;
59+
import com.oracle.truffle.api.frame.Frame;
60+
import com.oracle.truffle.api.frame.VirtualFrame;
61+
62+
public abstract class ThrowNode extends PNodeWithContext {
63+
// Returns true when the generator finished
64+
public abstract boolean execute(VirtualFrame virtualFrame, int stackTop, Frame localFrame, Object iter, PException exception);
65+
66+
@Specialization
67+
boolean doGenerator(VirtualFrame virtualFrame, int stackTop, Frame localFrame, PGenerator generator, PException exception,
68+
@Cached GeneratorBuiltins.ThrowNode throwNode,
69+
@Cached GeneratorBuiltins.CloseNode closeNode,
70+
@Shared("exitProfile") @Cached IsBuiltinClassProfile profileExit,
71+
@Shared("profile") @Cached IsBuiltinClassProfile stopIterationProfile,
72+
@Shared("getValue") @Cached StopIterationBuiltins.StopIterationValueNode getValue) {
73+
if (profileExit.profileException(exception, GeneratorExit)) {
74+
closeNode.execute(virtualFrame, generator);
75+
throw exception;
76+
} else {
77+
try {
78+
Object value = throwNode.execute(virtualFrame, generator, exception.getEscapedException(), PNone.NO_VALUE, PNone.NO_VALUE);
79+
localFrame.setObject(stackTop, value);
80+
return false;
81+
} catch (PException e) {
82+
handleException(e, stopIterationProfile, getValue, stackTop, localFrame);
83+
return true;
84+
}
85+
}
86+
}
87+
88+
@Fallback
89+
boolean doOther(VirtualFrame virtualFrame, int stackTop, Frame localFrame, Object obj, PException exception,
90+
@Cached PyObjectLookupAttr lookupThrow,
91+
@Cached PyObjectLookupAttr lookupClose,
92+
@Cached CallNode callThrow,
93+
@Cached CallNode callClose,
94+
@Cached WriteUnraisableNode writeUnraisableNode,
95+
@Shared("exitProfile") @Cached IsBuiltinClassProfile profileExit,
96+
@Shared("profile") @Cached IsBuiltinClassProfile stopIterationProfile,
97+
@Shared("getValue") @Cached StopIterationBuiltins.StopIterationValueNode getValue) {
98+
if (profileExit.profileException(exception, GeneratorExit)) {
99+
Object close = PNone.NO_VALUE;
100+
try {
101+
close = lookupClose.execute(virtualFrame, obj, "close");
102+
} catch (PException e) {
103+
writeUnraisableNode.execute(virtualFrame, e.getEscapedException(), null, obj);
104+
}
105+
if (close != PNone.NO_VALUE) {
106+
callClose.execute(virtualFrame, close);
107+
}
108+
throw exception;
109+
} else {
110+
Object throwMethod = lookupThrow.execute(virtualFrame, obj, "throw");
111+
if (throwMethod == PNone.NO_VALUE) {
112+
throw exception;
113+
}
114+
try {
115+
Object value = callThrow.execute(virtualFrame, throwMethod);
116+
localFrame.setObject(stackTop, value);
117+
return false;
118+
} catch (PException e) {
119+
handleException(e, stopIterationProfile, getValue, stackTop, localFrame);
120+
return true;
121+
}
122+
}
123+
}
124+
125+
private static void handleException(PException e, IsBuiltinClassProfile stopIterationProfile, StopIterationBuiltins.StopIterationValueNode getValue, int stackTop, Frame localFrame) {
126+
e.expectStopIteration(stopIterationProfile);
127+
Object value = getValue.execute(e.getUnreifiedException());
128+
localFrame.setObject(stackTop, null);
129+
localFrame.setObject(stackTop - 1, value);
130+
}
131+
132+
public static ThrowNode create() {
133+
return ThrowNodeGen.create();
134+
}
135+
}

0 commit comments

Comments
 (0)