Skip to content

Commit ea608d2

Browse files
committed
Fix ContinuationRootNodeImpl.findBytecodeIndex and extend stack trace tests
1 parent 9c1533b commit ea608d2

File tree

2 files changed

+255
-23
lines changed

2 files changed

+255
-23
lines changed

truffle/src/com.oracle.truffle.api.bytecode.test/src/com/oracle/truffle/api/bytecode/test/StackTraceTest.java

Lines changed: 241 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,28 @@
6363

6464
import com.oracle.truffle.api.CallTarget;
6565
import com.oracle.truffle.api.CompilerDirectives;
66+
import com.oracle.truffle.api.Truffle;
6667
import com.oracle.truffle.api.TruffleStackTrace;
6768
import com.oracle.truffle.api.TruffleStackTraceElement;
6869
import com.oracle.truffle.api.bytecode.BytecodeConfig;
70+
import com.oracle.truffle.api.bytecode.BytecodeLocal;
6971
import com.oracle.truffle.api.bytecode.BytecodeNode;
7072
import com.oracle.truffle.api.bytecode.BytecodeParser;
7173
import com.oracle.truffle.api.bytecode.BytecodeRootNode;
7274
import com.oracle.truffle.api.bytecode.BytecodeRootNodes;
7375
import com.oracle.truffle.api.bytecode.ConstantOperand;
76+
import com.oracle.truffle.api.bytecode.ContinuationResult;
7477
import com.oracle.truffle.api.bytecode.GenerateBytecode;
7578
import com.oracle.truffle.api.bytecode.GenerateBytecodeTestVariants;
7679
import com.oracle.truffle.api.bytecode.GenerateBytecodeTestVariants.Variant;
80+
import com.oracle.truffle.api.bytecode.Instruction;
7781
import com.oracle.truffle.api.bytecode.Operation;
7882
import com.oracle.truffle.api.dsl.Bind;
7983
import com.oracle.truffle.api.dsl.Specialization;
8084
import com.oracle.truffle.api.exception.AbstractTruffleException;
8185
import com.oracle.truffle.api.frame.FrameDescriptor;
86+
import com.oracle.truffle.api.frame.FrameInstance;
87+
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
8288
import com.oracle.truffle.api.interop.ArityException;
8389
import com.oracle.truffle.api.interop.InteropLibrary;
8490
import com.oracle.truffle.api.interop.TruffleObject;
@@ -166,10 +172,7 @@ public void testThrow() {
166172
Assert.fail();
167173
} catch (TestException e) {
168174
List<TruffleStackTraceElement> elements = TruffleStackTrace.getStackTrace(e);
169-
assertEquals(nodes.length, elements.size());
170-
for (int i = 0; i < nodes.length; i++) {
171-
assertStackElement(elements.get(i), nodes[i], false);
172-
}
175+
assertStackElements(nodes, elements, "c.ThrowError", "c.Call", false);
173176
}
174177
}
175178
}
@@ -192,10 +195,7 @@ public void testThrowBehindInterop() {
192195
Assert.fail();
193196
} catch (TestException e) {
194197
List<TruffleStackTraceElement> elements = TruffleStackTrace.getStackTrace(e);
195-
assertEquals(nodes.length, elements.size());
196-
for (int i = 0; i < nodes.length; i++) {
197-
assertStackElement(elements.get(i), nodes[i], false);
198-
}
198+
assertStackElements(nodes, elements, "c.ThrowErrorBehindInterop", "c.Call", false);
199199
}
200200
}
201201
}
@@ -216,10 +216,7 @@ public void testCapture() {
216216

217217
for (int repeat = 0; repeat < REPEATS; repeat++) {
218218
List<TruffleStackTraceElement> elements = (List<TruffleStackTraceElement>) outer.getCallTarget().call();
219-
assertEquals(nodes.length, elements.size());
220-
for (int i = 0; i < nodes.length; i++) {
221-
assertStackElement(elements.get(i), nodes[i], false);
222-
}
219+
assertStackElements(nodes, elements, "c.CaptureStack", "c.Call", false);
223220
}
224221
}
225222

@@ -244,15 +241,112 @@ public void testCaptureWithSources() {
244241

245242
for (int repeat = 0; repeat < REPEATS; repeat++) {
246243
List<TruffleStackTraceElement> elements = (List<TruffleStackTraceElement>) outer.getCallTarget().call();
247-
assertEquals(nodes.length, elements.size());
248-
for (int i = 0; i < nodes.length; i++) {
249-
assertStackElement(elements.get(i), nodes[i], true);
250-
}
244+
assertStackElements(nodes, elements, "c.CaptureStack", "c.Call", true);
251245
}
252246
}
253247

254-
private void assertStackElement(TruffleStackTraceElement element, StackTraceTestRootNode target, boolean checkSources) {
255-
assertSame(target.getCallTarget(), element.getTarget());
248+
@Test
249+
@SuppressWarnings("unchecked")
250+
public void testValidateFrameInstances() {
251+
int depth = run.depth;
252+
StackTraceTestRootNode[] nodesConstant = new StackTraceTestRootNode[run.depth];
253+
StackTraceTestRootNode[] nodes = chainCalls(depth, b -> {
254+
b.beginRoot();
255+
b.emitDummy();
256+
b.emitValidateFrameInstances(run, nodesConstant, "c.Call");
257+
b.endRoot();
258+
}, true, false);
259+
StackTraceTestRootNode outer = nodes[nodes.length - 1];
260+
System.arraycopy(nodes, 0, nodesConstant, 0, depth);
261+
262+
for (int repeat = 0; repeat < REPEATS; repeat++) {
263+
outer.getCallTarget().call();
264+
}
265+
}
266+
267+
@Test
268+
@SuppressWarnings("unchecked")
269+
public void testCaptureResumed() {
270+
int dept = run.depth;
271+
StackTraceTestRootNode[] nodes = chainResumes(dept, b -> {
272+
b.beginRoot();
273+
b.beginYield();
274+
b.emitLoadNull();
275+
b.endYield();
276+
b.beginReturn();
277+
b.emitCaptureStack();
278+
b.endReturn();
279+
b.endRoot();
280+
}, false);
281+
StackTraceTestRootNode outer = nodes[nodes.length - 1];
282+
283+
for (int repeat = 0; repeat < REPEATS; repeat++) {
284+
ContinuationResult outerCont = (ContinuationResult) outer.getCallTarget().call();
285+
List<TruffleStackTraceElement> elements = (List<TruffleStackTraceElement>) outerCont.continueWith(null);
286+
assertStackElements(nodes, elements, "c.CaptureStack", "c.Resume", false);
287+
}
288+
}
289+
290+
@Test
291+
@SuppressWarnings("unchecked")
292+
public void testCaptureResumedWithSources() {
293+
int dept = run.depth;
294+
Source s = Source.newBuilder(BytecodeDSLTestLanguage.ID, "root0", "root0.txt").build();
295+
StackTraceTestRootNode[] nodes = chainResumes(dept, b -> {
296+
b.beginSource(s);
297+
b.beginSourceSection(0, "root0".length());
298+
b.beginRoot();
299+
b.beginYield();
300+
b.emitLoadNull();
301+
b.endYield();
302+
b.beginReturn();
303+
b.emitCaptureStack();
304+
b.endReturn();
305+
b.endRoot();
306+
b.endSourceSection();
307+
b.endSource();
308+
}, true);
309+
StackTraceTestRootNode outer = nodes[nodes.length - 1];
310+
311+
for (int repeat = 0; repeat < REPEATS; repeat++) {
312+
ContinuationResult outerCont = (ContinuationResult) outer.getCallTarget().call();
313+
List<TruffleStackTraceElement> elements = (List<TruffleStackTraceElement>) outerCont.continueWith(null);
314+
assertStackElements(nodes, elements, "c.CaptureStack", "c.Resume", true);
315+
}
316+
}
317+
318+
@Test
319+
@SuppressWarnings("unchecked")
320+
public void testValidateResumedFrameInstances() {
321+
int depth = run.depth;
322+
StackTraceTestRootNode[] nodesConstant = new StackTraceTestRootNode[run.depth];
323+
StackTraceTestRootNode[] nodes = chainResumes(depth, b -> {
324+
b.beginRoot();
325+
b.beginYield();
326+
b.emitLoadNull();
327+
b.endYield();
328+
b.emitValidateFrameInstances(run, nodesConstant, "c.Resume");
329+
b.endRoot();
330+
}, false);
331+
StackTraceTestRootNode outer = nodes[nodes.length - 1];
332+
System.arraycopy(nodes, 0, nodesConstant, 0, depth);
333+
334+
for (int repeat = 0; repeat < REPEATS; repeat++) {
335+
ContinuationResult outerCont = (ContinuationResult) outer.getCallTarget().call();
336+
outerCont.continueWith(null);
337+
}
338+
}
339+
340+
private void assertStackElements(StackTraceTestRootNode[] nodes, List<TruffleStackTraceElement> elements, String firstElementInstruction, String chainedElementInstruction, boolean checkSources) {
341+
assertEquals(nodes.length, elements.size());
342+
assertStackElement(elements.get(0), nodes[0], firstElementInstruction, checkSources);
343+
for (int i = 1; i < nodes.length; i++) {
344+
assertStackElement(elements.get(i), nodes[i], chainedElementInstruction, checkSources);
345+
}
346+
}
347+
348+
private void assertStackElement(TruffleStackTraceElement element, StackTraceTestRootNode target, String instruction, boolean checkSources) {
349+
assertEquals(target, run.interpreter.variant.cast(element.getTarget()));
256350
assertNotNull(element.getLocation());
257351
BytecodeNode bytecode = target.getBytecodeNode();
258352
if (run.interpreter.cached) {
@@ -261,7 +355,7 @@ private void assertStackElement(TruffleStackTraceElement element, StackTraceTest
261355
} else {
262356
assertSame(bytecode, element.getLocation());
263357
}
264-
assertEquals(bytecode.getInstructionsAsList().get(1).getBytecodeIndex(), element.getBytecodeIndex());
358+
assertEquals(getBytecodeIndexOfFirstInstruction(bytecode, instruction), element.getBytecodeIndex());
265359

266360
Object interopObject = element.getGuestObject();
267361
InteropLibrary lib = InteropLibrary.getFactory().create(interopObject);
@@ -276,7 +370,16 @@ private void assertStackElement(TruffleStackTraceElement element, StackTraceTest
276370
} catch (UnsupportedMessageException ex) {
277371
fail("Interop object could not receive message: " + ex);
278372
}
373+
}
279374

375+
private static int getBytecodeIndexOfFirstInstruction(BytecodeNode bytecode, String instructionName) {
376+
for (Instruction i : bytecode.getInstructions()) {
377+
if (instructionName.equals(i.getName())) {
378+
return i.getBytecodeIndex();
379+
}
380+
}
381+
fail("No instruction found matching instruction name " + instructionName);
382+
return -1;
280383
}
281384

282385
@Test
@@ -345,15 +448,64 @@ private StackTraceTestRootNode[] chainCalls(int depth, BytecodeParser<StackTrace
345448
return nodes;
346449
}
347450

451+
private StackTraceTestRootNode[] chainResumes(int depth, BytecodeParser<StackTraceTestRootNodeBuilder> innerParser, boolean includeSources) {
452+
// Constructs a chain of methods that call the previous method, then yield, and then resume
453+
// the previous method.
454+
StackTraceTestRootNode[] nodes = new StackTraceTestRootNode[depth];
455+
nodes[0] = parse(innerParser);
456+
nodes[0].setName("root0");
457+
for (int i = 1; i < depth; i++) {
458+
int index = i;
459+
String name = "root" + i;
460+
Source s = includeSources ? Source.newBuilder(BytecodeDSLTestLanguage.ID, name, name + ".txt").build() : null;
461+
nodes[i] = parse(b -> {
462+
// x = prev_root()
463+
// yield
464+
// resume(x)
465+
if (includeSources) {
466+
b.beginSource(s);
467+
b.beginSourceSection(0, name.length());
468+
}
469+
b.beginRoot();
470+
BytecodeLocal calleeContinuation = b.createLocal();
471+
b.beginStoreLocal(calleeContinuation);
472+
b.emitCall(nodes[index - 1].getCallTarget());
473+
b.endStoreLocal();
474+
475+
b.beginYield();
476+
b.emitLoadConstant(index);
477+
b.endYield();
478+
479+
b.beginReturn();
480+
b.beginResume();
481+
b.emitLoadLocal(calleeContinuation);
482+
b.emitLoadNull();
483+
b.endResume();
484+
b.endReturn();
485+
b.endRoot().depth = index;
486+
if (includeSources) {
487+
b.endSourceSection();
488+
b.endSource();
489+
}
490+
});
491+
nodes[i].setName(name);
492+
}
493+
return nodes;
494+
}
495+
348496
@GenerateBytecodeTestVariants({
349497
@Variant(suffix = "CachedDefault", configuration = //
350-
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = false, boxingEliminationTypes = {int.class})),
498+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = false, enableYield = true, boxingEliminationTypes = {
499+
int.class})),
351500
@Variant(suffix = "UncachedDefault", configuration = //
352-
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = true, boxingEliminationTypes = {int.class})),
501+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = false, enableUncachedInterpreter = true, enableYield = true, boxingEliminationTypes = {
502+
int.class})),
353503
@Variant(suffix = "CachedBciInFrame", configuration = //
354-
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = false, boxingEliminationTypes = {int.class})),
504+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = false, enableYield = true, boxingEliminationTypes = {
505+
int.class})),
355506
@Variant(suffix = "UncachedBciInFrame", configuration = //
356-
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = true, boxingEliminationTypes = {int.class}))
507+
@GenerateBytecode(languageClass = BytecodeDSLTestLanguage.class, storeBytecodeIndexInFrame = true, enableUncachedInterpreter = true, enableYield = true, boxingEliminationTypes = {
508+
int.class}))
357509
})
358510
public abstract static class StackTraceTestRootNode extends DebugBytecodeRootNode implements BytecodeRootNode {
359511

@@ -405,6 +557,14 @@ static Object doDefault(CallTarget target) {
405557
}
406558
}
407559

560+
@Operation(storeBytecodeIndex = true)
561+
static final class Resume {
562+
@Specialization
563+
static Object doDefault(ContinuationResult cont, Object resumeValue, @Bind Node node) {
564+
return cont.getContinuationCallTarget().call(node, cont.getFrame(), resumeValue);
565+
}
566+
}
567+
408568
@Operation(storeBytecodeIndex = true)
409569
static final class ThrowErrorBehindInterop {
410570

@@ -445,6 +605,64 @@ static Object doDefault(@Bind Node node) {
445605
return TruffleStackTrace.getStackTrace(ex);
446606
}
447607
}
608+
609+
@SuppressWarnings("truffle-interpreted-performance")
610+
@Operation(storeBytecodeIndex = true)
611+
// The parser should populate this array after parsing. This is a hack to expose the roots.
612+
@ConstantOperand(type = Run.class, name = "run")
613+
@ConstantOperand(type = StackTraceTestRootNode[].class, name = "rootNodes")
614+
@ConstantOperand(type = String.class, name = "chainedInstruction")
615+
static final class ValidateFrameInstances {
616+
617+
@Specialization
618+
static void doDefault(Run run, StackTraceTestRootNode[] rootNodes, String chainedInstruction) {
619+
FrameInstanceValidator visitor = new FrameInstanceValidator(run, rootNodes, chainedInstruction);
620+
Truffle.getRuntime().iterateFrames(visitor);
621+
visitor.checkFinished();
622+
}
623+
}
624+
625+
static final class FrameInstanceValidator implements FrameInstanceVisitor<Void> {
626+
public final Run run;
627+
public final StackTraceTestRootNode[] targets;
628+
public final String chainedFrameInstruction;
629+
public int index = 0;
630+
631+
FrameInstanceValidator(Run run, StackTraceTestRootNode[] targets, String chainedFrameInstruction) {
632+
this.run = run;
633+
this.targets = targets;
634+
this.chainedFrameInstruction = chainedFrameInstruction;
635+
}
636+
637+
public Void visitFrame(FrameInstance frameInstance) {
638+
if (index >= targets.length) {
639+
fail("Visited too many frames.");
640+
}
641+
StackTraceTestRootNode target = targets[index];
642+
643+
assertEquals(target, run.interpreter.variant.cast(frameInstance.getCallTarget()));
644+
BytecodeNode bytecode = target.getBytecodeNode();
645+
if (index != 0) {
646+
assertNotNull(frameInstance.getCallNode());
647+
if (run.interpreter.cached) {
648+
assertSame(bytecode, BytecodeNode.get(frameInstance.getCallNode()));
649+
assertSame(bytecode, BytecodeNode.get(frameInstance));
650+
} else {
651+
assertSame(bytecode, frameInstance.getCallNode());
652+
}
653+
assertEquals(getBytecodeIndexOfFirstInstruction(bytecode, chainedFrameInstruction), frameInstance.getBytecodeIndex());
654+
}
655+
index++;
656+
return null;
657+
}
658+
659+
public void checkFinished() {
660+
if (index != targets.length) {
661+
fail("Did not visit every frame. Index is " + index + " and there are " + targets.length + " expected frames.");
662+
}
663+
}
664+
665+
}
448666
}
449667

450668
@ExportLibrary(InteropLibrary.class)

truffle/src/com.oracle.truffle.dsl.processor/src/com/oracle/truffle/dsl/processor/bytecode/generator/BytecodeRootNodeElement.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18981,6 +18981,7 @@ void lazyInit() {
1898118981
// RootNode overrides.
1898218982
this.add(createIsCloningAllowed());
1898318983
this.add(createIsCloneUninitializedSupported());
18984+
this.add(createFindBytecodeIndex());
1898418985
this.addOptional(createPrepareForCompilation());
1898518986
}
1898618987

@@ -19130,6 +19131,19 @@ private CodeExecutableElement createIsCloneUninitializedSupported() {
1913019131
return ex;
1913119132
}
1913219133

19134+
private CodeExecutableElement createFindBytecodeIndex() {
19135+
CodeExecutableElement ex = GeneratorUtils.override(types.RootNode, "findBytecodeIndex", new String[]{"node", "frame"});
19136+
CodeTreeBuilder b = ex.createBuilder();
19137+
b.startReturn().startCall("root", "findBytecodeIndex");
19138+
b.string("node");
19139+
// unwrap the frame from the continuation frame
19140+
b.startGroup().string("frame == null ? null : ");
19141+
b.startCall("findFrame").string("frame").end();
19142+
b.end();
19143+
b.end(2);
19144+
return ex;
19145+
}
19146+
1913319147
private CodeExecutableElement createPrepareForCompilation() {
1913419148
if (!model.enableUncachedInterpreter) {
1913519149
return null;

0 commit comments

Comments
 (0)