Skip to content

Commit 9118437

Browse files
committed
[GR-45863] Instrumentation intercepts yield/resume and debugger fixes stepping over await.
PullRequest: graal/15198
2 parents 69fcc62 + 8691755 commit 9118437

File tree

16 files changed

+1400
-182
lines changed

16 files changed

+1400
-182
lines changed

truffle/CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
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.
44

5+
## Version 24.0.0
6+
7+
* GR-45863 Yield and resume events added to the instrumentation:
8+
* `ExecutionEventListener.onYield()` and `ExecutionEventNode.onYield()` is invoked on a yield of the current thread
9+
* `ExecutionEventListener.onResume()` and `ExecutionEventNode.onResume()` is invoked on a resume of the execution on the current thread after a yield
10+
* `ProbeNode.onYield()` and `ProbeNode.onResume()`
11+
* `GenerateWrapper` has new `yieldExceptions()` and `resumeMethodPrefix()` parameters to automatically call the new `onYield()`/`onResume()` methods from wrapper nodes.
12+
* `RootNode.isSameFrame()` and `TruffleInstrument.Env.isSameFrame()` added to test if two frames are the same, to match the yielded and resumed execution.
13+
* GR-45863 Adopted onYield() and onResume() instrumentation events in the debugger stepping logic.
14+
515
## Version 23.1.0
616

717
* GR-45123 Added `GenerateInline#inlineByDefault` to force usage of inlined node variant even when the node has also a cached variant (`@GenerateCached(true)`).
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright (c) 2023, 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.truffle.api.debug.test;
42+
43+
import com.oracle.truffle.api.debug.DebuggerSession;
44+
import com.oracle.truffle.api.debug.SuspendedEvent;
45+
import com.oracle.truffle.api.instrumentation.test.YieldResumeTest.YieldResumeLanguage;
46+
47+
import org.graalvm.polyglot.Source;
48+
49+
import org.junit.Assert;
50+
import org.junit.Test;
51+
52+
public class DebugYieldResumeTest extends AbstractDebugTest {
53+
54+
@Test
55+
public void testYieldResume() {
56+
final Source sourceYield = Source.create(YieldResumeLanguage.ID, "yield");
57+
final Source sourceStatement = Source.create(YieldResumeLanguage.ID, "statement");
58+
final Source sourceResume = Source.create(YieldResumeLanguage.ID, "resume");
59+
try (DebuggerSession session = startSession()) {
60+
session.suspendNextExecution();
61+
startEval(sourceYield);
62+
63+
expectSuspended((SuspendedEvent event) -> {
64+
Assert.assertEquals("yield", event.getSourceSection().getCharacters());
65+
event.prepareStepOver(1);
66+
});
67+
expectDone();
68+
startEval(sourceStatement);
69+
expectDone();
70+
startEval(sourceResume);
71+
expectSuspended((SuspendedEvent event) -> {
72+
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
73+
event.prepareStepOver(1);
74+
});
75+
expectDone();
76+
}
77+
}
78+
79+
@Test
80+
public void testMultiYieldResume1() {
81+
final Source sourceYield = Source.create(YieldResumeLanguage.ID, "yield");
82+
final Source sourceStatement = Source.create(YieldResumeLanguage.ID, "statement");
83+
final Source sourceResume = Source.create(YieldResumeLanguage.ID, "resume");
84+
try (DebuggerSession session = startSession()) {
85+
session.suspendNextExecution();
86+
startEval(sourceYield);
87+
88+
expectSuspended((SuspendedEvent event) -> {
89+
Assert.assertEquals("yield", event.getSourceSection().getCharacters());
90+
event.prepareStepOver(1);
91+
});
92+
expectDone();
93+
startEval(sourceYield);
94+
expectDone();
95+
startEval(sourceStatement);
96+
expectDone();
97+
startEval(sourceResume);
98+
expectSuspended((SuspendedEvent event) -> {
99+
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
100+
event.prepareStepOver(1);
101+
});
102+
expectDone();
103+
startEval(sourceResume);
104+
expectSuspended((SuspendedEvent event) -> {
105+
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
106+
event.prepareStepOver(1);
107+
});
108+
expectDone();
109+
}
110+
}
111+
112+
@Test
113+
public void testMultiYieldResume2() {
114+
final Source sourceYield = Source.create(YieldResumeLanguage.ID, "yield");
115+
final Source sourceStatement = Source.create(YieldResumeLanguage.ID, "statement");
116+
final Source sourceResume = Source.create(YieldResumeLanguage.ID, "resume");
117+
try (DebuggerSession session = startSession()) {
118+
startEval(sourceYield);
119+
expectDone();
120+
session.suspendNextExecution();
121+
122+
startEval(sourceYield);
123+
expectSuspended((SuspendedEvent event) -> {
124+
Assert.assertEquals("yield", event.getSourceSection().getCharacters());
125+
event.prepareStepOver(1);
126+
});
127+
expectDone();
128+
startEval(sourceStatement);
129+
expectDone();
130+
startEval(sourceResume);
131+
expectDone();
132+
startEval(sourceStatement);
133+
expectDone();
134+
startEval(sourceResume);
135+
expectSuspended((SuspendedEvent event) -> {
136+
Assert.assertEquals("resume", event.getSourceSection().getCharacters());
137+
event.prepareStepOver(1);
138+
});
139+
expectDone();
140+
startEval(sourceStatement);
141+
expectSuspended((SuspendedEvent event) -> {
142+
Assert.assertEquals("statement", event.getSourceSection().getCharacters());
143+
event.prepareStepOver(1);
144+
});
145+
expectDone();
146+
}
147+
}
148+
}

truffle/src/com.oracle.truffle.api.debug/src/com/oracle/truffle/api/debug/DebuggerSession.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2022, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2023, 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
@@ -1550,6 +1550,38 @@ protected void onReturnExceptional(VirtualFrame frame, Throwable exception) {
15501550
}
15511551
}
15521552

1553+
@Override
1554+
protected void onYield(VirtualFrame frame, Object value) {
1555+
if (stepping.get()) {
1556+
doYield(frame.materialize());
1557+
doStepAfter(frame.materialize(), value);
1558+
}
1559+
}
1560+
1561+
@TruffleBoundary
1562+
private void doYield(MaterializedFrame frame) {
1563+
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
1564+
if (steppingStrategy != null) {
1565+
steppingStrategy.setYieldBreak(frame, context.getInstrumentedSourceSection());
1566+
}
1567+
}
1568+
1569+
@Override
1570+
protected void onResume(VirtualFrame frame) {
1571+
if (stepping.get()) {
1572+
doYieldResume(frame.materialize());
1573+
doStepBefore(frame.materialize());
1574+
}
1575+
}
1576+
1577+
@TruffleBoundary
1578+
private void doYieldResume(MaterializedFrame frame) {
1579+
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
1580+
if (steppingStrategy != null) {
1581+
steppingStrategy.setYieldResume(context, frame);
1582+
}
1583+
}
1584+
15531585
@Override
15541586
protected void onInputValue(VirtualFrame frame, EventContext inputContext, int inputIndex, Object inputValue) {
15551587
if (stepping.get() && hasExpressionElement) {
@@ -1691,6 +1723,32 @@ private void doReturn(MaterializedFrame frame, Object result) {
16911723
}
16921724
}
16931725

1726+
@Override
1727+
protected void onYield(VirtualFrame frame, Object value) {
1728+
if (stepping.get()) {
1729+
doReturn(frame.materialize(), value);
1730+
}
1731+
}
1732+
1733+
@Override
1734+
protected void onResume(VirtualFrame frame) {
1735+
if (stepping.get()) {
1736+
doYieldResume(frame.materialize());
1737+
if (hasRootElement) {
1738+
super.onEnter(frame);
1739+
}
1740+
}
1741+
}
1742+
1743+
@TruffleBoundary
1744+
private void doYieldResume(MaterializedFrame frame) {
1745+
SteppingStrategy steppingStrategy = getSteppingStrategy(Thread.currentThread());
1746+
if (steppingStrategy != null) {
1747+
steppingStrategy.setYieldResume(context, frame);
1748+
steppingStrategy.notifyCallEntry();
1749+
}
1750+
}
1751+
16941752
@TruffleBoundary
16951753
private void doReturn() {
16961754
SteppingStrategy steppingStrategy = strategyMap.get(Thread.currentThread());

truffle/src/com.oracle.truffle.api.debug/src/com/oracle/truffle/api/debug/SteppingStrategy.java

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2023, 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
@@ -40,8 +40,11 @@
4040
*/
4141
package com.oracle.truffle.api.debug;
4242

43+
import com.oracle.truffle.api.frame.Frame;
4344
import com.oracle.truffle.api.instrumentation.EventContext;
4445
import com.oracle.truffle.api.instrumentation.ProbeNode;
46+
import com.oracle.truffle.api.nodes.RootNode;
47+
import com.oracle.truffle.api.source.SourceSection;
4548

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

91+
@SuppressWarnings("unused")
92+
void setYieldBreak(Frame frame, SourceSection section) {
93+
}
94+
95+
@SuppressWarnings("unused")
96+
void setYieldResume(EventContext context, Frame frame) {
97+
}
98+
8899
boolean isCollectingInputValues() {
89100
return false;
90101
}
@@ -265,6 +276,7 @@ private static final class StepInto extends SteppingStrategy {
265276
private final StepConfig stepConfig;
266277
private int stackCounter;
267278
private int unfinishedStepCount;
279+
private Frame yieldFrame;
268280

269281
StepInto(DebuggerSession session, StepConfig stepConfig) {
270282
this.session = session;
@@ -292,6 +304,23 @@ boolean isStopAfterCall() {
292304
return stackCounter < 0;
293305
}
294306

307+
@Override
308+
void setYieldBreak(Frame frame, SourceSection section) {
309+
if (stackCounter == 0 && this.yieldFrame == null) {
310+
this.yieldFrame = frame;
311+
}
312+
}
313+
314+
@Override
315+
void setYieldResume(EventContext context, Frame frame) {
316+
if (this.yieldFrame != null) {
317+
RootNode root = context.getInstrumentedNode().getRootNode();
318+
if (session.getDebugger().getEnv().isSameFrame(root, frame, this.yieldFrame)) {
319+
this.yieldFrame = null;
320+
}
321+
}
322+
}
323+
295324
@Override
296325
boolean isCollectingInputValues() {
297326
return stepConfig.containsSourceElement(session, SourceElement.EXPRESSION);
@@ -304,8 +333,8 @@ boolean isActive(EventContext context, SuspendAnchor suspendAnchor) {
304333

305334
@Override
306335
boolean step(DebuggerSession steppingSession, EventContext context, SuspendAnchor suspendAnchor) {
307-
if (stepConfig.matches(session, context, suspendAnchor) ||
308-
SuspendAnchor.AFTER == suspendAnchor && stackCounter < 0) {
336+
if (yieldFrame == null && (stepConfig.matches(session, context, suspendAnchor) ||
337+
SuspendAnchor.AFTER == suspendAnchor && stackCounter < 0)) {
309338
stackCounter = 0;
310339
if (--unfinishedStepCount <= 0) {
311340
return true;
@@ -448,6 +477,8 @@ private static final class StepOver extends SteppingStrategy {
448477
private int unfinishedStepCount;
449478
private boolean activeFrame = true;
450479
private boolean activeExpression = true;
480+
private Frame yieldFrame;
481+
private SourceSection yieldSection;
451482

452483
StepOver(DebuggerSession session, StepConfig stepConfig) {
453484
this.session = session;
@@ -499,6 +530,24 @@ boolean isStopAfterCall() {
499530
return stackCounter < 0;
500531
}
501532

533+
@Override
534+
void setYieldBreak(Frame frame, SourceSection section) {
535+
if (stackCounter == 0 && this.yieldFrame == null) {
536+
this.yieldFrame = frame;
537+
this.yieldSection = section;
538+
}
539+
}
540+
541+
@Override
542+
void setYieldResume(EventContext context, Frame frame) {
543+
if (this.yieldFrame != null) {
544+
RootNode root = context.getInstrumentedNode().getRootNode();
545+
if (session.getDebugger().getEnv().isSameFrame(root, frame, this.yieldFrame)) {
546+
this.yieldFrame = null;
547+
}
548+
}
549+
}
550+
502551
@Override
503552
boolean isCollectingInputValues() {
504553
return stepConfig.containsSourceElement(session, SourceElement.EXPRESSION);
@@ -511,8 +560,16 @@ boolean isActive(EventContext context, SuspendAnchor suspendAnchor) {
511560

512561
@Override
513562
boolean step(DebuggerSession steppingSession, EventContext context, SuspendAnchor suspendAnchor) {
514-
if (stepConfig.matches(session, context, suspendAnchor) ||
515-
SuspendAnchor.AFTER == suspendAnchor && (stackCounter < 0 || exprCounter < 0)) {
563+
if (yieldFrame == null && (stepConfig.matches(session, context, suspendAnchor) ||
564+
SuspendAnchor.AFTER == suspendAnchor && (stackCounter < 0 || exprCounter < 0))) {
565+
if (yieldSection != null) {
566+
if (yieldSection.equals(context.getInstrumentedSourceSection()) && suspendAnchor == SuspendAnchor.BEFORE) {
567+
// Do not stop on the same location where yield was performed.
568+
return false;
569+
} else {
570+
yieldSection = null;
571+
}
572+
}
516573
stackCounter = 0;
517574
exprCounter = context.hasTag(SourceElement.EXPRESSION.getTag()) && SuspendAnchor.BEFORE == suspendAnchor ? 0 : -1;
518575
return --unfinishedStepCount <= 0;
@@ -636,6 +693,16 @@ boolean isStopAfterCall() {
636693
return current.isStopAfterCall();
637694
}
638695

696+
@Override
697+
void setYieldBreak(Frame frame, SourceSection section) {
698+
current.setYieldBreak(frame, section);
699+
}
700+
701+
@Override
702+
void setYieldResume(EventContext context, Frame frame) {
703+
current.setYieldResume(context, frame);
704+
}
705+
639706
@Override
640707
boolean isActive(EventContext context, SuspendAnchor suspendAnchor) {
641708
return current.isActive(context, suspendAnchor);

0 commit comments

Comments
 (0)