Skip to content

Commit 87c43fb

Browse files
committed
Lazily create traceback from frame info.
1 parent a910a26 commit 87c43fb

File tree

9 files changed

+267
-48
lines changed

9 files changed

+267
-48
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import com.oracle.graal.python.builtins.objects.common.SequenceStorageNodes;
104104
import com.oracle.graal.python.builtins.objects.complex.PComplex;
105105
import com.oracle.graal.python.builtins.objects.dict.PDict;
106+
import com.oracle.graal.python.builtins.objects.exception.GetTracebackNode;
106107
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
107108
import com.oracle.graal.python.builtins.objects.frame.PFrame;
108109
import com.oracle.graal.python.builtins.objects.function.PArguments;
@@ -416,7 +417,8 @@ abstract static class PyErrFetchNode extends NativeBuiltin {
416417
public Object run(VirtualFrame frame, Object module,
417418
@Exclusive @Cached GetClassNode getClassNode,
418419
@Exclusive @Cached CExtNodes.GetNativeNullNode getNativeNullNode,
419-
@Cached MaterializeFrameNode materializeNode) {
420+
@Cached MaterializeFrameNode materializeNode,
421+
@Cached GetTracebackNode getTracebackNode) {
420422
PythonContext context = getContext();
421423
PException currentException = context.getCurrentException();
422424
Object result;
@@ -430,12 +432,11 @@ public Object run(VirtualFrame frame, Object module,
430432
// could (since this is python_cext API) call this from Python
431433
// instead of sys.exc_info() and then it should also work. So we
432434
// do do it here if it hasn't been done already.
433-
PTraceback storedTraceback = exception.getTraceback();
434-
if (storedTraceback == null) {
435+
if (!exception.hasTraceback()) {
435436
PFrame escapedFrame = materializeNode.execute(frame, this, true, false);
436-
exception.setTraceback(factory().createTraceback(escapedFrame, currentException));
437+
exception.reifyException(escapedFrame, factory());
437438
}
438-
result = factory().createTuple(new Object[]{getClassNode.execute(exception), exception, exception.getTraceback()});
439+
result = factory().createTuple(new Object[]{getClassNode.execute(exception), exception, getTracebackNode.execute(frame, exception)});
439440
context.setCurrentException(null);
440441
}
441442
return result;

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.oracle.graal.python.builtins.CoreFunctions;
5757
import com.oracle.graal.python.builtins.PythonBuiltins;
5858
import com.oracle.graal.python.builtins.objects.PNone;
59+
import com.oracle.graal.python.builtins.objects.exception.GetTracebackNode;
5960
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
6061
import com.oracle.graal.python.builtins.objects.frame.PFrame;
6162
import com.oracle.graal.python.builtins.objects.frame.PFrame.Reference;
@@ -64,6 +65,7 @@
6465
import com.oracle.graal.python.builtins.objects.list.PList;
6566
import com.oracle.graal.python.builtins.objects.module.PythonModule;
6667
import com.oracle.graal.python.builtins.objects.str.PString;
68+
import com.oracle.graal.python.builtins.objects.traceback.PTraceback;
6769
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
6870
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode.NoAttributeHandler;
6971
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode;
@@ -279,7 +281,8 @@ public abstract static class ExcInfoNode extends PythonBuiltinNode {
279281
public Object run(VirtualFrame frame,
280282
@Cached GetClassNode getClassNode,
281283
@Cached GetCaughtExceptionNode getCaughtExceptionNode,
282-
@Cached ReadCallerFrameNode readCallerFrameNode) {
284+
@Cached ReadCallerFrameNode readCallerFrameNode,
285+
@Cached GetTracebackNode getTracebackNode) {
283286
PException currentException = getCaughtExceptionNode.execute(frame);
284287
assert currentException != PException.NO_EXCEPTION;
285288
if (currentException == null) {
@@ -289,8 +292,12 @@ public Object run(VirtualFrame frame,
289292
Reference currentFrameInfo = PArguments.getCurrentFrameInfo(frame);
290293
PFrame escapedFrame = readCallerFrameNode.executeWith(frame, currentFrameInfo, 0);
291294
currentFrameInfo.markAsEscaped();
292-
exception.setTraceback(factory().createTraceback(escapedFrame, currentException));
293-
return factory().createTuple(new Object[]{getClassNode.execute(exception), exception, exception.getTraceback()});
295+
PTraceback exceptionTraceback = getTracebackNode.execute(frame, exception);
296+
// n.b. a call to 'sys.exc_info' always creates a new traceback with the current
297+
// frame and links (via 'tb_next') to the traceback of the exception
298+
PTraceback chainedTraceback = factory().createTraceback(escapedFrame, exceptionTraceback);
299+
exception.setTraceback(chainedTraceback);
300+
return factory().createTuple(new Object[]{getClassNode.execute(exception), exception, chainedTraceback});
294301
}
295302
}
296303

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

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,31 +48,43 @@
4848
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.PCallCapiFunction;
4949
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.ToPyObjectNode;
5050
import com.oracle.graal.python.builtins.objects.dict.PDict;
51+
import com.oracle.graal.python.builtins.objects.exception.GetTracebackNode;
52+
import com.oracle.graal.python.builtins.objects.exception.GetTracebackNodeGen;
5153
import com.oracle.graal.python.builtins.objects.exception.PBaseException;
54+
import com.oracle.graal.python.builtins.objects.function.PArguments;
55+
import com.oracle.graal.python.builtins.objects.function.Signature;
5256
import com.oracle.graal.python.builtins.objects.traceback.PTraceback;
5357
import com.oracle.graal.python.builtins.objects.type.PythonAbstractClass;
5458
import com.oracle.graal.python.builtins.objects.type.PythonClass;
5559
import com.oracle.graal.python.nodes.PNodeWithContext;
5660
import com.oracle.graal.python.nodes.PRaiseNode;
61+
import com.oracle.graal.python.nodes.PRootNode;
62+
import com.oracle.graal.python.nodes.call.GenericInvokeNode;
5763
import com.oracle.graal.python.nodes.object.GetClassNode;
64+
import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext;
5865
import com.oracle.graal.python.runtime.PythonContext;
5966
import com.oracle.graal.python.runtime.exception.PException;
6067
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
6168
import com.oracle.truffle.api.Assumption;
69+
import com.oracle.truffle.api.CompilerDirectives;
6270
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
71+
import com.oracle.truffle.api.RootCallTarget;
72+
import com.oracle.truffle.api.TruffleLanguage;
6373
import com.oracle.truffle.api.dsl.Cached;
6474
import com.oracle.truffle.api.dsl.Cached.Exclusive;
6575
import com.oracle.truffle.api.dsl.Cached.Shared;
6676
import com.oracle.truffle.api.dsl.CachedContext;
6777
import com.oracle.truffle.api.dsl.GenerateUncached;
6878
import com.oracle.truffle.api.dsl.ImportStatic;
6979
import com.oracle.truffle.api.dsl.Specialization;
80+
import com.oracle.truffle.api.frame.VirtualFrame;
7081
import com.oracle.truffle.api.interop.InteropLibrary;
7182
import com.oracle.truffle.api.interop.UnknownIdentifierException;
7283
import com.oracle.truffle.api.interop.UnsupportedMessageException;
7384
import com.oracle.truffle.api.library.CachedLibrary;
7485
import com.oracle.truffle.api.library.ExportLibrary;
7586
import com.oracle.truffle.api.library.ExportMessage;
87+
import com.oracle.truffle.api.profiles.BranchProfile;
7688
import com.oracle.truffle.llvm.spi.NativeTypeLibrary;
7789

7890
@ExportLibrary(InteropLibrary.class)
@@ -136,6 +148,9 @@ protected Object readMember(String member,
136148
@ImportStatic(PThreadState.class)
137149
@GenerateUncached
138150
abstract static class ThreadStateReadNode extends PNodeWithContext {
151+
152+
private static GetTracebackRootNode getTracebackRootNode;
153+
139154
public abstract Object execute(Object key);
140155

141156
@Specialization(guards = "eq(key, CUR_EXC_TYPE)")
@@ -162,12 +177,16 @@ PBaseException doCurExcValue(@SuppressWarnings("unused") String key,
162177
}
163178

164179
@Specialization(guards = "eq(key, CUR_EXC_TRACEBACK)")
165-
PTraceback doCurExcTraceback(@SuppressWarnings("unused") String key,
166-
@Shared("context") @CachedContext(PythonLanguage.class) PythonContext context) {
180+
Object doCurExcTraceback(@SuppressWarnings("unused") String key,
181+
@Shared("context") @CachedContext(PythonLanguage.class) PythonContext context,
182+
@Shared("invokeNode") @Cached GenericInvokeNode invokeNode) {
167183
PException currentException = context.getCurrentException();
168184
if (currentException != null) {
169185
PBaseException exceptionObject = currentException.getExceptionObject();
170-
return exceptionObject.getTraceback();
186+
// we use 'GetTracebackNode' via a call to have a frame
187+
Object[] arguments = PArguments.create(1);
188+
PArguments.setArgument(arguments, 0, exceptionObject);
189+
return invokeNode.execute(null, ensureCallTarget(context.getLanguage()), arguments);
171190
}
172191
return null;
173192
}
@@ -196,12 +215,16 @@ PBaseException doExcValue(@SuppressWarnings("unused") String key,
196215
}
197216

198217
@Specialization(guards = "eq(key, EXC_TRACEBACK)")
199-
PTraceback doExcTraceback(@SuppressWarnings("unused") String key,
200-
@Shared("context") @CachedContext(PythonLanguage.class) PythonContext context) {
218+
Object doExcTraceback(@SuppressWarnings("unused") String key,
219+
@Shared("context") @CachedContext(PythonLanguage.class) PythonContext context,
220+
@Shared("invokeNode") @Cached GenericInvokeNode invokeNode) {
201221
PException currentException = context.getCaughtException();
202222
if (currentException != null) {
203223
PBaseException exceptionObject = currentException.getExceptionObject();
204-
return exceptionObject.getTraceback();
224+
// we use 'GetTracebackNode' via a call to have a frame
225+
Object[] arguments = PArguments.create(1);
226+
PArguments.setArgument(arguments, 0, exceptionObject);
227+
return invokeNode.execute(null, ensureCallTarget(context.getLanguage()), arguments);
205228
}
206229
return null;
207230
}
@@ -228,6 +251,13 @@ Object doPrev(@SuppressWarnings("unused") String key,
228251
protected static boolean eq(String key, String expected) {
229252
return expected.equals(key);
230253
}
254+
255+
private static RootCallTarget ensureCallTarget(PythonLanguage language) {
256+
if (getTracebackRootNode == null) {
257+
getTracebackRootNode = new GetTracebackRootNode(language);
258+
}
259+
return getTracebackRootNode.getCallTarget();
260+
}
231261
}
232262

233263
// WRITE
@@ -441,4 +471,47 @@ protected static Assumption singleNativeContextAssumption() {
441471
return PythonContext.getSingleNativeContextAssumption();
442472
}
443473
}
474+
475+
private static final class GetTracebackRootNode extends PRootNode {
476+
477+
protected GetTracebackRootNode(TruffleLanguage<?> language) {
478+
super(language);
479+
}
480+
481+
@Child private GetTracebackNode getTracebackNode;
482+
@Child private CalleeContext calleeContext = CalleeContext.create();
483+
484+
private final BranchProfile profile = BranchProfile.create();
485+
486+
@Override
487+
public Object execute(VirtualFrame frame) {
488+
CalleeContext.enter(frame, profile);
489+
try {
490+
if (getTracebackNode == null) {
491+
CompilerDirectives.transferToInterpreterAndInvalidate();
492+
getTracebackNode = insert(GetTracebackNodeGen.create());
493+
}
494+
PBaseException e = (PBaseException) PArguments.getArgument(frame, 0);
495+
return getTracebackNode.execute(frame, e);
496+
} finally {
497+
calleeContext.exit(frame, this);
498+
}
499+
}
500+
501+
@Override
502+
public boolean isInternal() {
503+
return true;
504+
}
505+
506+
@Override
507+
public Signature getSignature() {
508+
return Signature.EMPTY;
509+
}
510+
511+
@Override
512+
public boolean isPythonInternal() {
513+
return true;
514+
}
515+
516+
}
444517
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/exception/BaseExceptionBuiltins.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,9 @@ public boolean suppressContext(@SuppressWarnings("unused") PBaseException self)
201201
public abstract static class TracebackNode extends PythonBuiltinNode {
202202

203203
@Specialization(guards = "isNoValue(tb)")
204-
public Object getTraceback(PBaseException self, @SuppressWarnings("unused") Object tb) {
205-
PTraceback traceback = self.getTraceback();
204+
public Object getTraceback(VirtualFrame frame, PBaseException self, @SuppressWarnings("unused") Object tb,
205+
@Cached GetTracebackNode getTracebackNode) {
206+
PTraceback traceback = getTracebackNode.execute(frame, self);
206207
return traceback == null ? PNone.NONE : traceback;
207208
}
208209

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright (c) 2019, 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.builtins.objects.exception;
42+
43+
import com.oracle.graal.python.builtins.objects.frame.PFrame;
44+
import com.oracle.graal.python.builtins.objects.frame.PFrame.Reference;
45+
import com.oracle.graal.python.builtins.objects.function.PArguments;
46+
import com.oracle.graal.python.builtins.objects.traceback.PTraceback;
47+
import com.oracle.graal.python.nodes.frame.MaterializeFrameNode;
48+
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode;
49+
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
50+
import com.oracle.truffle.api.dsl.Cached;
51+
import com.oracle.truffle.api.dsl.Specialization;
52+
import com.oracle.truffle.api.frame.VirtualFrame;
53+
import com.oracle.truffle.api.nodes.Node;
54+
import com.oracle.truffle.api.profiles.ConditionProfile;
55+
56+
/**
57+
* Use this node to get the traceback object of an exception object. The traceback may need to be
58+
* created lazily and this node takes care of it.
59+
*/
60+
public abstract class GetTracebackNode extends Node {
61+
62+
public abstract PTraceback execute(VirtualFrame frame, PBaseException e);
63+
64+
@Specialization(guards = "!hasLazyTraceback(e)")
65+
static PTraceback doExisting(PBaseException e) {
66+
return e.getTraceback();
67+
}
68+
69+
// case 1: not on stack: there is already a PFrame (so the frame of this frame info is no
70+
// longer on the stack) and the frame has already been materialized
71+
@Specialization(guards = {"hasLazyTraceback(e)", "isMaterialized(e.getFrameInfo())"})
72+
static PTraceback doMaterializedFrame(PBaseException e,
73+
@Cached PythonObjectFactory factory) {
74+
Reference frameInfo = e.getFrameInfo();
75+
assert frameInfo.isEscaped() : "cannot create traceback for non-escaped frame";
76+
77+
PFrame escapedFrame = frameInfo.getPyFrame();
78+
assert escapedFrame != null;
79+
80+
PTraceback result = factory.createTraceback(escapedFrame, e.getException());
81+
e.setTraceback(result);
82+
return result;
83+
}
84+
85+
// case 2: on stack: the PFrame is not yet available so the frame must still be on the stack
86+
@Specialization(guards = {"hasLazyTraceback(e)", "!isMaterialized(e.getFrameInfo())"})
87+
PTraceback doOnStack(VirtualFrame frame, PBaseException e,
88+
@Cached PythonObjectFactory factory,
89+
@Cached MaterializeFrameNode materializeNode,
90+
@Cached ReadCallerFrameNode readCallerFrame,
91+
@Cached("createBinaryProfile()") ConditionProfile isCurFrameProfile) {
92+
Reference frameInfo = e.getFrameInfo();
93+
assert frameInfo.isEscaped() : "cannot create traceback for non-escaped frame";
94+
95+
PFrame escapedFrame = null;
96+
97+
// case 2.1: the frame info refers to the current frame
98+
if (isCurFrameProfile.profile(PArguments.getCurrentFrameInfo(frame) == frameInfo)) {
99+
// materialize the current frame; marking is not necessary (already done); refreshing
100+
// values is also not necessary (will be done on access to the locals or when returning
101+
// from the frame)
102+
escapedFrame = materializeNode.execute(frame, false);
103+
} else {
104+
// case 2.2: the frame info does not refer to the current frame
105+
for (int i = 0;; i++) {
106+
escapedFrame = readCallerFrame.executeWith(frame, i);
107+
if (escapedFrame == null || escapedFrame.getRef() == frameInfo) {
108+
break;
109+
}
110+
}
111+
}
112+
113+
PTraceback result = factory.createTraceback(escapedFrame, e.getException());
114+
e.setTraceback(result);
115+
return result;
116+
}
117+
118+
protected static boolean hasLazyTraceback(PBaseException e) {
119+
return e.getTraceback() == null && e.getFrameInfo() != null;
120+
}
121+
122+
protected static boolean isMaterialized(PFrame.Reference frameInfo) {
123+
return frameInfo.getPyFrame() != null;
124+
}
125+
}

0 commit comments

Comments
 (0)