diff truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/LineBreakpointFactory.java @ 22003:5bc7f7b867ab

Making debugger always on for each TruffleVM execution. Introducing EventConsumer to process such debugger events. Requesting each RootNode to be associated with a TruffleLanguage, so debugger can find out proper context for each Node where executions gets suspended.
author Jaroslav Tulach <jaroslav.tulach@oracle.com>
date Sat, 18 Jul 2015 18:03:36 +0200
parents
children 95d5d6a93968
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/LineBreakpointFactory.java	Sat Jul 18 18:03:36 2015 +0200
@@ -0,0 +1,465 @@
+/*
+ * Copyright (c) 2015, 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.debug;
+
+import static com.oracle.truffle.api.debug.Breakpoint.State.*;
+
+import java.io.*;
+import java.util.*;
+import java.util.Map.Entry;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.debug.Debugger.BreakpointCallback;
+import com.oracle.truffle.api.debug.Debugger.WarningLog;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.instrument.*;
+import com.oracle.truffle.api.instrument.impl.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.utilities.*;
+
+//TODO (mlvdv) some common functionality could be factored out of this and TagBreakpointSupport
+
+/**
+ * Support class for creating and managing all existing ordinary (user visible) line breakpoints.
+ * <p>
+ * Notes:
+ * <ol>
+ * <li>Line breakpoints can only be set at nodes tagged as {@link StandardSyntaxTag#STATEMENT}.</li>
+ * <li>A newly created breakpoint looks for probes matching the location, attaches to them if found
+ * by installing an {@link Instrument} that calls back to the breakpoint.</li>
+ * <li>When Truffle "splits" or otherwise copies an AST, any attached {@link Instrument} will be
+ * copied along with the rest of the AST and will call back to the same breakpoint.</li>
+ * <li>When notification is received of a new Node being tagged as a statement, and if a
+ * breakpoint's line location matches the Probe's line location, then the breakpoint will attach a
+ * new Instrument at the probe to activate the breakpoint at that location.</li>
+ * <li>A breakpoint may have multiple Instruments deployed, one attached to each Probe that matches
+ * the breakpoint's line location; this might happen when a source is reloaded.</li>
+ * </ol>
+ *
+ */
+final class LineBreakpointFactory {
+
+    private static final boolean TRACE = false;
+    private static final PrintStream OUT = System.out;
+
+    private static final String BREAKPOINT_NAME = "LINE BREAKPOINT";
+
+    private static void trace(String format, Object... args) {
+        if (TRACE) {
+            OUT.println(String.format("%s: %s", BREAKPOINT_NAME, String.format(format, args)));
+        }
+    }
+
+    private static final Comparator<Entry<LineLocation, LineBreakpointImpl>> BREAKPOINT_COMPARATOR = new Comparator<Entry<LineLocation, LineBreakpointImpl>>() {
+
+        @Override
+        public int compare(Entry<LineLocation, LineBreakpointImpl> entry1, Entry<LineLocation, LineBreakpointImpl> entry2) {
+            final LineLocation line1 = entry1.getKey();
+            final LineLocation line2 = entry2.getKey();
+            final int nameOrder = line1.getSource().getShortName().compareTo(line2.getSource().getShortName());
+            if (nameOrder != 0) {
+                return nameOrder;
+            }
+            return Integer.compare(line1.getLineNumber(), line2.getLineNumber());
+        }
+    };
+
+    private final Debugger executionSupport;
+    private final BreakpointCallback breakpointCallback;
+    private final WarningLog warningLog;
+
+    /**
+     * Map: Source lines ==> attached breakpoints. There may be no more than one line breakpoint
+     * associated with a line.
+     */
+    private final Map<LineLocation, LineBreakpointImpl> lineToBreakpoint = new HashMap<>();
+
+    /**
+     * A map of {@link LineLocation} to a collection of {@link Probe}s. This list must be
+     * initialized and filled prior to being used by this class.
+     */
+    private final LineToProbesMap lineToProbesMap;
+
+    /**
+     * Globally suspends all line breakpoint activity when {@code false}, ignoring whether
+     * individual breakpoints are enabled.
+     */
+    @CompilationFinal private boolean breakpointsActive = true;
+    private final CyclicAssumption breakpointsActiveUnchanged = new CyclicAssumption(BREAKPOINT_NAME + " globally active");
+
+    LineBreakpointFactory(Debugger executionSupport, BreakpointCallback breakpointCallback, final WarningLog warningLog) {
+        this.executionSupport = executionSupport;
+        this.breakpointCallback = breakpointCallback;
+        this.warningLog = warningLog;
+
+        lineToProbesMap = new LineToProbesMap();
+        lineToProbesMap.install();
+
+        Probe.addProbeListener(new DefaultProbeListener() {
+
+            @Override
+            public void probeTaggedAs(Probe probe, SyntaxTag tag, Object tagValue) {
+                if (tag == StandardSyntaxTag.STATEMENT) {
+                    final SourceSection sourceSection = probe.getProbedSourceSection();
+                    if (sourceSection != null) {
+                        final LineLocation lineLocation = sourceSection.getLineLocation();
+                        if (lineLocation != null) {
+                            // A Probe with line location tagged STATEMENT we haven't seen before.
+                            final LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation);
+                            if (breakpoint != null) {
+                                try {
+                                    breakpoint.attach(probe);
+                                } catch (IOException e) {
+                                    warningLog.addWarning(BREAKPOINT_NAME + " failure attaching to newly tagged Probe: " + e.getMessage());
+                                    if (TRACE) {
+                                        OUT.println(BREAKPOINT_NAME + " failure: " + e.getMessage());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Globally enables line breakpoint activity; all breakpoints are ignored when set to
+     * {@code false}. When set to {@code true}, the enabled/disabled status of each breakpoint
+     * determines whether it will trigger when flow of execution reaches it.
+     *
+     * @param breakpointsActive
+     */
+    void setActive(boolean breakpointsActive) {
+        if (this.breakpointsActive != breakpointsActive) {
+            breakpointsActiveUnchanged.invalidate();
+            this.breakpointsActive = breakpointsActive;
+        }
+    }
+
+    /**
+     * Gets all current line breakpoints,regardless of status; sorted and modification safe.
+     */
+    List<LineBreakpoint> getAll() {
+        ArrayList<Entry<LineLocation, LineBreakpointImpl>> entries = new ArrayList<>(lineToBreakpoint.entrySet());
+        Collections.sort(entries, BREAKPOINT_COMPARATOR);
+
+        final ArrayList<LineBreakpoint> breakpoints = new ArrayList<>(entries.size());
+        for (Entry<LineLocation, LineBreakpointImpl> entry : entries) {
+            breakpoints.add(entry.getValue());
+        }
+        return breakpoints;
+    }
+
+    /**
+     * Creates a new line breakpoint if one doesn't already exist. If one does exist, then resets
+     * the <em>ignore count</em>.
+     *
+     * @param lineLocation where to set the breakpoint
+     * @param ignoreCount number of initial hits before the breakpoint starts causing breaks.
+     * @param oneShot whether the breakpoint should dispose itself after one hit
+     * @return a possibly new breakpoint
+     * @throws IOException if a breakpoint already exists at the location and the ignore count is
+     *             the same
+     */
+    LineBreakpoint create(int ignoreCount, LineLocation lineLocation, boolean oneShot) throws IOException {
+
+        LineBreakpointImpl breakpoint = lineToBreakpoint.get(lineLocation);
+
+        if (breakpoint == null) {
+            breakpoint = new LineBreakpointImpl(ignoreCount, lineLocation, oneShot);
+
+            if (TRACE) {
+                trace("NEW " + breakpoint.getShortDescription());
+            }
+
+            lineToBreakpoint.put(lineLocation, breakpoint);
+
+            for (Probe probe : lineToProbesMap.findProbes(lineLocation)) {
+                if (probe.isTaggedAs(StandardSyntaxTag.STATEMENT)) {
+                    breakpoint.attach(probe);
+                    break;
+                }
+            }
+        } else {
+            if (ignoreCount == breakpoint.getIgnoreCount()) {
+                throw new IOException(BREAKPOINT_NAME + " already set at line " + lineLocation);
+            }
+            breakpoint.setIgnoreCount(ignoreCount);
+            if (TRACE) {
+                trace("CHANGED ignoreCount %s", breakpoint.getShortDescription());
+            }
+        }
+        return breakpoint;
+    }
+
+    /**
+     * Returns the {@link LineBreakpoint} for a given line. There should only ever be one breakpoint
+     * per line.
+     *
+     * @param lineLocation The {@link LineLocation} to get the breakpoint for.
+     * @return The breakpoint for the given line.
+     */
+    LineBreakpoint get(LineLocation lineLocation) {
+        return lineToBreakpoint.get(lineLocation);
+    }
+
+    /**
+     * Removes the associated instrumentation for all one-shot breakpoints only.
+     */
+    void disposeOneShots() {
+        List<LineBreakpointImpl> breakpoints = new ArrayList<>(lineToBreakpoint.values());
+        for (LineBreakpointImpl breakpoint : breakpoints) {
+            if (breakpoint.isOneShot()) {
+                breakpoint.dispose();
+            }
+        }
+    }
+
+    /**
+     * Removes all knowledge of a breakpoint, presumed disposed.
+     */
+    private void forget(LineBreakpointImpl breakpoint) {
+        lineToBreakpoint.remove(breakpoint.getLineLocation());
+    }
+
+    /**
+     * Concrete representation of a line breakpoint, implemented by attaching an instrument to a
+     * probe at the designated source location.
+     */
+    private final class LineBreakpointImpl extends LineBreakpoint implements AdvancedInstrumentResultListener {
+
+        private static final String SHOULD_NOT_HAPPEN = "LineBreakpointImpl:  should not happen";
+
+        private final LineLocation lineLocation;
+
+        // Cached assumption that the global status of line breakpoint activity has not changed.
+        private Assumption breakpointsActiveAssumption;
+
+        // Whether this breakpoint is enable/disabled
+        @CompilationFinal private boolean isEnabled;
+        private Assumption enabledUnchangedAssumption;
+
+        private String conditionExpr;
+
+        /**
+         * The instrument(s) that this breakpoint currently has attached to a {@link Probe}:
+         * {@code null} if not attached.
+         */
+        private List<Instrument> instruments = new ArrayList<>();
+
+        public LineBreakpointImpl(int ignoreCount, LineLocation lineLocation, boolean oneShot) {
+            super(ENABLED_UNRESOLVED, ignoreCount, oneShot);
+            this.lineLocation = lineLocation;
+
+            this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
+            this.isEnabled = true;
+            this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption(BREAKPOINT_NAME + " enabled state unchanged");
+        }
+
+        @Override
+        public boolean isEnabled() {
+            return isEnabled;
+        }
+
+        @Override
+        public void setEnabled(boolean enabled) {
+            if (enabled != isEnabled) {
+                switch (getState()) {
+                    case ENABLED:
+                        assert !enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(false);
+                        changeState(DISABLED);
+                        break;
+                    case ENABLED_UNRESOLVED:
+                        assert !enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(false);
+                        changeState(DISABLED_UNRESOLVED);
+                        break;
+                    case DISABLED:
+                        assert enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(true);
+                        changeState(ENABLED);
+                        break;
+                    case DISABLED_UNRESOLVED:
+                        assert enabled : SHOULD_NOT_HAPPEN;
+                        doSetEnabled(true);
+                        changeState(DISABLED_UNRESOLVED);
+                        break;
+                    case DISPOSED:
+                        assert false : "breakpoint disposed";
+                        break;
+                    default:
+                        assert false : SHOULD_NOT_HAPPEN;
+                        break;
+                }
+            }
+        }
+
+        @Override
+        public void setCondition(String expr) throws IOException {
+            if (this.conditionExpr != null || expr != null) {
+                // De-instrument the Probes instrumented by this breakpoint
+                final ArrayList<Probe> probes = new ArrayList<>();
+                for (Instrument instrument : instruments) {
+                    probes.add(instrument.getProbe());
+                    instrument.dispose();
+                }
+                instruments.clear();
+                this.conditionExpr = expr;
+                // Re-instrument the probes previously instrumented
+                for (Probe probe : probes) {
+                    attach(probe);
+                }
+            }
+        }
+
+        @Override
+        public String getCondition() {
+            return conditionExpr;
+        }
+
+        @Override
+        public void dispose() {
+            if (getState() != DISPOSED) {
+                for (Instrument instrument : instruments) {
+                    instrument.dispose();
+                }
+                changeState(DISPOSED);
+                LineBreakpointFactory.this.forget(this);
+            }
+        }
+
+        private void attach(Probe newProbe) throws IOException {
+            if (getState() == DISPOSED) {
+                throw new IllegalStateException("Attempt to attach a disposed " + BREAKPOINT_NAME);
+            }
+            Instrument newInstrument = null;
+            if (conditionExpr == null) {
+                newInstrument = Instrument.create(new UnconditionalLineBreakInstrumentListener(), BREAKPOINT_NAME);
+            } else {
+                newInstrument = Instrument.create(this, executionSupport.createAdvancedInstrumentRootFactory(newProbe, conditionExpr, this), Boolean.class, BREAKPOINT_NAME);
+            }
+            newProbe.attach(newInstrument);
+            instruments.add(newInstrument);
+            changeState(isEnabled ? ENABLED : DISABLED);
+        }
+
+        private void doSetEnabled(boolean enabled) {
+            if (this.isEnabled != enabled) {
+                enabledUnchangedAssumption.invalidate();
+                this.isEnabled = enabled;
+            }
+        }
+
+        private String getShortDescription() {
+            return BREAKPOINT_NAME + "@" + getLineLocation().getShortDescription();
+        }
+
+        private void changeState(State after) {
+            if (TRACE) {
+                trace("STATE %s-->%s %s", getState().getName(), after.getName(), getShortDescription());
+            }
+            setState(after);
+        }
+
+        private void doBreak(Node node, VirtualFrame vFrame) {
+            if (incrHitCountCheckIgnore()) {
+                breakpointCallback.haltedAt(node, vFrame.materialize(), BREAKPOINT_NAME);
+            }
+        }
+
+        /**
+         * Receives notification from the attached instrument that execution is about to enter node
+         * where the breakpoint is set. Designed so that when in the fast path, there is either an
+         * unconditional "halt" call to the debugger or nothing.
+         */
+        private void nodeEnter(Node astNode, VirtualFrame vFrame) {
+
+            // Deopt if the global active/inactive flag has changed
+            try {
+                this.breakpointsActiveAssumption.check();
+            } catch (InvalidAssumptionException ex) {
+                this.breakpointsActiveAssumption = LineBreakpointFactory.this.breakpointsActiveUnchanged.getAssumption();
+            }
+
+            // Deopt if the enabled/disabled state of this breakpoint has changed
+            try {
+                this.enabledUnchangedAssumption.check();
+            } catch (InvalidAssumptionException ex) {
+                this.enabledUnchangedAssumption = Truffle.getRuntime().createAssumption("LineBreakpoint enabled state unchanged");
+            }
+
+            if (LineBreakpointFactory.this.breakpointsActive && this.isEnabled) {
+                if (isOneShot()) {
+                    dispose();
+                }
+                LineBreakpointImpl.this.doBreak(astNode, vFrame);
+            }
+        }
+
+        public void notifyResult(Node node, VirtualFrame vFrame, Object result) {
+            final boolean condition = (Boolean) result;
+            if (TRACE) {
+                trace("breakpoint condition = %b  %s", condition, getShortDescription());
+            }
+            if (condition) {
+                nodeEnter(node, vFrame);
+            }
+        }
+
+        @TruffleBoundary
+        public void notifyFailure(Node node, VirtualFrame vFrame, RuntimeException ex) {
+            warningLog.addWarning(String.format("Exception in %s:  %s", getShortDescription(), ex.getMessage()));
+            if (TRACE) {
+                trace("breakpoint failure = %s  %s", ex.toString(), getShortDescription());
+            }
+            // Take the breakpoint if evaluation fails.
+            nodeEnter(node, vFrame);
+        }
+
+        @Override
+        public String getLocationDescription() {
+            return "Line: " + lineLocation.getShortDescription();
+        }
+
+        @Override
+        public LineLocation getLineLocation() {
+            return lineLocation;
+        }
+
+        private final class UnconditionalLineBreakInstrumentListener extends DefaultStandardInstrumentListener {
+
+            @Override
+            public void enter(Probe probe, Node node, VirtualFrame vFrame) {
+                LineBreakpointImpl.this.nodeEnter(node, vFrame);
+            }
+        }
+    }
+
+}