view graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeNode.java @ 14094:3f27e57439ed

Truffle/Instrumentation: significant rearrangement (including moved class) and extension of the Truffle Instrumentation Framework. New interfaces include DebugContext (which can be attached to the ExecutionContext), through which access is provided to possibly language-specific (a) node instrumentation, (b) debug services manager, (c) notification when programs halt, (d) display of language values, and (e) display of variable identifiers.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Mon, 03 Feb 2014 20:58:23 -0800
parents 8d99131c8940
children a124cc76cde9
line wrap: on
line source

/*
 * Copyright (c) 2013, 2014, 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.api.nodes.instrument;

import java.util.*;

import com.oracle.truffle.api.*;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.nodes.*;

/**
 * A <strong>probe</strong>: a Truffle instrumentation node that holds code to perform some action
 * when notified (via a {@linkplain InstrumentationProxyNode proxy node} in the AST) of a
 * {@linkplain InstrumentationProbeEvents probe event} taking place at the AST node.
 * <p>
 * Probes are only active when attached to a {@linkplain ProbeChain "probe chain"} that is referred
 * to by one or more {@linkplain InstrumentationProxyNode proxy nodes} in an AST.
 */
public abstract class InstrumentationProbeNode extends Node implements InstrumentationNode, InstrumentationProbeEvents {

    /**
     * Next in chain.
     */
    @Child protected InstrumentationProbeNode next;

    protected InstrumentationProbeNode() {
    }

    protected int countProbes() {
        return next == null ? 0 : next.countProbes() + 1;
    }

    protected boolean isStepping() {
        final InstrumentationProbeNode parent = (InstrumentationProbeNode) getParent();
        return parent.isStepping();
    }

    /**
     * Add a probe to the end of this probe chain.
     */
    protected void internalAppendProbe(InstrumentationProbeNode newProbeNode) {
        if (next == null) {
            this.next = adoptChild(newProbeNode);
        } else {
            next.internalAppendProbe(newProbeNode);
        }
    }

    protected void internalRemoveProbe(InstrumentationProbeNode oldProbeNode) {
        if (next == null) {
            throw new RuntimeException("Couldn't find probe to remove: " + oldProbeNode);
        } else if (next == oldProbeNode) {
            if (oldProbeNode.next == null) {
                this.next = null;
            } else {
                this.next = adoptChild(oldProbeNode.next);
                oldProbeNode.next = null;
            }
        } else {
            next.internalRemoveProbe(oldProbeNode);
        }
    }

    /**
     * Passes up the chain notification that a probe has changed its execution state in a way that
     * invalidates fast path code. Assumes that there is an instance of {@link ProbeChain} at the
     * head of the chain.
     */
    @CompilerDirectives.SlowPath
    protected void notifyProbeChanged(InstrumentationProbeNode probeNode) {
        final InstrumentationProbeNode parent = (InstrumentationProbeNode) getParent();
        parent.notifyProbeChanged(probeNode);
    }

    // TODO (mlvdv) making the internal*() methods public is a workaround for a bug/limitation in
    // the Truffle compiler; they are intended to be private.

    public void internalEnter(Node astNode, VirtualFrame frame) {
        enter(astNode, frame);
        if (next != null) {
            next.internalEnter(astNode, frame);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame) {
        leave(astNode, frame);
        if (next != null) {
            next.internalLeave(astNode, frame);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, boolean result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, byte result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, short result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, int result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, long result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, char result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, float result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, double result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeave(Node astNode, VirtualFrame frame, Object result) {
        leave(astNode, frame, result);
        if (next != null) {
            next.internalLeave(astNode, frame, result);
        }
    }

    public void internalLeaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
        leaveExceptional(astNode, frame, null);
        if (next != null) {
            next.internalLeaveExceptional(astNode, frame, e);
        }
    }

    public void internalReplace(Node oldAstNode, Node newAstNode, String reason) {
        replace(oldAstNode, newAstNode, reason);
        if (next != null) {
            next.internalReplace(oldAstNode, newAstNode, reason);
        }
    }

    /**
     * A probe implementation that implements all of {@link InstrumentationProbeEvents} with empty
     * methods; concrete subclasses can override only the methods for which something is to be done.
     */
    public static class DefaultProbeNode extends InstrumentationProbeNode {

        private final ExecutionContext executionContext;

        protected DefaultProbeNode(ExecutionContext context) {
            this.executionContext = context;
        }

        public ExecutionContext getContext() {
            return executionContext;
        }

        public void enter(Node astNode, VirtualFrame frame) {
        }

        public void leave(Node astNode, VirtualFrame frame) {
        }

        public void leave(Node astNode, VirtualFrame frame, boolean result) {
        }

        public void leave(Node astNode, VirtualFrame frame, byte result) {
        }

        public void leave(Node astNode, VirtualFrame frame, short result) {
        }

        public void leave(Node astNode, VirtualFrame frame, int result) {
        }

        public void leave(Node astNode, VirtualFrame frame, long result) {
        }

        public void leave(Node astNode, VirtualFrame frame, char result) {
        }

        public void leave(Node astNode, VirtualFrame frame, float result) {
        }

        public void leave(Node astNode, VirtualFrame frame, double result) {
        }

        public void leave(Node astNode, VirtualFrame frame, Object result) {
        }

        public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
        }

        public void replace(Node oldAstNode, Node newAstNode, String reason) {
        }

    }

    /**
     * Holder of a chain of {@linkplain InstrumentationProbeNode probes}: manages the
     * {@link Assumption} that the chain has not changed since checked checked.
     * <p>
     * May be categorized by one or more {@linkplain NodePhylum node phyla}, signifying information
     * useful for instrumentation about its AST location(s).
     */
    public static final class ProbeChain extends DefaultProbeNode implements PhylumMarked {

        @CompilerDirectives.CompilationFinal private Assumption probeUnchanged;

        /**
         * When in stepping mode, ordinary line breakpoints are ignored, but every entry at a line
         * will cause a halt.
         */
        @CompilerDirectives.CompilationFinal private boolean stepping;

        /**
         * Source information about the node to which this probe chain is attached; it isn't
         * otherwise available. A probe chain is shared by every copy made during runtime, so there
         * is no parent pointer.
         */
        private final SourceSection probedSourceSection;

        private final Set<NodePhylum> phyla = EnumSet.noneOf(NodePhylum.class);

        private final String description; // for debugging

        /**
         * Creates a new, empty chain of {@linkplain InstrumentationProbeNode probes}, to which
         * probes can be added/removed, and all of which will be notified of
         * {@linkplain InstrumentationProbeEvents events} when the chain is notified.
         */
        public ProbeChain(ExecutionContext context, SourceSection sourceSection, String description) {
            super(context);
            this.probeUnchanged = Truffle.getRuntime().createAssumption();
            this.probedSourceSection = sourceSection;
            this.description = description;
            this.next = null;
        }

        public int probeCount() {
            return countProbes();
        }

        public String getDescription() {
            return description;
        }

        public SourceSection getProbedSourceSection() {
            return probedSourceSection;
        }

        /**
         * Mark this probe chain as being associated with an AST node in some category useful for
         * debugging and other tools.
         */
        public void markAs(NodePhylum phylum) {
            assert phylum != null;
            phyla.add(phylum);
        }

        /**
         * Is this probe chain as being associated with an AST node in some category useful for
         * debugging and other tools.
         */
        public boolean isMarkedAs(NodePhylum phylum) {
            assert phylum != null;
            return phyla.contains(phylum);
        }

        /**
         * In which categories is the AST (with which this probe is associated) marked?
         */
        public Set<NodePhylum> getPhylumMarks() {
            return phyla;
        }

        /**
         * Change <em>stepping mode</em> for statements.
         */
        public void setStepping(boolean stepping) {
            if (this.stepping != stepping) {
                this.stepping = stepping;
                probeUnchanged.invalidate();
                probeUnchanged = Truffle.getRuntime().createAssumption();
            }
        }

        @Override
        protected boolean isStepping() {
            return stepping;
        }

        @Override
        protected int countProbes() {
            // The head of the chain does not itself hold a probe
            return next == null ? 0 : next.countProbes();
        }

        @Override
        protected void notifyProbeChanged(InstrumentationProbeNode probeNode) {
            probeUnchanged.invalidate();
            probeUnchanged = Truffle.getRuntime().createAssumption();
        }

        @CompilerDirectives.SlowPath
        public void appendProbe(InstrumentationProbeNode newProbeNode) {
            probeUnchanged.invalidate();
            super.internalAppendProbe(newProbeNode);
            probeUnchanged = Truffle.getRuntime().createAssumption();
        }

        @CompilerDirectives.SlowPath
        public void removeProbe(InstrumentationProbeNode oldProbeNode) {
            probeUnchanged.invalidate();
            super.internalRemoveProbe(oldProbeNode);
            probeUnchanged = Truffle.getRuntime().createAssumption();
        }

        public void notifyEnter(Node astNode, VirtualFrame frame) {
            if (stepping || next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                if (stepping) {
                    getContext().getDebugContext().getDebugManager().haltedAt(astNode, frame);
                }
                if (next != null) {
                    next.internalEnter(astNode, frame);
                }
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, boolean result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, byte result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, short result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, int result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, long result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, char result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, float result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, double result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeave(Node astNode, VirtualFrame frame, Object result) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeave(astNode, frame, result);
            }
        }

        public void notifyLeaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalLeaveExceptional(astNode, frame, e);
            }
        }

        public void notifyReplace(Node oldAstNode, Node newAstNode, String reason) {
            if (next != null) {
                if (!probeUnchanged.isValid()) {
                    CompilerDirectives.transferToInterpreter();
                }
                next.internalReplace(oldAstNode, newAstNode, reason);
            }
        }

    }

}