/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.junit.Assert;
import org.junit.Test;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstance.FrameAccess;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.IndirectCallNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
public class StackTraceTest {
@Test
public void testFirstFrameIsCurrentFrame() {
CallTarget callTarget = createCallTarget(new ReturnStackTraceNode());
StackTrace stack = (StackTrace) callTarget.call();
Assert.assertEquals(1, stack.frames.size());
assertFrameEquals(stack.currentFrame, stack.frames.get(0));
}
@Test
public void testNoStackTrace() {
StackTrace stack = new StackTrace();
Assert.assertNull(stack.callerFrame);
Assert.assertNull(stack.currentFrame);
Assert.assertEquals(0, stack.frames.size());
}
@Test
public void testSingleStackTrace() {
CallTarget callTarget = createCallTarget(new ReturnStackTraceNode());
StackTrace stack = (StackTrace) callTarget.call();
Assert.assertEquals(1, stack.frames.size());
Assert.assertSame(callTarget, stack.currentFrame.getCallTarget());
Assert.assertNull(stack.currentFrame.getCallNode());
assertInvariants(stack);
}
@Test
public void testDirectStackTrace() {
CallTarget createStackTrace = createCallTarget(new ReturnStackTraceNode());
CallTarget call = createCallTarget(new TestCallWithDirectTargetNode(createStackTrace));
StackTrace stack = (StackTrace) call.call();
assertInvariants(stack);
Assert.assertEquals(2, stack.frames.size());
Assert.assertSame(createStackTrace, stack.currentFrame.getCallTarget());
Assert.assertNull(stack.currentFrame.getCallNode());
Assert.assertSame(call, stack.callerFrame.getCallTarget());
Assert.assertSame(findCallNode(call), stack.callerFrame.getCallNode());
}
@Test
public void testIndirectStackTrace() {
CallTarget createStackTrace = createCallTarget(new ReturnStackTraceNode());
CallTarget call = createCallTarget(new TestCallWithIndirectTargetNode(createStackTrace));
StackTrace stack = (StackTrace) call.call();
Assert.assertEquals(2, stack.frames.size());
Assert.assertSame(createStackTrace, stack.currentFrame.getCallTarget());
Assert.assertNull(stack.currentFrame.getCallNode());
Assert.assertSame(call, stack.callerFrame.getCallTarget());
Assert.assertSame(findCallNode(call), stack.callerFrame.getCallNode());
assertInvariants(stack);
}
@Test
public void testCallTargetStackTrace() {
CallTarget createStackTrace = createCallTarget(new ReturnStackTraceNode());
CallTarget call = createCallTarget(new TestCallWithCallTargetNode(createStackTrace));
StackTrace stack = (StackTrace) call.call();
assertInvariants(stack);
Assert.assertEquals(2, stack.frames.size());
Assert.assertSame(createStackTrace, stack.currentFrame.getCallTarget());
Assert.assertNull(stack.currentFrame.getCallNode());
Assert.assertSame(call, stack.callerFrame.getCallTarget());
Assert.assertNull(stack.callerFrame.getCallNode());
}
@Test
public void testCombinedStackTrace() {
CallTarget createStackTrace = createCallTarget(new ReturnStackTraceNode());
CallTarget callTarget = createCallTarget(new TestCallWithCallTargetNode(createStackTrace));
CallTarget indirect = createCallTarget(new TestCallWithIndirectTargetNode(callTarget));
CallTarget direct = createCallTarget(new TestCallWithDirectTargetNode(indirect));
StackTrace stack = (StackTrace) direct.call();
assertInvariants(stack);
Assert.assertEquals(4, stack.frames.size());
Assert.assertSame(createStackTrace, stack.currentFrame.getCallTarget());
Assert.assertNull(stack.currentFrame.getCallNode());
Assert.assertSame(callTarget, stack.callerFrame.getCallTarget());
Assert.assertNull(stack.callerFrame.getCallNode());
Assert.assertSame(indirect, stack.frames.get(2).getCallTarget());
Assert.assertSame(findCallNode(indirect), stack.frames.get(2).getCallNode());
Assert.assertSame(direct, stack.frames.get(3).getCallTarget());
Assert.assertSame(findCallNode(direct), stack.frames.get(3).getCallNode());
}
@Test
public void testFrameAccess() {
CallTarget callTarget = createCallTarget(new TestCallWithCallTargetNode(null));
CallTarget indirect = createCallTarget(new TestCallWithIndirectTargetNode(callTarget));
CallTarget direct = createCallTarget(new TestCallWithDirectTargetNode(indirect));
CallTarget test = createCallTarget(new TestCallNode(null) {
@Override
Object execute(VirtualFrame frame) {
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Object>() {
@SuppressWarnings("deprecation")
public Object visitFrame(FrameInstance frameInstance) {
Assert.assertNull(frameInstance.getFrame(FrameAccess.NONE));
Frame readOnlyFrame = frameInstance.getFrame(FrameAccess.READ_ONLY);
FrameSlot slot = readOnlyFrame.getFrameDescriptor().findFrameSlot("demo");
Assert.assertEquals(42, readOnlyFrame.getValue(slot));
Frame readWriteFrame = frameInstance.getFrame(FrameAccess.READ_WRITE);
Assert.assertEquals(42, readWriteFrame.getValue(slot));
readWriteFrame.setObject(slot, 43);
Frame materializedFrame = frameInstance.getFrame(FrameAccess.MATERIALIZE);
Assert.assertEquals(43, materializedFrame.getValue(slot));
materializedFrame.setObject(slot, 44);
Assert.assertEquals(44, readOnlyFrame.getValue(slot));
Assert.assertEquals(44, readWriteFrame.getValue(slot));
return null;
}
});
return null;
}
});
findTestCallNode(callTarget).setNext(test);
direct.call();
}
@Test
public void testStackTraversal() {
CallTarget callTarget = createCallTarget(new TestCallWithCallTargetNode(null));
CallTarget indirect = createCallTarget(new TestCallWithIndirectTargetNode(callTarget));
CallTarget direct = createCallTarget(new TestCallWithDirectTargetNode(indirect));
CallTarget test = createCallTarget(new TestCallNode(null) {
int visitCount = 0;
@Override
Object execute(VirtualFrame frame) {
Object result = Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Object>() {
public Object visitFrame(FrameInstance frameInstance) {
visitCount++;
return "foobar";
}
});
Assert.assertEquals(1, visitCount);
Assert.assertEquals("foobar", result);
visitCount = 0;
result = Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Object>() {
public Object visitFrame(FrameInstance frameInstance) {
visitCount++;
if (visitCount == 2) {
return "foobar";
} else {
return null; // continue traversing
}
}
});
Assert.assertEquals(2, visitCount);
Assert.assertEquals("foobar", result);
return null;
}
});
findTestCallNode(callTarget).setNext(test);
direct.call();
}
@Test
public void testAsynchronousFrameAccess() throws InterruptedException, ExecutionException, TimeoutException {
final ExecutorService exec = Executors.newFixedThreadPool(50);
try {
List<Callable<Void>> callables = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
callables.add(new Callable<Void>() {
@Override
public Void call() {
final CallTarget createStackTrace = createCallTarget(new ReturnStackTraceNode());
final CallTarget callTarget = createCallTarget(new TestCallWithCallTargetNode(createStackTrace));
final CallTarget indirect = createCallTarget(new TestCallWithIndirectTargetNode(callTarget));
final CallTarget direct = createCallTarget(new TestCallWithDirectTargetNode(indirect));
for (int j = 0; j < 10; j++) {
StackTrace stack = (StackTrace) direct.call();
assertInvariants(stack);
Assert.assertEquals(4, stack.frames.size());
Assert.assertSame(createStackTrace, stack.currentFrame.getCallTarget());
Assert.assertNull(stack.currentFrame.getCallNode());
Assert.assertSame(callTarget, stack.callerFrame.getCallTarget());
Assert.assertNull(stack.callerFrame.getCallNode());
Assert.assertSame(indirect, stack.frames.get(2).getCallTarget());
Assert.assertSame(findCallNode(indirect), stack.frames.get(2).getCallNode());
Assert.assertSame(direct, stack.frames.get(3).getCallTarget());
Assert.assertSame(findCallNode(direct), stack.frames.get(3).getCallNode());
}
return null;
}
});
}
for (Future<?> future : exec.invokeAll(callables)) {
future.get(5000, TimeUnit.MILLISECONDS);
}
} finally {
exec.shutdown();
}
}
private static TestCallNode findTestCallNode(CallTarget target) {
return ((TestRootNode) ((RootCallTarget) target).getRootNode()).callNode;
}
private static Node findCallNode(CallTarget target) {
return findTestCallNode(target).getCallNode();
}
private static void assertInvariants(StackTrace stack) {
if (stack.frames.size() == 0) {
Assert.assertNull(stack.currentFrame);
} else {
Assert.assertNotNull(stack.currentFrame);
}
if (stack.frames.size() <= 1) {
Assert.assertNull(stack.callerFrame);
} else {
Assert.assertNotNull(stack.callerFrame);
}
for (int i = 0; i < stack.frames.size(); i++) {
FrameInstance frame = stack.frames.get(i);
if (i == 0) {
assertFrameEquals(stack.currentFrame, frame);
} else if (i == 1) {
assertFrameEquals(stack.callerFrame, frame);
}
Assert.assertNotNull(frame.getCallTarget());
Assert.assertNotNull(frame.toString()); // # does not crash
}
}
private static void assertFrameEquals(FrameInstance expected, FrameInstance other) {
Assert.assertEquals(expected.isVirtualFrame(), other.isVirtualFrame());
Assert.assertSame(expected.getCallNode(), other.getCallNode());
Assert.assertSame(expected.getCallTarget(), other.getCallTarget());
}
private static CallTarget createCallTarget(TestCallNode callNode) {
return Truffle.getRuntime().createCallTarget(new TestRootNode(callNode));
}
private static class TestCallWithCallTargetNode extends TestCallNode {
TestCallWithCallTargetNode(CallTarget next) {
super(next);
}
@Override
Object execute(VirtualFrame frame) {
return next.call();
}
}
private static class TestCallWithIndirectTargetNode extends TestCallNode {
@Child IndirectCallNode indirectCall = Truffle.getRuntime().createIndirectCallNode();
TestCallWithIndirectTargetNode(CallTarget next) {
super(next);
}
@Override
Object execute(VirtualFrame frame) {
return indirectCall.call(next, new Object[0]);
}
@Override
public Node getCallNode() {
return indirectCall;
}
}
private static class TestCallWithDirectTargetNode extends TestCallNode {
@Child DirectCallNode directCall;
TestCallWithDirectTargetNode(CallTarget next) {
super(next);
}
@Override
Object execute(VirtualFrame frame) {
if (directCall == null || directCall.getCallTarget() != next) {
CompilerDirectives.transferToInterpreterAndInvalidate();
directCall = insert(Truffle.getRuntime().createDirectCallNode(next));
}
return directCall.call(new Object[0]);
}
@Override
public Node getCallNode() {
return directCall;
}
}
private static class ReturnStackTraceNode extends TestCallNode {
ReturnStackTraceNode() {
super(null);
}
@Override
Object execute(VirtualFrame frame) {
return new StackTrace();
}
}
private static class StackTrace {
final List<FrameInstance> frames;
final FrameInstance currentFrame;
final FrameInstance callerFrame;
StackTrace() {
frames = new ArrayList<>();
Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Void>() {
public Void visitFrame(FrameInstance frameInstance) {
frames.add(frameInstance);
return null;
}
});
currentFrame = Truffle.getRuntime().getCurrentFrame();
callerFrame = Truffle.getRuntime().getCallerFrame();
}
}
private abstract static class TestCallNode extends Node {
protected CallTarget next;
TestCallNode(CallTarget next) {
this.next = next;
}
public void setNext(CallTarget next) {
this.next = next;
}
abstract Object execute(VirtualFrame frame);
public Node getCallNode() {
return null;
}
}
private static class TestRootNode extends RootNode {
@Child private TestCallNode callNode;
TestRootNode(TestCallNode callNode) {
super(null);
this.callNode = callNode;
getFrameDescriptor().addFrameSlot("demo");
}
@Override
public Object execute(VirtualFrame frame) {
frame.setObject(getFrameDescriptor().findFrameSlot("demo"), 42);
return callNode.execute(frame);
}
}
}