diff --git a/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py b/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py index 8107b4ffd0..64381542f8 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py +++ b/graalpython/com.oracle.graal.python.test/src/tests/test_frame_tests.py @@ -314,13 +314,21 @@ def target_inner(): frame = frames[worker_ident] if frame is None: return # we hit the timeout + + def format_frame(frame): + code = frame.f_code + return f"{code.co_name}@{os.path.basename(code.co_filename)}" + + seen_frames = [] while frame is not None and frame.f_code.co_name != "target_inner": + seen_frames.append(format_frame(frame)) frame = frame.f_back - assert frame is not None - assert frame.f_code.co_name == "target_inner", frame.f_code.co_name - assert "target_inner" in repr(frame) - assert "my_local_var" in frame.f_locals + message = f"test case: {test_case}; traversed frames: {', '.join(seen_frames) or ''}" + assert frame is not None, message + assert frame.f_code.co_name == "target_inner", f"{frame.f_code.co_name=}; {message}" + assert "target_inner" in repr(frame), f"{repr(frame)=}; {message}" + assert "my_local_var" in frame.f_locals, f"{frame.f_locals.keys()=}; {message}" frame = frame.f_back assert frame is not None diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java index 721966021d..71d80cccf8 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java @@ -935,8 +935,16 @@ private static EconomicMapStorage collectCurrentFrames(Node inliningTarget, Pyth Future future = context.getEnv().submitThreadLocal(threadArray, new ThreadLocalAction(true, false) { @Override protected void perform(Access access) { + /* + * Force-escape the frame while we are still running in the target thread. + * Otherwise, the TLA can capture a frame after that frame has already + * checked info.isEscaped() in CalleeContext.exitImpl. Merely marking the + * returned PFrame as escaped would then be too late: the frame can unwind + * without exitEscaped filling in callerInfo, and f_back would be unable to + * recover the caller by stack-walking from the already-unwound frame. + */ PFrame pyFrame = ReadFrameNode.readFrameInThreadLocal(access, null, FrameAccess.READ_ONLY, AllPythonFramesSelector.INSTANCE, 0, CallerFlags.NEEDS_PFRAME, - MaterializeFrameNode.getUncached()); + MaterializeFrameNode.getUncached(), true); frames.put(access.getThread(), escapedFrameOrPlaceholder(pyFrame)); } }); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java index d8acadaf56..24840959fc 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/frame/ReadFrameNode.java @@ -61,6 +61,7 @@ import com.oracle.graal.python.nodes.bytecode.PBytecodeRootNode; import com.oracle.graal.python.nodes.bytecode_dsl.PBytecodeDSLRootNode; import com.oracle.graal.python.runtime.CallerFlags; +import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext; import com.oracle.graal.python.runtime.GilNode; import com.oracle.graal.python.runtime.IndirectCallData; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; @@ -290,12 +291,20 @@ protected void perform(Access access) { public static PFrame readFrameInThreadLocal(Access access, Reference startFrameInfo, FrameAccess frameAccess, FrameSelector selector, int level, int callerFlags, MaterializeFrameNode materializeFrameNode) { + return readFrameInThreadLocal(access, startFrameInfo, frameAccess, selector, level, callerFlags, materializeFrameNode, false); + } + + public static PFrame readFrameInThreadLocal(Access access, Reference startFrameInfo, FrameAccess frameAccess, FrameSelector selector, int level, int callerFlags, + MaterializeFrameNode materializeFrameNode, boolean forceEscape) { Node location = access.getLocation(); if (location instanceof PBytecodeDSLRootNode) { // See AsyncPythonAction#execute for explanation location = PythonLanguage.get(null).unavailableSafepointLocation; } StackWalkResult result = ReadFrameNode.getFrame(location, startFrameInfo, frameAccess, selector, level, callerFlags); + if (forceEscape && result != null) { + CalleeContext.forceEscapeFrame(result.frame, getMaterializationLocation(result), PArguments.getCurrentFrameInfo(result.frame), materializeFrameNode); + } return processStackWalkResult(materializeFrameNode, callerFlags, result); } @@ -308,19 +317,24 @@ private static Thread getFrameThread(PFrame.Reference startFrameInfo, Thread fra private static PFrame processStackWalkResult(MaterializeFrameNode materializeFrameNode, int callerFlags, StackWalkResult callerFrameResult) { if (callerFrameResult != null) { - Node location = callerFrameResult.callNode; - if (!(callerFrameResult.rootNode instanceof PBytecodeDSLRootNode) && location == null) { - /* - * We can fixup the location like this only for other root nodes, for Bytecode DSL - * we need the BytecodeNode - */ - location = callerFrameResult.rootNode; - } + Node location = getMaterializationLocation(callerFrameResult); return materializeFrameNode.execute(location, false, CallerFlags.needsLocals(callerFlags), callerFrameResult.frame); } return null; } + private static Node getMaterializationLocation(StackWalkResult callerFrameResult) { + Node location = callerFrameResult.callNode; + if (!(callerFrameResult.rootNode instanceof PBytecodeDSLRootNode) && location == null) { + /* + * We can fixup the location like this only for other root nodes, for Bytecode DSL we + * need the BytecodeNode + */ + location = callerFrameResult.rootNode; + } + return location; + } + /** * Walk up the stack to find the currently top Python frame. This method is mostly useful for * code that cannot accept a {@code VirtualFrame} parameter (e.g. library code). It is necessary diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java index de97b4e0f9..4463b8b10b 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/ExecutionContext.java @@ -436,7 +436,10 @@ private void exitEscaped(VirtualFrame frame, PRootNode node, Node location, Refe CompilerDirectives.transferToInterpreterAndInvalidate(); everEscaped = true; } - // This assumption acts as our branch profile here + forceEscapeFrame(frame, location, info, ensureMaterializeNode()); + } + + public static void forceEscapeFrame(Frame frame, Node location, Reference info, MaterializeFrameNode materializeNode) { Reference callerInfo = info.getCallerInfo(); if (callerInfo == null) { // we didn't request the caller frame reference. now we need it. @@ -460,11 +463,7 @@ private void exitEscaped(VirtualFrame frame, PRootNode node, Node location, Refe // initializations (importing a module) from invalidating the assumption // force the frame so that it can be accessed later - if (PythonOptions.ENABLE_BYTECODE_DSL_INTERPRETER && node instanceof PBytecodeDSLRootNode) { - ensureMaterializeNode().executeOnStack(frame, (BytecodeNode) location, false, true); - } else { - ensureMaterializeNode().executeOnStack(frame, node, false, true); - } + materializeNode.execute(location, false, true, frame); // if this frame escaped we must ensure that also f_back does callerInfo.markAsEscaped(); info.setCallerInfo(callerInfo);