Skip to content

Commit

Permalink
[GR-45863] Instrumentation intercepts yield/resume and debugger fixes…
Browse files Browse the repository at this point in the history
… stepping over await.

PullRequest: graal/15198
  • Loading branch information
entlicher committed Sep 19, 2023
2 parents 69fcc62 + 8691755 commit 9118437
Show file tree
Hide file tree
Showing 16 changed files with 1,400 additions and 182 deletions.
10 changes: 10 additions & 0 deletions truffle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

This changelog summarizes major changes between Truffle versions relevant to languages implementors building upon the Truffle framework. The main focus is on APIs exported by Truffle.

## Version 24.0.0

* GR-45863 Yield and resume events added to the instrumentation:
* `ExecutionEventListener.onYield()` and `ExecutionEventNode.onYield()` is invoked on a yield of the current thread
* `ExecutionEventListener.onResume()` and `ExecutionEventNode.onResume()` is invoked on a resume of the execution on the current thread after a yield
* `ProbeNode.onYield()` and `ProbeNode.onResume()`
* `GenerateWrapper` has new `yieldExceptions()` and `resumeMethodPrefix()` parameters to automatically call the new `onYield()`/`onResume()` methods from wrapper nodes.
* `RootNode.isSameFrame()` and `TruffleInstrument.Env.isSameFrame()` added to test if two frames are the same, to match the yielded and resumed execution.
* GR-45863 Adopted onYield() and onResume() instrumentation events in the debugger stepping logic.

## Version 23.1.0

* GR-45123 Added `GenerateInline#inlineByDefault` to force usage of inlined node variant even when the node has also a cached variant (`@GenerateCached(true)`).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright (c) 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.truffle.api.debug.test;

import com.oracle.truffle.api.debug.DebuggerSession;
import com.oracle.truffle.api.debug.SuspendedEvent;
import com.oracle.truffle.api.instrumentation.test.YieldResumeTest.YieldResumeLanguage;

import org.graalvm.polyglot.Source;

import org.junit.Assert;
import org.junit.Test;

public class DebugYieldResumeTest extends AbstractDebugTest {

@Test
public void testYieldResume() {
final Source sourceYield = Source.create(YieldResumeLanguage.ID, "yield");
final Source sourceStatement = Source.create(YieldResumeLanguage.ID, "statement");
final Source sourceResume = Source.create(YieldResumeLanguage.ID, "resume");
try (DebuggerSession session = startSession()) {
session.suspendNextExecution();
startEval(sourceYield);

expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("yield", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
startEval(sourceStatement);
expectDone();
startEval(sourceResume);
expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
}
}

@Test
public void testMultiYieldResume1() {
final Source sourceYield = Source.create(YieldResumeLanguage.ID, "yield");
final Source sourceStatement = Source.create(YieldResumeLanguage.ID, "statement");
final Source sourceResume = Source.create(YieldResumeLanguage.ID, "resume");
try (DebuggerSession session = startSession()) {
session.suspendNextExecution();
startEval(sourceYield);

expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("yield", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
startEval(sourceYield);
expectDone();
startEval(sourceStatement);
expectDone();
startEval(sourceResume);
expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
startEval(sourceResume);
expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
}
}

@Test
public void testMultiYieldResume2() {
final Source sourceYield = Source.create(YieldResumeLanguage.ID, "yield");
final Source sourceStatement = Source.create(YieldResumeLanguage.ID, "statement");
final Source sourceResume = Source.create(YieldResumeLanguage.ID, "resume");
try (DebuggerSession session = startSession()) {
startEval(sourceYield);
expectDone();
session.suspendNextExecution();

startEval(sourceYield);
expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("yield", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
startEval(sourceStatement);
expectDone();
startEval(sourceResume);
expectDone();
startEval(sourceStatement);
expectDone();
startEval(sourceResume);
expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
startEval(sourceStatement);
expectSuspended((SuspendedEvent event) -> {
Assert.assertEquals("statement", event.getSourceSection().getCharacters());
event.prepareStepOver(1);
});
expectDone();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2016, 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
Expand Down Expand Up @@ -1550,6 +1550,38 @@ protected void onReturnExceptional(VirtualFrame frame, Throwable exception) {
}
}

@Override
protected void onYield(VirtualFrame frame, Object value) {
if (stepping.get()) {
doYield(frame.materialize());
doStepAfter(frame.materialize(), value);
}
}

@TruffleBoundary
private void doYield(MaterializedFrame frame) {
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
if (steppingStrategy != null) {
steppingStrategy.setYieldBreak(frame, context.getInstrumentedSourceSection());
}
}

@Override
protected void onResume(VirtualFrame frame) {
if (stepping.get()) {
doYieldResume(frame.materialize());
doStepBefore(frame.materialize());
}
}

@TruffleBoundary
private void doYieldResume(MaterializedFrame frame) {
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
if (steppingStrategy != null) {
steppingStrategy.setYieldResume(context, frame);
}
}

@Override
protected void onInputValue(VirtualFrame frame, EventContext inputContext, int inputIndex, Object inputValue) {
if (stepping.get() && hasExpressionElement) {
Expand Down Expand Up @@ -1691,6 +1723,32 @@ private void doReturn(MaterializedFrame frame, Object result) {
}
}

@Override
protected void onYield(VirtualFrame frame, Object value) {
if (stepping.get()) {
doReturn(frame.materialize(), value);
}
}

@Override
protected void onResume(VirtualFrame frame) {
if (stepping.get()) {
doYieldResume(frame.materialize());
if (hasRootElement) {
super.onEnter(frame);
}
}
}

@TruffleBoundary
private void doYieldResume(MaterializedFrame frame) {
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
if (steppingStrategy != null) {
steppingStrategy.setYieldResume(context, frame);
steppingStrategy.notifyCallEntry();
}
}

@TruffleBoundary
private void doReturn() {
SteppingStrategy steppingStrategy = strategyMap.get(Thread.currentThread());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 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
Expand Down Expand Up @@ -40,8 +40,11 @@
*/
package com.oracle.truffle.api.debug;

import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;

/**
* Implementation of a strategy for a debugger <em>action</em> that allows execution to continue
Expand Down Expand Up @@ -85,6 +88,14 @@ boolean isStopAfterCall() {
return true;
}

@SuppressWarnings("unused")
void setYieldBreak(Frame frame, SourceSection section) {
}

@SuppressWarnings("unused")
void setYieldResume(EventContext context, Frame frame) {
}

boolean isCollectingInputValues() {
return false;
}
Expand Down Expand Up @@ -265,6 +276,7 @@ private static final class StepInto extends SteppingStrategy {
private final StepConfig stepConfig;
private int stackCounter;
private int unfinishedStepCount;
private Frame yieldFrame;

StepInto(DebuggerSession session, StepConfig stepConfig) {
this.session = session;
Expand Down Expand Up @@ -292,6 +304,23 @@ boolean isStopAfterCall() {
return stackCounter < 0;
}

@Override
void setYieldBreak(Frame frame, SourceSection section) {
if (stackCounter == 0 && this.yieldFrame == null) {
this.yieldFrame = frame;
}
}

@Override
void setYieldResume(EventContext context, Frame frame) {
if (this.yieldFrame != null) {
RootNode root = context.getInstrumentedNode().getRootNode();
if (session.getDebugger().getEnv().isSameFrame(root, frame, this.yieldFrame)) {
this.yieldFrame = null;
}
}
}

@Override
boolean isCollectingInputValues() {
return stepConfig.containsSourceElement(session, SourceElement.EXPRESSION);
Expand All @@ -304,8 +333,8 @@ boolean isActive(EventContext context, SuspendAnchor suspendAnchor) {

@Override
boolean step(DebuggerSession steppingSession, EventContext context, SuspendAnchor suspendAnchor) {
if (stepConfig.matches(session, context, suspendAnchor) ||
SuspendAnchor.AFTER == suspendAnchor && stackCounter < 0) {
if (yieldFrame == null && (stepConfig.matches(session, context, suspendAnchor) ||
SuspendAnchor.AFTER == suspendAnchor && stackCounter < 0)) {
stackCounter = 0;
if (--unfinishedStepCount <= 0) {
return true;
Expand Down Expand Up @@ -448,6 +477,8 @@ private static final class StepOver extends SteppingStrategy {
private int unfinishedStepCount;
private boolean activeFrame = true;
private boolean activeExpression = true;
private Frame yieldFrame;
private SourceSection yieldSection;

StepOver(DebuggerSession session, StepConfig stepConfig) {
this.session = session;
Expand Down Expand Up @@ -499,6 +530,24 @@ boolean isStopAfterCall() {
return stackCounter < 0;
}

@Override
void setYieldBreak(Frame frame, SourceSection section) {
if (stackCounter == 0 && this.yieldFrame == null) {
this.yieldFrame = frame;
this.yieldSection = section;
}
}

@Override
void setYieldResume(EventContext context, Frame frame) {
if (this.yieldFrame != null) {
RootNode root = context.getInstrumentedNode().getRootNode();
if (session.getDebugger().getEnv().isSameFrame(root, frame, this.yieldFrame)) {
this.yieldFrame = null;
}
}
}

@Override
boolean isCollectingInputValues() {
return stepConfig.containsSourceElement(session, SourceElement.EXPRESSION);
Expand All @@ -511,8 +560,16 @@ boolean isActive(EventContext context, SuspendAnchor suspendAnchor) {

@Override
boolean step(DebuggerSession steppingSession, EventContext context, SuspendAnchor suspendAnchor) {
if (stepConfig.matches(session, context, suspendAnchor) ||
SuspendAnchor.AFTER == suspendAnchor && (stackCounter < 0 || exprCounter < 0)) {
if (yieldFrame == null && (stepConfig.matches(session, context, suspendAnchor) ||
SuspendAnchor.AFTER == suspendAnchor && (stackCounter < 0 || exprCounter < 0))) {
if (yieldSection != null) {
if (yieldSection.equals(context.getInstrumentedSourceSection()) && suspendAnchor == SuspendAnchor.BEFORE) {
// Do not stop on the same location where yield was performed.
return false;
} else {
yieldSection = null;
}
}
stackCounter = 0;
exprCounter = context.hasTag(SourceElement.EXPRESSION.getTag()) && SuspendAnchor.BEFORE == suspendAnchor ? 0 : -1;
return --unfinishedStepCount <= 0;
Expand Down Expand Up @@ -636,6 +693,16 @@ boolean isStopAfterCall() {
return current.isStopAfterCall();
}

@Override
void setYieldBreak(Frame frame, SourceSection section) {
current.setYieldBreak(frame, section);
}

@Override
void setYieldResume(EventContext context, Frame frame) {
current.setYieldResume(context, frame);
}

@Override
boolean isActive(EventContext context, SuspendAnchor suspendAnchor) {
return current.isActive(context, suspendAnchor);
Expand Down
Loading

0 comments on commit 9118437

Please sign in to comment.