All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.oracle.truffle.api.test.StackTraceTest Maven / Gradle / Ivy

The newest version!
/*
 * 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() {
                    @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() {
                    public Object visitFrame(FrameInstance frameInstance) {
                        visitCount++;
                        return "foobar";
                    }
                });
                Assert.assertEquals(1, visitCount);
                Assert.assertEquals("foobar", result);

                visitCount = 0;
                result = Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() {
                    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> callables = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                callables.add(new Callable() {
                    @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 frames;
        final FrameInstance currentFrame;
        final FrameInstance callerFrame;

        StackTrace() {
            frames = new ArrayList<>();
            Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() {
                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);
        }

    }

}