RubyCallStack.java

/*
 * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved. This
 * code is released under a tri EPL/GPL/LGPL license. You can use it,
 * redistribute it and/or modify it under the terms of the:
 *
 * Eclipse Public License version 1.0
 * GNU General Public License version 2
 * GNU Lesser General Public License version 2.1
 */
package org.jruby.truffle.runtime;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameInstanceVisitor;
import com.oracle.truffle.api.nodes.Node;
import org.jruby.truffle.nodes.CoreSourceSection;
import org.jruby.truffle.runtime.backtrace.Activation;
import org.jruby.truffle.runtime.backtrace.Backtrace;
import org.jruby.truffle.runtime.core.RubyModule;
import org.jruby.truffle.runtime.methods.MethodLike;
import org.jruby.truffle.runtime.methods.RubyMethod;
import org.jruby.util.Memo;
import org.jruby.util.cli.Options;

import java.util.ArrayList;

public abstract class RubyCallStack {

    /** Called "cref" in MRI. */
    public static RubyModule getCurrentDeclaringModule() {
        final FrameInstance currentFrame = Truffle.getRuntime().getCurrentFrame();
        final MethodLike method = getMethod(currentFrame);
        return method.getDeclaringModule();
    }

    public static RubyMethod getCurrentMethod() {
        CompilerAsserts.neverPartOfCompilation();

        final FrameInstance currentFrame = Truffle.getRuntime().getCurrentFrame();
        final MethodLike method = getMethod(currentFrame);

        if (method instanceof RubyMethod) {
            return (RubyMethod) method;
        }

        return Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<RubyMethod>() {

            @Override
            public RubyMethod visitFrame(FrameInstance frameInstance) {
                final MethodLike maybeMethod = getMethod(frameInstance);

                if (maybeMethod instanceof RubyMethod) {
                    return (RubyMethod) maybeMethod;
                } else {
                    return null;
                }
            }

        });
    }

    public static RubyMethod getCallingMethod() {
        CompilerAsserts.neverPartOfCompilation();

        final Memo<Boolean> seenCurrent = new Memo<Boolean>();

        MethodLike method;

        final FrameInstance currentFrame = Truffle.getRuntime().getCurrentFrame();

        method = getMethod(currentFrame);

        if (method instanceof RubyMethod) {
            seenCurrent.set(true);
        }

        return Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<RubyMethod>() {

            @Override
            public RubyMethod visitFrame(FrameInstance frameInstance) {
                final MethodLike maybeMethod = getMethod(frameInstance);

                if (maybeMethod instanceof RubyMethod) {
                    if (seenCurrent.get()) {
                        return (RubyMethod) maybeMethod;
                    } else {
                        seenCurrent.set(true);
                        return null;
                    }
                } else {
                    return null;
                }
            }

        });
    }

    public static MethodLike getMethod(FrameInstance frame) {
        CompilerAsserts.neverPartOfCompilation();

        if (frame == null) {
            return null;
        }

        return RubyArguments.getMethod(frame.getFrame(FrameInstance.FrameAccess.READ_ONLY, true).getArguments());
    }

    public static Backtrace getBacktrace(Node currentNode) {
        CompilerAsserts.neverPartOfCompilation();

        final ArrayList<Activation> activations = new ArrayList<>();

        if (Options.TRUFFLE_BACKTRACE_GENERATE.load()) {
            /*
             * TODO(cs): if this materializing the frames proves really expensive
             * we might want to make it optional - I think it's only used for some
             * features beyond what MRI does like printing locals in backtraces.
             */

            if (currentNode != null && Truffle.getRuntime().getCurrentFrame() != null) {
                activations.add(new Activation(currentNode, Truffle.getRuntime().getCurrentFrame().getFrame(FrameInstance.FrameAccess.MATERIALIZE, true).materialize()));
            }

            Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<RubyMethod>() {

                @Override
                public RubyMethod visitFrame(FrameInstance frameInstance) {
                    // Multiple top level methods (require) introduce null call nodes - ignore them
                    
                    if (frameInstance.getCallNode() != null) {
                        activations.add(new Activation(frameInstance.getCallNode(),
                                frameInstance.getFrame(FrameInstance.FrameAccess.MATERIALIZE, true).materialize()));
                    }

                    return null;
                }

            });

        }

        return new Backtrace(activations.toArray(new Activation[activations.size()]));
    }

    public static Node getTopMostUserCallNode() {
        CompilerAsserts.neverPartOfCompilation();

        return Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Node>() {

            @Override
            public Node visitFrame(FrameInstance frameInstance) {
                if (frameInstance.getCallNode().getEncapsulatingSourceSection() instanceof CoreSourceSection) {
                    return null;
                } else {
                    return frameInstance.getCallNode();
                }
            }

        });
    }

}