com.oracle.graal.python.runtime.ExecutionContext Maven / Gradle / Ivy
/*
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.graal.python.runtime;
import com.oracle.graal.python.PythonLanguage;
import com.oracle.graal.python.builtins.objects.frame.PFrame;
import com.oracle.graal.python.builtins.objects.frame.PFrame.Reference;
import com.oracle.graal.python.builtins.objects.function.PArguments;
import com.oracle.graal.python.nodes.IndirectCallNode;
import com.oracle.graal.python.nodes.PRootNode;
import com.oracle.graal.python.nodes.exception.TopLevelExceptionHandler;
import com.oracle.graal.python.nodes.frame.MaterializeFrameNode;
import com.oracle.graal.python.nodes.frame.MaterializeFrameNodeGen;
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode;
import com.oracle.graal.python.nodes.frame.ReadCallerFrameNode.FrameSelector;
import com.oracle.graal.python.nodes.util.ExceptionStateNodes.GetCaughtExceptionNode;
import com.oracle.graal.python.runtime.PythonContext.PythonThreadState;
import com.oracle.graal.python.runtime.exception.PException;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.ValueType;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.dsl.NeverDefault;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.ConditionProfile;
/**
* An ExecutionContext ensures proper entry and exit for Python calls on both sides of the call, and
* depending on whether the other side is also a Python frame.
*/
public abstract class ExecutionContext {
public static final class CallContext extends Node {
@CompilationFinal boolean neededCallerFrame;
@CompilationFinal boolean neededExceptionState;
private static final CallContext INSTANCE = new CallContext(false);
@Child private MaterializeFrameNode materializeNode;
private final boolean adoptable;
@CompilationFinal private ConditionProfile isPythonFrameProfile;
private CallContext(boolean adoptable) {
this.adoptable = adoptable;
this.neededExceptionState = !adoptable;
this.neededCallerFrame = !adoptable;
}
/**
* Prepare an indirect call from a Python frame to a Python function.
*/
public void prepareIndirectCall(VirtualFrame frame, Object[] callArguments, Node callNode) {
prepareCall(frame, callArguments, callNode, true, true);
}
/**
* Prepare a call from a Python frame to a Python function.
*/
public void prepareCall(VirtualFrame frame, Object[] callArguments, RootCallTarget callTarget, Node callNode) {
// n.b.: The class cast should always be correct, since this context
// must only be used when calling from Python to Python
PRootNode calleeRootNode = (PRootNode) callTarget.getRootNode();
prepareCall(frame, callArguments, callNode, calleeRootNode.needsCallerFrame(), calleeRootNode.needsExceptionState());
}
private void prepareCall(VirtualFrame frame, Object[] callArguments, Node callNode, boolean needsCallerFrame, boolean needsExceptionState) {
// equivalent to PyPy's ExecutionContext.enter `frame.f_backref =
// self.topframeref` we here pass the current top frame reference to
// the next frame. An optimization we do is to only pass the frame
// info if the caller requested it, otherwise they'll have to deopt
// and walk the stack up once.
if (needsCallerFrame) {
if (!neededCallerFrame) {
CompilerDirectives.transferToInterpreterAndInvalidate();
neededCallerFrame = true;
}
PFrame.Reference thisInfo;
if (isPythonFrame(frame, callNode)) {
thisInfo = PArguments.getCurrentFrameInfo(frame);
// We are handing the PFrame of the current frame to the caller, i.e., it does
// not 'escape' since it is still on the stack.Also, force synchronization of
// values
PFrame pyFrame = materialize(frame, callNode, false, true);
assert thisInfo.getPyFrame() == pyFrame;
assert pyFrame.getRef() == thisInfo;
} else {
thisInfo = PFrame.Reference.EMPTY;
}
thisInfo.setCallNode(callNode);
PArguments.setCallerFrameInfo(callArguments, thisInfo);
}
if (needsExceptionState) {
if (!neededExceptionState) {
CompilerDirectives.transferToInterpreterAndInvalidate();
neededExceptionState = true;
}
PException curExc;
if (isPythonFrame(frame, callNode)) {
curExc = PArguments.getException(frame);
if (curExc == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
PException fromStackWalk = GetCaughtExceptionNode.fullStackWalk();
curExc = fromStackWalk != null ? fromStackWalk : PException.NO_EXCEPTION;
// now, set in our args, such that we won't do this again
PArguments.setException(frame, curExc);
}
} else {
// If we're here, it can only be because some top-level call
// inside Python led us here
curExc = PException.NO_EXCEPTION;
}
PArguments.setException(callArguments, curExc);
}
}
private PFrame materialize(VirtualFrame frame, Node callNode, boolean markAsEscaped, boolean forceSync) {
if (adoptable) {
return ensureMaterializeNode().execute(frame, callNode, markAsEscaped, forceSync);
}
return MaterializeFrameNode.getUncached().execute(frame, callNode, markAsEscaped, forceSync);
}
private boolean isPythonFrame(VirtualFrame frame, Node callNode) {
if (isPythonFrameProfile == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
isPythonFrameProfile = ConditionProfile.create();
}
boolean result = isPythonFrameProfile.profile(PArguments.isPythonFrame(frame));
assert result || callNode.getRootNode() instanceof TopLevelExceptionHandler : "calling from non-Python or non-top-level frame";
return result;
}
private MaterializeFrameNode ensureMaterializeNode() {
if (materializeNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
materializeNode = insert(MaterializeFrameNodeGen.create());
}
return materializeNode;
}
@Override
public boolean isAdoptable() {
return adoptable;
}
@NeverDefault
public static CallContext create() {
return new CallContext(true);
}
public static CallContext getUncached() {
return INSTANCE;
}
}
public static final class CalleeContext extends Node {
@Child private MaterializeFrameNode materializeNode;
@CompilationFinal private boolean everEscaped = false;
@Override
public Node copy() {
return new CalleeContext();
}
/**
* Wrap the execution of a Python callee called from a Python frame.
*/
public void enter(VirtualFrame frame) {
// TODO: assert PythonLanguage.getContext().ownsGil() :
// PythonContext.dumpStackOnAssertionHelper("callee w/o GIL");
// tfel: Create our frame reference here and store it so that
// there's no reference to it from the caller side.
PFrame.Reference thisFrameRef = new PFrame.Reference(PArguments.getCallerFrameInfo(frame));
PArguments.setCurrentFrameInfo(frame, thisFrameRef);
}
public void exit(VirtualFrame frame, PRootNode node) {
/*
* equivalent to PyPy's ExecutionContext.leave. Note that got_exception in
* their code is handled automatically by the Truffle lazy exceptions, so here we only
* deal with explicitly escaped frames.
*/
PFrame.Reference info = PArguments.getCurrentFrameInfo(frame);
CompilerAsserts.partialEvaluationConstant(node);
if (node.getFrameEscapedProfile().profile(info.isEscaped())) {
exitEscaped(frame, node, info);
}
}
@InliningCutoff
private void exitEscaped(VirtualFrame frame, PRootNode node, Reference info) {
if (!everEscaped) {
CompilerDirectives.transferToInterpreterAndInvalidate();
everEscaped = true;
reportPolymorphicSpecialize();
}
// This assumption acts as our branch profile here
Reference callerInfo = PArguments.getCallerFrameInfo(frame);
if (callerInfo == null) {
// we didn't request the caller frame reference. now we need it.
CompilerDirectives.transferToInterpreter();
// n.b. We need to use 'ReadCallerFrameNode.getCallerFrame' instead of
// 'Truffle.getRuntime().getCallerFrame()' because we still need to skip
// non-Python frames, even if we do not skip frames of builtin functions.
Frame callerFrame = ReadCallerFrameNode.getCallerFrame(info, FrameInstance.FrameAccess.READ_ONLY, FrameSelector.ALL_PYTHON_FRAMES, 0);
if (PArguments.isPythonFrame(callerFrame)) {
callerInfo = PArguments.getCurrentFrameInfo(callerFrame);
} else {
// TODO: frames: an assertion should be that this is one of our
// entry point call nodes
callerInfo = Reference.EMPTY;
}
// ReadCallerFrameNode.getCallerFrame must have the assumption invalidated
assert node.needsCallerFrame() : "stack walk did not invalidate caller frame assumption";
}
// force the frame so that it can be accessed later
ensureMaterializeNode().execute(frame, node, false, true);
// if this frame escaped we must ensure that also f_back does
callerInfo.markAsEscaped();
info.setBackref(callerInfo);
}
private MaterializeFrameNode ensureMaterializeNode() {
if (materializeNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
materializeNode = insert(MaterializeFrameNodeGen.create());
}
return materializeNode;
}
public static CalleeContext create() {
return new CalleeContext();
}
}
@ValueType
private static final class IndirectCallState {
private final PFrame.Reference info;
private final PException curExc;
private IndirectCallState(PFrame.Reference info, PException curExc) {
this.info = info;
this.curExc = curExc;
}
}
public abstract static class IndirectCallContext {
/**
* Prepare a call from a Python frame to a callable without frame. This transfers the
* exception state from the frame to the context and also puts the current frame info (which
* represents the last Python caller) in the context.
*
* This is mostly useful when calling methods annotated with {@code @TruffleBoundary} that
* again use nodes that would require a frame. Use following pattern to call such methods
* and just pass a {@code null} frame.
*
*
*
* public abstract class SomeNode extends Node {
* {@literal @}Child private OtherNode otherNode = OtherNode.create();
*
* public abstract Object execute(VirtualFrame frame, Object arg);
*
* {@literal @}Specialization
* Object doSomething(VirtualFrame frame, Object arg) {
* // ...
* PException savedExceptionState = IndirectCallContext.enter(frame, PythonContext.get(this), this);
* try {
* truffleBoundaryMethod(arg);
* } finally {
* IndirectCallContext.exit(context, savedExceptionState);
* }
* // ...
* }
*
* {@literal @}TruffleBoundary
* private void truffleBoundaryMethod(Object arg) {
* otherNode.execute(null, arg);
* }
*
*
*
*/
public static Object enter(VirtualFrame frame, PythonLanguage language, PythonContext context, IndirectCallNode callNode) {
if (frame == null || callNode == null) {
return null;
}
boolean needsCallerFrame = callNode.calleeNeedsCallerFrame();
boolean needsExceptionState = callNode.calleeNeedsExceptionState();
if (!needsCallerFrame && !needsExceptionState) {
return null;
}
PythonThreadState pythonThreadState = context.getThreadState(language);
return enter(frame, pythonThreadState, needsCallerFrame, needsExceptionState, callNode);
}
public static Object enter(VirtualFrame frame, T indirectCallNode) {
if (frame == null || indirectCallNode == null) {
return null;
}
boolean needsCallerFrame = indirectCallNode.calleeNeedsCallerFrame();
boolean needsExceptionState = indirectCallNode.calleeNeedsExceptionState();
if (!needsCallerFrame && !needsExceptionState) {
return null;
}
PythonThreadState pythonThreadState = PythonContext.get(indirectCallNode).getThreadState(PythonLanguage.get(indirectCallNode));
return enter(frame, pythonThreadState, needsCallerFrame, needsExceptionState, indirectCallNode);
}
/**
* @see #enter(VirtualFrame, PythonLanguage, PythonContext, IndirectCallNode)
*/
public static Object enter(VirtualFrame frame, PythonThreadState pythonThreadState, IndirectCallNode callNode) {
if (frame == null || callNode == null) {
return null;
}
return enter(frame, pythonThreadState, callNode.calleeNeedsCallerFrame(), callNode.calleeNeedsExceptionState(), callNode);
}
private static IndirectCallState enter(VirtualFrame frame, PythonThreadState pythonThreadState, boolean needsCallerFrame, boolean needsExceptionState, IndirectCallNode callNode) {
PFrame.Reference info = null;
if (needsCallerFrame) {
PFrame.Reference prev = pythonThreadState.popTopFrameInfo();
assert prev == null : "trying to call from Python to a foreign function, but we didn't clear the topframeref. " +
"This indicates that a call into Python code happened without a proper enter through ForeignToPythonCallContext";
info = PArguments.getCurrentFrameInfo(frame);
info.setCallNode((Node) callNode);
pythonThreadState.setTopFrameInfo(info);
}
PException curExc = pythonThreadState.getCaughtException();
PException exceptionState = PArguments.getException(frame);
if (needsExceptionState) {
pythonThreadState.setCaughtException(exceptionState);
} else if (exceptionState != curExc) {
// the thread state has exception info inconsistent with the current frame's. we
// need to force lower frames to walk the stack
pythonThreadState.setCaughtException(null);
}
if (curExc == null && info == null) {
return null;
} else {
return new IndirectCallState(info, curExc);
}
}
/**
* Cleanup after a call without frame. For more details, see {@link #enter}.
*/
public static void exit(VirtualFrame frame, PythonLanguage language, PythonContext context, Object savedState) {
if (savedState != null && frame != null && context != null) {
exit(frame, context.getThreadState(language), savedState);
return;
}
assert savedState == null : "tried to exit an indirect call with state, but without frame/context";
}
public static void exit(VirtualFrame frame, T indirectCallNode, Object savedState) {
if (savedState != null && frame != null) {
PythonContext context = PythonContext.get(indirectCallNode);
if (context != null) {
PythonLanguage language = PythonLanguage.get(indirectCallNode);
exit(frame, context.getThreadState(language), savedState);
return;
}
}
assert savedState == null : "tried to exit an indirect call with state, but without frame/context";
}
/**
* @see #exit(VirtualFrame, PythonLanguage, PythonContext, Object)
*/
public static void exit(VirtualFrame frame, PythonThreadState pythonThreadState, Object savedState) {
if (frame == null) {
assert savedState == null : "tried to exit an indirect call with state, but without frame";
return;
}
if (savedState == null) {
return;
}
IndirectCallState state = (IndirectCallState) savedState;
if (state.info != null) {
pythonThreadState.popTopFrameInfo();
}
pythonThreadState.setCaughtException(state.curExc);
}
}
public abstract static class IndirectCalleeContext {
/**
* Prepare an indirect call from a foreign frame to a Python function.
*/
public static Object enterIndirect(PythonLanguage language, PythonContext context, Object[] pArguments) {
return enter(context.getThreadState(language), pArguments, true);
}
/**
* @see #enterIndirect(PythonLanguage, PythonContext, Object[])
*/
public static Object enterIndirect(PythonThreadState threadState, Object[] pArguments) {
return enter(threadState, pArguments, true);
}
/**
* @see #enter(PythonThreadState, Object[], RootCallTarget)
*/
public static Object enter(PythonLanguage language, PythonContext context, Object[] pArguments, RootCallTarget callTarget) {
PRootNode calleeRootNode = (PRootNode) callTarget.getRootNode();
return enter(context.getThreadState(language), pArguments, calleeRootNode.needsExceptionState());
}
/**
* Prepare a call from a foreign frame to a Python function.
*/
public static Object enter(PythonThreadState threadState, Object[] pArguments, RootCallTarget callTarget) {
PRootNode calleeRootNode = (PRootNode) callTarget.getRootNode();
return enter(threadState, pArguments, calleeRootNode.needsExceptionState());
}
private static Object enter(PythonThreadState threadState, Object[] pArguments, boolean needsExceptionState) {
Reference popTopFrameInfo = threadState.popTopFrameInfo();
PArguments.setCallerFrameInfo(pArguments, popTopFrameInfo);
if (needsExceptionState) {
PException curExc = threadState.getCaughtException();
if (curExc != null) {
threadState.setCaughtException(null);
}
PArguments.setException(pArguments, curExc);
return new IndirectCallState(popTopFrameInfo, curExc);
}
return popTopFrameInfo;
}
public static void exit(PythonLanguage language, PythonContext context, Object state) {
exit(context.getThreadState(language), state);
}
public static void exit(PythonThreadState threadState, Object state) {
/*
* Note that the Python callee, if it escaped, has already been materialized due to a
* CalleeContext in its RootNode. If this topframeref was marked as escaped, it'll be
* materialized at the latest needed time
*/
if (state instanceof IndirectCallState) {
IndirectCallState indirectCallState = (IndirectCallState) state;
threadState.setTopFrameInfo(indirectCallState.info);
threadState.setCaughtException(indirectCallState.curExc);
} else {
threadState.setTopFrameInfo((Reference) state);
}
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy