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

com.oracle.truffle.tools.profiler.ShadowStack Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.tools.profiler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventNode;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.instrumentation.Instrumenter;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.nodes.Node;

/**
 * Custom more efficient stack representations for profilers.
 *
 * @since 0.30
 */
final class ShadowStack {

    private final ConcurrentHashMap stacks = new ConcurrentHashMap<>();
    private final int stackLimit;
    private final SourceSectionFilter sourceSectionFilter;
    private final Instrumenter initInstrumenter;
    private final TruffleLogger logger;

    ShadowStack(int stackLimit, SourceSectionFilter sourceSectionFilter, Instrumenter instrumenter, TruffleLogger logger) {
        this.stackLimit = stackLimit;
        this.sourceSectionFilter = sourceSectionFilter;
        this.initInstrumenter = instrumenter;
        this.logger = logger;
    }

    ThreadLocalStack getStack(Thread thread) {
        return stacks.get(thread);
    }

    Collection getStacks() {
        return stacks.values();
    }

    EventBinding install(Instrumenter instrumenter, SourceSectionFilter filter, boolean compiledOnly) {
        return instrumenter.attachExecutionEventFactory(filter, new ExecutionEventNodeFactory() {
            public ExecutionEventNode create(EventContext context) {
                Node instrumentedNode = context.getInstrumentedNode();
                if (instrumentedNode.getSourceSection() == null) {
                    logger.warning("Instrumented node " + instrumentedNode + " has null SourceSection.");
                    return null;
                }
                return new StackPushPopNode(ShadowStack.this, instrumenter, context, compiledOnly);
            }
        });
    }

    ArrayList getInitialStack(Node instrumentedNode) {
        ArrayList sourceLocations = new ArrayList<>();
        reconstructStack(sourceLocations, instrumentedNode, sourceSectionFilter, initInstrumenter);
        Truffle.getRuntime().iterateFrames(frame -> {
            Node node = frame.getCallNode();
            if (node != null) {
                reconstructStack(sourceLocations, node, sourceSectionFilter, initInstrumenter);
            }
            return null;
        });
        Collections.reverse(sourceLocations);
        return sourceLocations;
    }

    private static void reconstructStack(ArrayList sourceLocations, Node node, SourceSectionFilter sourceSectionFilter, Instrumenter instrumenter) {
        if (node == null || sourceSectionFilter == null) {
            return;
        }
        // We exclude the node itself as it will be pushed on the stack by the StackPushPopNode
        Node current = node.getParent();
        while (current != null) {
            if (sourceSectionFilter.includes(current) && current.getSourceSection() != null) {
                sourceLocations.add(new StackTraceEntry(instrumenter, current, 0, true));
            }
            current = current.getParent();
        }
    }

    private static class StackPushPopNode extends ExecutionEventNode {

        private final ShadowStack profilerStack;

        private final StackTraceEntry compilationRootLocation;
        private final StackTraceEntry compiledLocation;
        private final StackTraceEntry interpretedLocation;

        private final Thread cachedThread;
        private final ThreadLocalStack cachedStack;

        @CompilationFinal private boolean seenOtherThreads;
        @CompilationFinal final boolean isAttachedToRootTag;
        @CompilationFinal final boolean ignoreInlinedRoots;

        StackPushPopNode(ShadowStack profilerStack, Instrumenter instrumenter, EventContext context, boolean ignoreInlinedRoots) {
            this.profilerStack = profilerStack;
            this.cachedThread = Thread.currentThread();
            this.interpretedLocation = new StackTraceEntry(instrumenter, context, 0, true);
            this.compiledLocation = new StackTraceEntry(interpretedLocation, 2, false);
            this.compilationRootLocation = new StackTraceEntry(interpretedLocation, 2, true);
            this.isAttachedToRootTag = context.hasTag(StandardTags.RootTag.class);
            this.ignoreInlinedRoots = ignoreInlinedRoots;
            this.cachedStack = getStack();
        }

        @Override
        protected void onEnter(VirtualFrame frame) {
            if (CompilerDirectives.inCompiledCode() && ignoreInlinedRoots && isAttachedToRootTag && !CompilerDirectives.inCompilationRoot()) {
                return;
            }
            doOnEnter();
        }

        private void doOnEnter() {
            StackTraceEntry location = CompilerDirectives.inInterpreter() ? interpretedLocation : (CompilerDirectives.inCompilationRoot() ? compiledLocation : compilationRootLocation);
            if (seenOtherThreads) {
                pushSlow(location);
            } else if (cachedThread == Thread.currentThread()) {
                cachedStack.push(location);
            } else {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                seenOtherThreads = true;
                pushSlow(location);
            }
        }

        @TruffleBoundary
        private void pushSlow(StackTraceEntry location) {
            getStack().push(location);
        }

        @Override
        protected void onReturnExceptional(VirtualFrame frame, Throwable exception) {
            onReturnValue(frame, null);
        }

        @Override
        protected void onReturnValue(VirtualFrame frame, Object result) {
            if (ignoreInlinedRoots) {
                if (CompilerDirectives.inCompiledCode()) {
                    if (isAttachedToRootTag && !CompilerDirectives.inCompilationRoot()) {
                        return;
                    }
                } else {
                    // This is needed to control for the case that an invalidation happened in an
                    // inlined root.
                    // Than there should be no stack pop until we exit the original compilation
                    // root.
                    // Not needed if stack overflowed
                    final ThreadLocalStack stack = getStack();
                    if (!stack.hasStackOverflowed() &&
                                    stack.top().getInstrumentedNode() != interpretedLocation.getInstrumentedNode()) {

                        return;
                    }
                }
            }

            if (seenOtherThreads) {
                popSlow(compiledLocation);
            } else if (cachedThread == Thread.currentThread()) {
                cachedStack.pop(compiledLocation);
            } else {
                CompilerDirectives.transferToInterpreterAndInvalidate();
                seenOtherThreads = true;
                popSlow(compiledLocation);
            }
        }

        @TruffleBoundary
        private void popSlow(StackTraceEntry entry) {
            getStack().pop(entry);
        }

        @TruffleBoundary
        private ThreadLocalStack getStack() {
            Thread currentThread = Thread.currentThread();
            ThreadLocalStack stack = profilerStack.stacks.get(currentThread);
            if (stack == null) {
                stack = profilerStack.new ThreadLocalStack(currentThread);
                ThreadLocalStack prevStack = profilerStack.stacks.putIfAbsent(currentThread, stack);
                if (prevStack != null) {
                    stack = prevStack;
                }
            }
            return stack;
        }

    }

    final class ThreadLocalStack {

        /*
         * Window in which we look ahead and before the current stack index to find the potentially
         * changed top of stack index, after copying.
         */
        private static final int CORRECTION_WINDOW = 5;

        private final Thread thread;
        private @CompilationFinal(dimensions = 0) StackTraceEntry[] stack;

        private boolean stackOverflowed = false;
        private final Assumption noStackOverflowedAssumption = Truffle.getRuntime().createAssumption();

        private int stackIndex;
        @CompilationFinal private int initialStackLength;
        @CompilationFinal private Assumption initialStackLengthStable;

        ThreadLocalStack(Thread thread) {
            this.thread = thread;
            // The stack is uninitialized initially
            this.stackIndex = -1;
            this.initialStackLength = -1;
        }

        private void initStack(Node instrumentedNode) {
            ArrayList init = getInitialStack(instrumentedNode);
            this.initialStackLength = init.size();
            this.initialStackLengthStable = initialStackLength > 0 ? Truffle.getRuntime().createAssumption("initial stack length stable") : null;
            this.stack = init.toArray(new StackTraceEntry[stackLimit]);
            this.stackIndex = init.size() - 1;
        }

        void push(StackTraceEntry element) {
            if (noStackOverflowedAssumption.isValid()) {
                if (stack == null) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    initStack(element.getInstrumentedNode());
                }
                int index = stackIndex + 1;
                if (index < stack.length) {
                    assert index >= 0;
                    stack[index] = element;
                } else {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    noStackOverflowedAssumption.invalidate();
                    stackOverflowed = true;
                }
                stackIndex = index;
            }
        }

        void pop(StackTraceEntry location) {
            if (noStackOverflowedAssumption.isValid()) {
                if (stack == null) {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    initStack(location.getInstrumentedNode());
                    if (stackIndex < 0) {
                        // no initial stack elements
                        return;
                    }
                }
                int index = stackIndex;
                if (index >= 0 && index < stack.length) {
                    if (initialStackLength > 0 && index <= initialStackLength && initialStackLengthStable.isValid()) {
                        CompilerDirectives.transferToInterpreter();
                        /*
                         * The initial stack needs to be reconstructed on every pop that pops into
                         * the initial stack.
                         */
                        List reconstructedStack = getInitialStack(location.getInstrumentedNode());
                        for (int i = 0; i < reconstructedStack.size(); i++) {
                            stack[i] = reconstructedStack.get(i);
                        }
                        for (int i = reconstructedStack.size(); i < initialStackLength; i++) {
                            stack[i] = null;
                        }
                        stackIndex = reconstructedStack.size() - 1;
                        if (reconstructedStack.size() != initialStackLength) {
                            initialStackLengthStable.invalidate();
                            initialStackLength = reconstructedStack.size();
                            initialStackLengthStable = initialStackLength > 0 ? Truffle.getRuntime().createAssumption("initial stack length stable") : null;
                        }
                    } else {
                        stack[index] = null;
                        stackIndex = index - 1;
                    }
                } else {
                    CompilerDirectives.transferToInterpreterAndInvalidate();
                    noStackOverflowedAssumption.invalidate();
                    stackOverflowed = true;
                    stackIndex = index - 1;
                }

            }
        }

        StackTraceEntry top() {
            return stack[stackIndex];
        }

        StackTraceEntry[] getStack() {
            if (stack == null) {
                // Nothing yet
                return null;
            }
            StackTraceEntry[] localStack = stack;
            int localStackIndex = stackIndex;
            if (localStackIndex == -1) {
                // nothing on the stack
                return null;
            }

            int length = localStackIndex + 1;
            if (length > localStack.length) {
                // stack was out of stack limit
                length = localStack.length;
            }

            // make a quick copy to minimize retries
            localStack = Arrays.copyOf(localStack, Math.min(length + CORRECTION_WINDOW, localStack.length));

            for (int i = 0; i < localStack.length; i++) {
                // find the first null hole in the stack and use it as new corrected top of
                // stack index
                if (localStack[i] == null) {
                    length = i;
                    break;
                }
            }

            if (localStack.length != length) {
                localStack = Arrays.copyOf(localStack, length);
            }
            return localStack;
        }

        Thread getThread() {
            return thread;
        }

        int getStackIndex() {
            return stackIndex;
        }

        boolean hasStackOverflowed() {
            return stackOverflowed;
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy