Skip to content

Commit e2857f4

Browse files
committed
Fix: Root node of current frame may also act as indirect call node.
1 parent 5115e73 commit e2857f4

File tree

2 files changed

+79
-6
lines changed

2 files changed

+79
-6
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/CExtNodes.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2302,11 +2302,11 @@ void doWithoutFrame(@SuppressWarnings("unused") Frame frame, PException e,
23022302
// the info on the next call
23032303
flag[0] = ConditionProfile.createBinaryProfile();
23042304
if (ref == null) {
2305-
ref = PArguments.getCurrentFrameInfo(ReadCallerFrameNode.getCallerFrame(null, FrameInstance.FrameAccess.READ_ONLY, false, 0));
2305+
ref = PArguments.getCurrentFrameInfo(ReadCallerFrameNode.getCurrentFrame(this, FrameInstance.FrameAccess.READ_ONLY));
23062306
}
23072307
}
23082308
if (flag[0].profile(ref == null)) {
2309-
ref = PArguments.getCurrentFrameInfo(ReadCallerFrameNode.getCallerFrame(null, FrameInstance.FrameAccess.READ_ONLY, false, 0));
2309+
ref = PArguments.getCurrentFrameInfo(ReadCallerFrameNode.getCurrentFrame(this, FrameInstance.FrameAccess.READ_ONLY));
23102310
}
23112311
transformToNative(context, ref, e);
23122312
}
@@ -2318,7 +2318,7 @@ void doGeneric(Frame frame, PException e,
23182318
if (frame == null) {
23192319
ref = context.peekTopFrameInfo();
23202320
if (ref == null) {
2321-
ref = PArguments.getCurrentFrameInfo(ReadCallerFrameNode.getCallerFrame(ref, FrameInstance.FrameAccess.READ_ONLY, true, 0));
2321+
ref = PArguments.getCurrentFrameInfo(ReadCallerFrameNode.getCurrentFrame(this, FrameInstance.FrameAccess.READ_ONLY));
23222322
}
23232323
} else {
23242324
ref = PArguments.getCurrentFrameInfo(frame);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadCallerFrameNode.java

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2020, 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
@@ -58,6 +58,8 @@
5858
import com.oracle.truffle.api.nodes.RootNode;
5959
import com.oracle.truffle.api.profiles.ConditionProfile;
6060

61+
import java.util.Objects;
62+
6163
@NodeInfo(shortName = "read_caller_fame")
6264
public final class ReadCallerFrameNode extends Node {
6365
@CompilationFinal private ConditionProfile cachedCallerFrameProfile;
@@ -137,19 +139,90 @@ private PFrame.Reference walkLevels(VirtualFrame frame, PFrame.Reference startFr
137139
return currentFrame;
138140
}
139141

142+
/**
143+
* Walk up the stack to find the currently top Python frame. This method is mostly useful for
144+
* code that cannot accept a {@code VirtualFrame} parameter (e.g. library code). It is necessary
145+
* to provide the requesting node because it might be necessary to locate the last
146+
* {@link IndirectCallNode} that effectively executes the requesting node such that the
147+
* necessary assumptions can be invalidated to avoid deopt loops.<br/>
148+
* Consider following situation:<br/>
149+
*
150+
* <pre>
151+
* public class SomeCaller extends PRootNode implements IndirectCallNode {
152+
* &#64;Child private InteropLibrary lib = ...;
153+
* public Object execute(VirtualFrame frame, Object callee, Object[] args) {
154+
* Object state = IndirectCallContext.enter(frame, ctx, this);
155+
* try {
156+
* return lib.execute(callee, args);
157+
* } finally {
158+
* IndirectCallContext.exit(frame, ctx, state);
159+
* }
160+
* }
161+
* }
162+
*
163+
* &#64;ExportLibrary(InteropLibrary.class)
164+
* public class ExecObject {
165+
* &#64;ExportMessage
166+
* boolean isExecutable() {
167+
* return true;
168+
* }
169+
*
170+
* &#64;ExportMessage
171+
* Object execute(Object[] args,
172+
* &#64;Cached SomeNode someNode) {
173+
* return someNode.execute(args);
174+
* }
175+
* }
176+
*
177+
* public class SomeNode extends Node {
178+
* public Object execute(Object[] args) {
179+
* try {
180+
* // do some stuff that might throw a PException
181+
* } catch (PException e) {
182+
* // read currently top Python frame because it's required for exception reification
183+
* Frame topPyFrame = ReadCallerFrameNode.getCurrentFrame(this
184+
* // ...
185+
* }
186+
* return PNone.NONE;
187+
* }
188+
* }
189+
* </pre>
190+
*
191+
* Assume that we run
192+
* {@code SomeCaller.create().execute(frame, new ExecObject(), new Object[0])}. It will in the
193+
* end run {@code SomeNode.execute} and if that tries to get the current frame, we need to do a
194+
* stack walk in the first run. However, on the second run, node {@code SomeCaller} should
195+
* already put the current frame reference into the context to avoid subsequent stack walks.
196+
* Since there is no Truffle call happening, this can only be achieved if we walk the node's
197+
* parent chain.
198+
*
199+
* @param requestingNode - the frame to start counting from or {@code null} to return the top
200+
* frame
201+
* @param frameAccess - the desired {@link FrameInstance} access kind
202+
*/
203+
public static Frame getCurrentFrame(Node requestingNode, FrameInstance.FrameAccess frameAccess) {
204+
CompilerDirectives.transferToInterpreterAndInvalidate();
205+
return getFrame(Objects.requireNonNull(requestingNode), null, frameAccess, false, 0);
206+
}
207+
140208
/**
141209
* Walk up the stack to find the {@code startFrame} and from then ({@code
142210
* level} + 1)-times (counting only non-internal Python frames if {@code
143211
* skipInternal} is true). If {@code startFrame} is {@code null}, return the currently top
144212
* Python frame.
145213
*
146-
* @param startFrame - the frame to start counting from or {@code null} to return the top frame
214+
* @param startFrame - the frame to start counting from (must not be {@code null})
147215
* @param frameAccess - the desired {@link FrameInstance} access kind
148216
* @param skipInternal - declares if Python internal frames should be skipped or counted
149217
* @param level - the stack depth to go to. Ignored if {@code startFrame} is {@code null}
150218
*/
151219
public static Frame getCallerFrame(PFrame.Reference startFrame, FrameInstance.FrameAccess frameAccess, boolean skipInternal, int level) {
152220
CompilerDirectives.transferToInterpreterAndInvalidate();
221+
return getFrame(null, Objects.requireNonNull(startFrame), frameAccess, skipInternal, level);
222+
}
223+
224+
private static Frame getFrame(Node requestingNode, PFrame.Reference startFrame, FrameInstance.FrameAccess frameAccess, boolean skipInternal, int level) {
225+
assert CompilerDirectives.inInterpreter();
153226
final Frame[] outputFrame = new Frame[1];
154227
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Frame>() {
155228
int i = -1;
@@ -187,7 +260,7 @@ public Frame visitFrame(FrameInstance frameInstance) {
187260
RootCallTarget target = (RootCallTarget) frameInstance.getCallTarget();
188261
RootNode rootNode = target.getRootNode();
189262
Node callNode = frameInstance.getCallNode();
190-
boolean didMark = IndirectCallNode.setEncapsulatingNeedsToPassCallerFrame(callNode);
263+
boolean didMark = IndirectCallNode.setEncapsulatingNeedsToPassCallerFrame(callNode != null ? callNode : requestingNode);
191264
if (rootNode instanceof PRootNode && outputFrame[0] == null) {
192265
PRootNode pRootNode = (PRootNode) rootNode;
193266
pRootNode.setNeedsCallerFrame();

0 commit comments

Comments
 (0)