changeset 13570:d7af2296cebb

Merge with 4fc8c8bb4c32878cc04b064d2ac9ad1fce1a85e0
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Wed, 08 Jan 2014 15:49:18 -0800
parents 1894412de0ed (diff) 4fc8c8bb4c32 (current diff)
children 1335577dc50c 5335d65fec56
files graal/com.oracle.graal.nodes/src/com/oracle/graal/nodes/extended/MonitorReference.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/call/CallNode.java graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java
diffstat 36 files changed, 1666 insertions(+), 418 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/ASTPrinter.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.io.*;
+
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * Language-agnostic access to AST-based debugging support.
+ * <p>
+ * <strong>WARNING:</strong> this interface is under development and will change substantially.
+ */
+public interface ASTPrinter {
+
+    /**
+     * Print a textual AST display, one line per node, with nesting.
+     * 
+     * @param p
+     * @param node the root node of the display.
+     * @param maxDepth the maximum number of levels to print below the root
+     * @param markNode a node to mark with a textual arrow prefix, if present.
+     */
+    void printTree(PrintWriter p, Node node, int maxDepth, Node markNode);
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/DebugManager.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/DebugManager.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -24,16 +24,24 @@
  */
 package com.oracle.truffle.api;
 
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.instrument.*;
 import com.oracle.truffle.api.source.*;
 
 /**
  * Language-agnostic access to AST-based debugging support.
  * <p>
- * <strong>WARNING:</strong> this interface is under development and will change substantially.
+ * <strong>Disclaimer:</strong> this interface is under development and will change.
  */
 public interface DebugManager {
 
     /**
+     * Gets a list of current breakpoints.
+     */
+    LineBreakpoint[] getBreakpoints();
+
+    /**
      * Sets a breakpoint at a line-based location.
      */
     LineBreakpoint setBreakpoint(SourceLineLocation lineLocation);
@@ -45,9 +53,14 @@
     LineBreakpoint setConditionalBreakpoint(SourceLineLocation lineLocation, String condition);
 
     /**
-     * Gets a list of current breakpoints.
+     * Sets a breakpoint at a line-based location that will remove itself when hit.
      */
-    LineBreakpoint[] getBreakpoints();
+    LineBreakpoint setOneShotBreakpoint(SourceLineLocation lineLocation);
+
+    /**
+     * Is there a line-based breakpoint set at a location?
+     */
+    boolean hasBreakpoint(SourceLineLocation lineLocation);
 
     /**
      * Removes a breakpoint at a line-based location.
@@ -55,6 +68,19 @@
     void removeBreakpoint(SourceLineLocation lineLocation);
 
     /**
+     * Notification from Truffle execution that execution should halt in a debugging context.
+     */
+    /**
+     * Receives notification of a suspended execution context; execution resumes when this method
+     * returns.
+     * 
+     * @param astNode a guest language AST node that represents the current execution site, assumed
+     *            not to be any kind of {@link InstrumentationNode},
+     * @param frame execution frame at the site where execution suspended
+     */
+    void haltedAt(Node astNode, MaterializedFrame frame);
+
+    /**
      * Description of a line-based breakpoint.
      */
     interface LineBreakpoint {
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/ExecutionContext.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/ExecutionContext.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -26,6 +26,8 @@
 
 /**
  * Information about the runtime context of a Truffle program.
+ * <p>
+ * <strong>Disclaimer:</strong> this interface is under development and will change.
  */
 public interface ExecutionContext {
 
@@ -40,4 +42,9 @@
      */
     DebugManager getDebugManager();
 
+    /**
+     * Gets access to utilities for printing different aspects of a Truffle AST.
+     */
+    ASTPrinter getASTPrinter();
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/NullSourceSection.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 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;
+
+/**
+ * Marker for a special flavor of {@link SourceSection} that has no content and can be ignored.
+ */
+public interface NullSourceSection extends SourceSection {
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/SourceSection.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/SourceSection.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -95,7 +95,7 @@
     /**
      * Singleton instance with no content.
      */
-    SourceSection NULL = new SourceSection() {
+    SourceSection NULL = new NullSourceSection() {
 
         public Source getSource() {
             return null;
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/EmptyProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2013, 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 com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.DefaultProbeNode;
-
-/**
- * An "probe" that does nothing, used for testing. It relies on the
- * {@link InstrumentationProbeNode.DefaultProbeNode} implementation to override every event with an
- * empty method.
- */
-public class EmptyProbe extends DefaultProbeNode {
-
-}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeNode.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProbeNode.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -24,6 +24,8 @@
  */
 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.*;
@@ -50,6 +52,11 @@
         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.
      */
@@ -69,6 +76,7 @@
                 this.next = null;
             } else {
                 this.next = adoptChild(oldProbeNode.next);
+                oldProbeNode.next = null;
             }
         } else {
             next.internalRemoveProbe(oldProbeNode);
@@ -186,6 +194,16 @@
      */
     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) {
         }
 
@@ -230,17 +248,28 @@
     /**
      * 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 {
+    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.
          */
-        @SuppressWarnings("unused") private final SourceSection sourceSection;
+        private final SourceSection probedSourceSection;
+
+        private final Set<NodePhylum> phyla = EnumSet.noneOf(NodePhylum.class);
 
         private final String description; // for debugging
 
@@ -249,9 +278,10 @@
          * probes can be added/removed, and all of which will be notified of
          * {@linkplain InstrumentationProbeEvents events} when the chain is notified.
          */
-        public ProbeChain(SourceSection sourceSection, String description) {
+        public ProbeChain(ExecutionContext context, SourceSection sourceSection, String description) {
+            super(context);
             this.probeUnchanged = Truffle.getRuntime().createAssumption();
-            this.sourceSection = sourceSection;
+            this.probedSourceSection = sourceSection;
             this.description = description;
             this.next = null;
         }
@@ -264,6 +294,51 @@
             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
@@ -291,10 +366,13 @@
         }
 
         public void notifyEnter(Node astNode, VirtualFrame frame) {
-            if (next != null) {
+            if (stepping || next != null) {
                 if (!probeUnchanged.isValid()) {
                     CompilerDirectives.transferToInterpreter();
                 }
+                if (stepping) {
+                    getContext().getDebugManager().haltedAt(astNode, frame.materialize());
+                }
                 next.internalEnter(astNode, frame);
             }
         }
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProxyNode.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/InstrumentationProxyNode.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -24,6 +24,7 @@
  */
 package com.oracle.truffle.api.nodes.instrument;
 
+import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.ProbeChain;
 
 /**
@@ -36,7 +37,12 @@
  * probe chain} which passes along {@linkplain InstrumentationProbeEvents events} to any
  * {@linkplain InstrumentationProbeNode probes} that might have been attached.
  */
-public interface InstrumentationProxyNode extends InstrumentationNode {
+public interface InstrumentationProxyNode extends InstrumentationNode, PhylumMarked {
+
+    /**
+     * Gets the non-instrumentation node being proxied.
+     */
+    Node getChild();
 
     /**
      * Gets the chain of probes to which events at this node are delegated. Note that a chain of
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/NodePhylum.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 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;
+
+/**
+ * Categories of {@link InstrumentationProxyNode}s to be used for defining user-visible debugging
+ * and other simple tool behavior. These categories (<em>phyla</em>) should correspond to program
+ * structures that are meaningful to a programmer using the guest language. A Truffle node without a
+ * proxy carrying some phylum should be treated as an artifact of the guest language implementation
+ * and should never to the user of a guest language programming tool.
+ * <p>
+ * Note that phyla are not intended to represent a partition of user-visible node categories, as the
+ * relative categorization of nodes can change with the particular programming tasks at hand.
+ * <p>
+ * This is a somewhat language-agnostic set of phyla, suitable for conventional imperative
+ * languages, and is being developed incrementally.
+ * <p>
+ * The need for alternative sets of phyla is likely to arise, perhaps for other families of
+ * languages (for example for mostly expression-oriented languages) or even for specific languages.
+ * <p>
+ * These are listed alphabetically so that listing from some collection classes will come out in
+ * that order.
+ * <p>
+ * <strong>Disclaimer:</strong> this interface is under development and will change.
+ */
+public enum NodePhylum {
+
+    /**
+     * Marker for a proxy at a variable assignment.
+     */
+    ASSIGNMENT,
+
+    /**
+     * Marker for a proxy at a call site.
+     */
+    CALL,
+
+    /**
+     * Marker for a proxy at which ordinary "stepping" should halt.
+     */
+    STATEMENT;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/instrument/PhylumMarked.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 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.nodes.*;
+
+/**
+ * A kind of {@link Node} that can be marked as belong to 0 or more {@linkplain NodePhylum phyla}.
+ */
+public interface PhylumMarked {
+
+    /**
+     * Is this proxy tagged as belonging to a particular category of language constructs?
+     */
+    boolean isMarkedAs(NodePhylum phylum);
+
+    /**
+     * In which categories is this node tagged (<em>empty set</em> if none).
+     */
+    Set<NodePhylum> getPhylumMarks();
+
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceLineLocation.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceLineLocation.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -38,11 +38,14 @@
 
     public SourceLineLocation(Source source, int line) {
         assert source != null;
-        assert source != SourceSection.NULL;
         this.source = source;
         this.line = line;
     }
 
+    public SourceLineLocation(SourceSection sourceSection) {
+        this(sourceSection.getSource(), sourceSection.getStartLine());
+    }
+
     public Source getSource() {
         return source;
     }
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceManager.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceManager.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -47,7 +47,7 @@
 public final class SourceManager {
 
     // Only files and fake files are indexed.
-    private final Map<String, SourceImpl> sourceMap = new HashMap<>();
+    private final Map<String, SourceImpl> pathToSource = new HashMap<>();
 
     public SourceManager() {
 
@@ -57,24 +57,29 @@
      * Gets the canonical representation of a source file, whose contents will be read lazily and
      * then cached.
      * 
-     * @param reset forces any existing {@link Source} cache to be cleared, forcing a re-read.
+     * @param reset forces any existing {@link Source} cache to be cleared, forcing a re-read
      */
     public Source get(String fileName, boolean reset) {
-        SourceImpl source = sourceMap.get(fileName);
+
+        SourceImpl source = pathToSource.get(fileName);
         if (source == null) {
-            String path = findPath(fileName);
-            if (path == null) {
-                throw new RuntimeException("Can't find file " + fileName);
+            final File file = new File(fileName);
+            String path = null;
+            if (file.exists()) {
+                try {
+                    path = file.getCanonicalPath();
+                } catch (IOException e) {
+                    throw new RuntimeException("Can't find file " + fileName);
+                }
             }
-            source = sourceMap.get(path);
+            source = pathToSource.get(path);
             if (source == null) {
-                source = new FileSourceImpl(fileName, path);
-                sourceMap.put(path, source);
+                source = new FileSourceImpl(file, fileName, path);
+                pathToSource.put(path, source);
             }
-        } else {
-            if (reset) {
-                source.reset();
-            }
+        }
+        if (reset) {
+            source.reset();
         }
         return source;
     }
@@ -111,22 +116,10 @@
      */
     public Source getFakeFile(String name, String code) {
         final SourceImpl source = new LiteralSourceImpl(name, code);
-        sourceMap.put(name, source);
+        pathToSource.put(name, source);
         return source;
     }
 
-    // If it names a real file, get the (canonical) normalized absolute path.
-    private static String findPath(String name) {
-        final File file = new File(name);
-        if (file.exists()) {
-            try {
-                return file.getCanonicalPath();
-            } catch (IOException e) {
-            }
-        }
-        return null;
-    }
-
     private static String readCode(Reader reader) throws IOException {
         final StringBuilder builder = new StringBuilder();
         final char[] buffer = new char[1024];
@@ -142,8 +135,7 @@
         return builder.toString();
     }
 
-    // TODO (mlvdv) make this private once some related code changes propagate
-    public abstract static class SourceImpl implements Source {
+    private abstract static class SourceImpl implements Source {
 
         protected TextMap textMap = null;
 
@@ -267,16 +259,17 @@
 
     private static class FileSourceImpl extends SourceImpl {
 
+        private final File file;
         private final String name; // Name used originally to describe the source
-        private String code = null;
         private final String path;  // Normalized path description of an actual file
-        private boolean readAttempted;
 
-        public FileSourceImpl(String name, String path) {
+        private String code = null;  // A cache of the file's contents
+        private long timeStamp;      // timestamp of the cache in the file system
+
+        public FileSourceImpl(File file, String name, String path) {
+            this.file = file;
             this.name = name;
             this.path = path;
-            this.readAttempted = false;
-
         }
 
         @Override
@@ -286,10 +279,10 @@
 
         @Override
         public String getCode() {
-            if (code == null && !readAttempted) {
-                readAttempted = true;
+            if (code == null || timeStamp != file.lastModified()) {
                 try {
                     code = readCode(getReader());
+                    timeStamp = file.lastModified();
                 } catch (IOException e) {
                 }
             }
@@ -303,11 +296,11 @@
 
         @Override
         public Reader getReader() {
-            if (code != null) {
+            if (code != null && timeStamp == file.lastModified()) {
                 return new StringReader(code);
             }
             try {
-                return new BufferedReader(new FileReader(path));
+                return new FileReader(file);
             } catch (FileNotFoundException e) {
                 throw new RuntimeException("Can't find file " + path);
             }
@@ -316,7 +309,6 @@
         @Override
         protected void reset() {
             this.code = null;
-            this.readAttempted = false;
         }
 
     }
--- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/CoreSourceSection.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/CoreSourceSection.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -12,9 +12,9 @@
 import com.oracle.truffle.api.*;
 
 /**
- * Singleton source section used for core method nodes.
+ * Source sections used for core method nodes.
  */
-public final class CoreSourceSection implements SourceSection {
+public final class CoreSourceSection implements NullSourceSection {
 
     private final String name;
 
--- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/call/CallNode.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/call/CallNode.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -152,4 +152,7 @@
         return context.makeString("method");
     }
 
+    public String getName() {
+        return name;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyASTPrinter.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,176 @@
+/*
+ * 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 com.oracle.truffle.ruby.nodes.debug;
+
+import java.io.*;
+import java.util.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.NodeUtil.NodeClass;
+import com.oracle.truffle.api.nodes.NodeUtil.NodeField;
+import com.oracle.truffle.api.nodes.NodeUtil.NodeFieldKind;
+import com.oracle.truffle.api.nodes.instrument.*;
+import com.oracle.truffle.ruby.nodes.*;
+import com.oracle.truffle.ruby.nodes.call.*;
+import com.oracle.truffle.ruby.nodes.literal.*;
+import com.oracle.truffle.ruby.nodes.methods.*;
+
+/**
+ * Printers for Truffle-internal AST information.
+ */
+public final class RubyASTPrinter implements ASTPrinter {
+
+    public RubyASTPrinter() {
+    }
+
+    public void printTree(PrintWriter p, Node node, int maxDepth, Node markNode) {
+        printTree(p, node, maxDepth, markNode, 1);
+        p.println();
+        p.flush();
+    }
+
+    public String printTreeToString(Node node, int maxDepth, Node markNode) {
+        StringWriter out = new StringWriter();
+        printTree(new PrintWriter(out), node, maxDepth, markNode);
+        return out.toString();
+    }
+
+    public String printTreeToString(Node node, int maxDepth) {
+        return printTreeToString(node, maxDepth, null);
+    }
+
+    private static void printTree(PrintWriter p, Node node, int maxDepth, Node markNode, int level) {
+        if (node == null) {
+            p.print("null");
+            return;
+        }
+
+        p.print(nodeName(node));
+
+        String sep = "";
+        p.print("(");
+
+        final SourceSection src = node.getSourceSection();
+        if (src != null) {
+            if (!(src instanceof NullSourceSection)) {
+                p.print(src.getSource().getName() + ":" + src.getStartLine());
+            } else if (src instanceof CoreSourceSection) {
+                final CoreSourceSection coreSection = (CoreSourceSection) src;
+                p.print("core=\"" + (coreSection == null ? "?" : coreSection.toString()) + "\"");
+            }
+        }
+        if (node instanceof PhylumMarked) {
+            final PhylumMarked markedNode = (PhylumMarked) node;
+            String prefix = "";
+            for (NodePhylum phylum : markedNode.getPhylumMarks()) {
+                p.print(prefix);
+                prefix = ",";
+                p.print(phylum.toString());
+            }
+
+        }
+
+        ArrayList<NodeField> childFields = new ArrayList<>();
+
+        for (NodeField field : NodeClass.get(node.getClass()).getFields()) {
+            if (field.getKind() == NodeFieldKind.CHILD || field.getKind() == NodeFieldKind.CHILDREN) {
+                childFields.add(field);
+            } else if (field.getKind() == NodeFieldKind.DATA) {
+                // p.print(sep);
+                // sep = ", ";
+                //
+                // final String fieldName = field.getName();
+                // switch (fieldName) {
+                //
+                // }
+                // p.print(fieldName);
+                // p.print(" = ");
+                // p.print(field.loadValue(node));
+            }
+        }
+        p.print(")");
+
+        if (level <= maxDepth) {
+
+            if (childFields.size() != 0) {
+                p.print(" {");
+                for (NodeField field : childFields) {
+
+                    Object value = field.loadValue(node);
+                    if (value == null) {
+                        printNewLine(p, level);
+                        p.print(field.getName());
+                        p.print(" = null ");
+                    } else if (field.getKind() == NodeFieldKind.CHILD) {
+                        final Node valueNode = (Node) value;
+                        printNewLine(p, level, valueNode == markNode);
+                        p.print(field.getName());
+                        p.print(" = ");
+                        printTree(p, valueNode, maxDepth, markNode, level + 1);
+                    } else if (field.getKind() == NodeFieldKind.CHILDREN) {
+                        printNewLine(p, level);
+                        p.print(field.getName());
+                        Node[] children = (Node[]) value;
+                        p.print(" = [");
+                        sep = "";
+                        for (Node child : children) {
+                            p.print(sep);
+                            sep = ", ";
+                            printTree(p, child, maxDepth, markNode, level + 1);
+                        }
+                        p.print("]");
+                    } else {
+                        printNewLine(p, level);
+                        p.print(field.getName());
+                    }
+                }
+                printNewLine(p, level - 1);
+                p.print("}");
+            }
+        }
+    }
+
+    private static void printNewLine(PrintWriter p, int level, boolean mark) {
+        p.println();
+        for (int i = 0; i < level; i++) {
+            if (mark && i == 0) {
+                p.print(" -->");
+            } else {
+                p.print("    ");
+            }
+        }
+    }
+
+    private static void printNewLine(PrintWriter p, int level) {
+        printNewLine(p, level, false);
+    }
+
+    private static String nodeName(Node node) {
+        String nodeVal = null;
+        if (node instanceof CallNode) {
+            final CallNode callNode = (CallNode) node;
+            nodeVal = callNode.getName();
+
+        } else if (node instanceof FixnumLiteralNode) {
+            final FixnumLiteralNode fixnum = (FixnumLiteralNode) node;
+            nodeVal = Integer.toString(fixnum.getValue());
+        } else if (node instanceof MethodDefinitionNode) {
+            final MethodDefinitionNode defNode = (MethodDefinitionNode) node;
+            nodeVal = defNode.getName();
+        }
+        String result = node.getClass().getSimpleName();
+        if (nodeVal != null) {
+            result = result + "[\"" + nodeVal + "\"]";
+        }
+        return result;
+    }
+
+}
--- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProxyNode.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProxyNode.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -10,6 +10,7 @@
 package com.oracle.truffle.ruby.nodes.debug;
 
 import java.math.*;
+import java.util.*;
 
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.frame.*;
@@ -34,9 +35,16 @@
 
     public RubyProxyNode(RubyContext context, RubyNode child) {
         super(context, SourceSection.NULL);
-        this.child = adoptChild(child);
         assert !(child instanceof RubyProxyNode);
-        this.probeChain = new ProbeChain(child.getSourceSection(), null);
+        this.child = adoptChild(child);
+        this.probeChain = context.getDebugManager().getProbeChain(child.getSourceSection());
+    }
+
+    public RubyProxyNode(RubyContext context, RubyNode child, ProbeChain probeChain) {
+        super(context, SourceSection.NULL);
+        assert !(child instanceof RubyProxyNode);
+        this.child = adoptChild(child);
+        this.probeChain = probeChain;
     }
 
     @Override
@@ -189,4 +197,16 @@
         }
     }
 
+    public boolean isMarkedAs(NodePhylum phylum) {
+        return probeChain.isMarkedAs(phylum);
+    }
+
+    public Set<NodePhylum> getPhylumMarks() {
+        return probeChain.getPhylumMarks();
+    }
+
+    public void markAs(NodePhylum phylum) {
+        probeChain.markAs(phylum);
+    }
+
 }
--- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/literal/FixnumLiteralNode.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/literal/FixnumLiteralNode.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -35,7 +35,6 @@
         return value;
     }
 
-    // TODO(CS): remove this - shouldn't be fiddling with nodes from the outside
     public int getValue() {
         return value;
     }
--- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/methods/MethodDefinitionNode.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/methods/MethodDefinitionNode.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -87,4 +87,8 @@
         return executeMethod(frame);
     }
 
+    public String getName() {
+        return name;
+    }
+
 }
--- a/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -16,6 +16,8 @@
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.impl.*;
+import com.oracle.truffle.api.nodes.instrument.*;
+import com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.ProbeChain;
 import com.oracle.truffle.ruby.nodes.*;
 import com.oracle.truffle.ruby.nodes.call.*;
 import com.oracle.truffle.ruby.nodes.cast.*;
@@ -87,6 +89,15 @@
         nodeDefinedNames.put(org.jrubyparser.ast.DVarNode.class, "local-variable");
     }
 
+    private static final Set<String> debugIgnoredCalls = new HashSet<>();
+
+    static {
+        debugIgnoredCalls.add("downto");
+        debugIgnoredCalls.add("each");
+        debugIgnoredCalls.add("times");
+        debugIgnoredCalls.add("upto");
+    }
+
     /**
      * Global variables which in common usage have frame local semantics.
      */
@@ -289,7 +300,20 @@
 
         final ArgumentsAndBlockTranslation argumentsAndBlock = translateArgumentsAndBlock(sourceSection, block, args, extraArgument);
 
-        return new CallNode(context, sourceSection, node.getName(), receiverTranslated, argumentsAndBlock.getBlock(), argumentsAndBlock.isSplatted(), argumentsAndBlock.getArguments());
+        RubyNode translated = new CallNode(context, sourceSection, node.getName(), receiverTranslated, argumentsAndBlock.getBlock(), argumentsAndBlock.isSplatted(), argumentsAndBlock.getArguments());
+
+        if (context.getConfiguration().getDebug()) {
+            final CallNode callNode = (CallNode) translated;
+            if (!debugIgnoredCalls.contains(callNode.getName())) {
+
+                final RubyProxyNode proxy = new RubyProxyNode(context, translated);
+                proxy.markAs(NodePhylum.CALL);
+                proxy.getProbeChain().appendProbe(new RubyCallProbe(context, node.getName()));
+                translated = proxy;
+            }
+        }
+
+        return translated;
     }
 
     protected class ArgumentsAndBlockTranslation {
@@ -1147,6 +1171,7 @@
             } else {
                 proxy = new RubyProxyNode(context, translated);
             }
+            proxy.markAs(NodePhylum.ASSIGNMENT);
             context.getDebugManager().registerLocalDebugProxy(methodIdentifier, node.getName(), proxy.getProbeChain());
 
             translated = proxy;
@@ -1455,15 +1480,19 @@
         if (context.getConfiguration().getDebug()) {
 
             RubyProxyNode proxy;
-            SourceSection sourceSection;
             if (translated instanceof RubyProxyNode) {
                 proxy = (RubyProxyNode) translated;
-                sourceSection = proxy.getChild().getSourceSection();
+                if (proxy.getChild() instanceof CallNode) {
+                    // Special case; replace proxy with one registered by line, merge in information
+                    final CallNode callNode = (CallNode) proxy.getChild();
+                    final ProbeChain probeChain = proxy.getProbeChain();
+
+                    proxy = new RubyProxyNode(context, callNode, probeChain);
+                }
             } else {
                 proxy = new RubyProxyNode(context, translated);
-                sourceSection = translated.getSourceSection();
             }
-            context.getDebugManager().registerProbeChain(sourceSection, proxy.getProbeChain());
+            proxy.markAs(NodePhylum.STATEMENT);
             translated = proxy;
         }
 
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/RubyContext.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/RubyContext.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -42,6 +42,7 @@
     private final AtExitManager atExitManager;
     private final RubyDebugManager debugManager;
     private final SourceManager sourceManager;
+    private final ASTPrinter astPrinter;
 
     private AtomicLong nextObjectID = new AtomicLong(0);
 
@@ -50,14 +51,19 @@
     private POSIX posix = POSIXFactory.getPOSIX();
 
     public RubyContext(RubyParser parser) {
-        this(new Configuration(new ConfigurationBuilder()), parser);
+        this(new Configuration(new ConfigurationBuilder()), parser, null);
     }
 
     public RubyContext(Configuration configuration, RubyParser parser) {
+        this(configuration, parser, null);
+    }
+
+    public RubyContext(Configuration configuration, RubyParser parser, ASTPrinter astPrinter) {
         assert configuration != null;
 
         this.configuration = configuration;
         this.parser = parser;
+        this.astPrinter = astPrinter;
 
         objectSpaceManager = new ObjectSpaceManager(this);
         traceManager = new TraceManager(this);
@@ -91,6 +97,10 @@
         return debugManager;
     }
 
+    public ASTPrinter getASTPrinter() {
+        return astPrinter;
+    }
+
     public void implementationMessage(String format, Object... arguments) {
         System.err.println("rubytruffle: " + String.format(format, arguments));
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterLineProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2013, 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+/**
+ * A Ruby probe for halting execution at a line after a child execution method completes.
+ */
+public final class RubyBreakAfterLineProbe extends RubyLineProbe {
+
+    /**
+     * Creates a probe that will cause a halt when child execution is complete; a {@code oneShot}
+     * probe will remove itself the first time it halts.
+     */
+    public RubyBreakAfterLineProbe(RubyContext context, SourceLineLocation location, boolean oneShot) {
+        super(context, location, oneShot);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame) {
+        if (oneShot) {
+            // One-shot breakpoints retire after one activation.
+            context.getDebugManager().retireLineProbe(location, this);
+        }
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, boolean result) {
+        leave(astNode, frame);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, int result) {
+        leave(astNode, frame);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, double result) {
+        leave(astNode, frame);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        leave(astNode, frame);
+    }
+
+    @Override
+    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        leave(astNode, frame);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterLocalProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,55 @@
+/*
+ * 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+/**
+ * A Ruby probe for halting execution after a local assignment.
+ */
+public final class RubyBreakAfterLocalProbe extends RubyLocalProbe {
+
+    public RubyBreakAfterLocalProbe(RubyContext context, MethodLocal local) {
+        super(context, local, false);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame) {
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, boolean result) {
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, int result) {
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, double result) {
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+    @Override
+    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        context.getDebugManager().haltedAt(astNode, frame.materialize());
+    }
+
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- * Copyright (c) 2013 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 com.oracle.truffle.ruby.runtime.debug;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.ruby.runtime.*;
-
-/**
- * A Ruby probe for invoking a breakpoint shell after a child execution method completes.
- */
-public final class RubyBreakAfterProbe extends RubyProbe {
-
-    public RubyBreakAfterProbe(RubyContext context) {
-        super(context);
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, boolean result) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, int result) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, double result) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, Object result) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-    @Override
-    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakBeforeLineProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2013, 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+/**
+ * A probe for halting execution at a line before a child execution method.
+ */
+public final class RubyBreakBeforeLineProbe extends RubyLineProbe {
+
+    /**
+     * Creates a probe that will cause a halt just before child execution starts; a {@code oneShot}
+     * probe will remove itself the first time it halts.
+     */
+    public RubyBreakBeforeLineProbe(RubyContext context, SourceLineLocation location, boolean oneShot) {
+        super(context, location, oneShot);
+    }
+
+    @Override
+    public void enter(Node astNode, VirtualFrame frame) {
+
+        if (!isStepping()) {
+            // Ordinary line breakpoints ignored during stepping so no double halts.
+            if (oneShot) {
+                // One-shot breakpoints retire after one activation.
+                context.getDebugManager().retireLineProbe(location, this);
+            }
+            context.getDebugManager().haltedAt(astNode, frame.materialize());
+        }
+    }
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakBeforeProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * Copyright (c) 2013 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 com.oracle.truffle.ruby.runtime.debug;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.ruby.runtime.*;
-
-/**
- * A probe for invoking a breakpoint shell before a child execution method.
- */
-public final class RubyBreakBeforeProbe extends RubyProbe {
-
-    public RubyBreakBeforeProbe(RubyContext context) {
-        super(context);
-    }
-
-    @Override
-    public void enter(Node astNode, VirtualFrame frame) {
-        context.getDebugManager().haltedAt(astNode, frame.materialize());
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyCallProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,59 @@
+/*
+ * 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+public final class RubyCallProbe extends RubyProbe {
+
+    private final String name;
+
+    public RubyCallProbe(RubyContext context, String name) {
+        super(context, false);
+        this.name = name;
+    }
+
+    @Override
+    public void enter(Node astNode, VirtualFrame frame) {
+        context.getDebugManager().notifyCallEntry(astNode, name);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame) {
+        context.getDebugManager().notifyCallExit(astNode, name);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, boolean result) {
+        context.getDebugManager().notifyCallExit(astNode, name);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, int result) {
+        context.getDebugManager().notifyCallExit(astNode, name);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, double result) {
+        context.getDebugManager().notifyCallExit(astNode, name);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        context.getDebugManager().notifyCallExit(astNode, name);
+    }
+
+    @Override
+    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        context.getDebugManager().notifyCallExit(astNode, name);
+    }
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -9,38 +9,113 @@
  */
 package com.oracle.truffle.ruby.runtime.debug;
 
+import java.io.*;
 import java.util.*;
+import java.util.Map.Entry;
 
 import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.ProbeChain;
 import com.oracle.truffle.api.nodes.instrument.*;
-import com.oracle.truffle.api.nodes.instrument.InstrumentationProbeNode.ProbeChain;
 import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.ruby.runtime.*;
 import com.oracle.truffle.ruby.runtime.core.*;
 import com.oracle.truffle.ruby.runtime.methods.*;
 
 /**
- * Manager for Ruby AST execution.
+ * Manager for Ruby AST execution under debugging control.
  */
 public final class RubyDebugManager implements DebugManager {
 
-    // TODO (mlvdv) no REPL support yet for debugging "locals"; only lines
+    // TODO (mlvdv) no REPL support yet for debugging "locals" (assignment to local variables); only
+    // line-based step/next/return
+
+    private static final boolean TRACE = false;
+    private static final PrintStream OUT = System.out;
+
+    private static enum ExecutionMode {
+
+        /**
+         * Context: ordinary debugging execution, e.g. in response to a "Continue" request or a
+         * "Load-Run" request.
+         * <ul>
+         * <li>User breakpoints are enabled.</li>
+         * <li>Continue until either:
+         * <ol>
+         * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li>
+         * <li>execution completes.</li>
+         * </ol>
+         * </ul>
+         */
+        CONTINUE,
+
+        /**
+         * Context: per-statement stepping execution, e.g. in response to a "Step" request.
+         * <ul>
+         * <li>User breakpoints are disabled.</li>
+         * <li>Continue until either:
+         * <ol>
+         * <li>execution arrives at a "Statement" node, <strong>or:</strong></li>
+         * <li>execution completes.</li>
+         * </ol>
+         * </ul>
+         */
+        STEP,
+
+        /**
+         * Context: per-statement stepping in response to a "Next" request and when not nested in
+         * any function/method call.
+         * <ul>
+         * <li>User breakpoints are disabled.</li>
+         * <li>Continue until either:
+         * <ol>
+         * <li>execution arrives at a "Statement" node <strong>or:</strong></li>
+         * <li>the program completes <strong>or:</strong></li>
+         * <li>execution arrives at a function/method entry, in which case the mode changes to
+         * {@link #NEXT_NESTED} and execution continues.</li>
+         * </ol>
+         * </ul>
+         */
+        NEXT,
+
+        /**
+         * Context: ordinary debugging execution in response to a "Next" requested and when nested
+         * at least one deep in function/method calls.
+         * <ul>
+         * <li>User breakpoints are enabled.</li>
+         * <li>Execute until either:
+         * <ol>
+         * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li>
+         * <li>execution completes, <strong>or:</strong></li>
+         * <li>execution returns from all nested function/method calls, in which case the mode
+         * changes to {@link #NEXT} and execution continues.</li>
+         * </ol>
+         * </ul>
+         */
+        NEXT_NESTED;
+    }
 
     private static enum BreakpointStatus {
 
         /**
-         * Created for a source location but not yet attached for some legitimate reason: new and
-         * not yet attached; new and the source file hasn't been loaded yet; old and the source file
-         * is in the process of being reloaded.
+         * Created for a source location but not yet attached for some legitimate reason: perhaps
+         * newly created and not yet attached; perhaps newly created and the source file hasn't been
+         * loaded yet; perhaps old and the source file is in the process of being reloaded.
          */
         PENDING("Pending"),
 
         /**
-         * Has an active break probe in the AST.
+         * Has a {@link RubyProbe}, which is attached to a {@linkplain ProbeChain known location} in
+         * the AST.
          */
-        ATTACHED("Active"),
+        ACTIVE("Active"),
+
+        /**
+         * Has a {@link RubyProbe}, which is associated with a {@linkplain ProbeChain known
+         * location} in the AST, but which has been temporarily removed.
+         */
+        DISABLED("Disabled"),
 
         /**
          * Should be attached, but the line location cannot be found in the source.
@@ -64,19 +139,45 @@
         }
     }
 
-    /**
-     * Map: Source lines ==> source chains known to be at line locations in an AST.
-     */
-    private final Map<SourceLineLocation, ProbeChain> linesToProbeChains = new HashMap<>();
-
     private final Set<Source> loadedSources = new HashSet<>();
 
     private Source beingLoaded = null;
 
     /**
-     * Map: Source lines ==> attached Breakpoints/procs to be activated before execution at line.
+     * The current mode of execution.
+     */
+    private ExecutionMode executionMode = ExecutionMode.CONTINUE;
+
+    /**
+     * When running in "step" mode, this is the number of steps that haven't yet completed.
+     */
+    private int unfinishedStepCount = 0;
+
+    /**
+     * When running in "next" mode, this is the number of steps that haven't yet completed.
+     */
+    private int unfinishedNextCount = 0;
+    /**
+     * When running in "next" mode, this is non-null when running a function/method that must be
+     * continued across.
      */
-    private final Map<SourceLineLocation, RubyLineBreakpoint> linesToBreakpoints = new TreeMap<>();
+    private Node nextNestedInCallNode = null;
+
+    /**
+     * Map: SourceSection ==> probe chain associated with that source section in an AST.
+     */
+    private final Map<SourceSection, ProbeChain> srcToProbeChain = new HashMap<>();
+
+    /**
+     * Map: Source lines ==> probe chains associated with source sections starting on the line.
+     */
+    private final Map<SourceLineLocation, Set<ProbeChain>> lineToProbeChains = new HashMap<>();
+
+    /**
+     * Map: Source lines ==> attached Breakpoints/procs to be activated before execution at line.
+     * There should be no more than one line breakpoint associated with a line.
+     */
+    private final Map<SourceLineLocation, RubyLineBreakpoint> lineToBreakpoint = new TreeMap<>();
 
     /**
      * Map: Method locals in AST ==> Method local assignments where breakpoints can be attached.
@@ -94,25 +195,58 @@
         this.context = context;
     }
 
+    /**
+     * Gets the {@linkplain ProbeChain probe} associated with a particular {@link SourceSection
+     * source location}, creating a new one if needed. There should only be one probe associated
+     * with each {@linkplain SourceSection source location}.
+     */
+    public ProbeChain getProbeChain(SourceSection sourceSection) {
+        assert sourceSection != null;
+        assert sourceSection.getSource().equals(beingLoaded);
+
+        ProbeChain probeChain = srcToProbeChain.get(sourceSection);
+
+        if (probeChain != null) {
+            return probeChain;
+        }
+        probeChain = new ProbeChain(context, sourceSection, null);
+
+        // Register new ProbeChain by unique SourceSection
+        srcToProbeChain.put(sourceSection, probeChain);
+
+        // Register new ProbeChain by source line, there may be more than one
+        // Create line location for map key
+        final SourceLineLocation lineLocation = new SourceLineLocation(sourceSection.getSource(), sourceSection.getStartLine());
+
+        Set<ProbeChain> probeChains = lineToProbeChains.get(lineLocation);
+        if (probeChains == null) {
+            probeChains = new HashSet<>();
+            lineToProbeChains.put(lineLocation, probeChains);
+        }
+        probeChains.add(probeChain);
+
+        return probeChain;
+    }
+
     public void notifyStartLoading(Source source) {
 
         beingLoaded = source;
 
-        // Forget all the probe chains from previous loading
-        final List<SourceLineLocation> locations = new ArrayList<>();
-        for (SourceLineLocation lineLocation : linesToProbeChains.keySet()) {
-            if (lineLocation.getSource().equals(beingLoaded)) {
-                locations.add(lineLocation);
-            }
-        }
-        for (SourceLineLocation lineLocation : locations) {
-            linesToProbeChains.remove(lineLocation);
-        }
-
-        // Forget whatever we knew, and detach from old AST/ProbeChain if needed
-        for (RubyLineBreakpoint breakpoint : linesToBreakpoints.values()) {
+        /**
+         * We'd like to know when we're reloading a file if the old AST is completely dead, so that
+         * we can correctly identify the state of breakpoints related to it, but that doesn't seem
+         * possible.
+         * 
+         * Before we start, find any breakpoints that never got attached, which get reported as
+         * errors. Revert them to "pending", in case their lines are found this time around.
+         */
+        for (RubyLineBreakpoint breakpoint : lineToBreakpoint.values()) {
             if (breakpoint.getSourceLineLocation().getSource().equals(beingLoaded)) {
-                breakpoint.setPending();
+                if (breakpoint.status == BreakpointStatus.ERROR) {
+                    // It was an error, which means we have not yet found that line for this Source.
+                    // It might show up while loading this time, so make it pending.
+                    breakpoint.setPending();
+                }
             }
         }
     }
@@ -120,51 +254,66 @@
     public void notifyFinishedLoading(Source source) {
         assert source == beingLoaded;
 
-        // Any pending breakpoints are now erroneous, didn't find the line.
-        for (RubyLineBreakpoint breakpoint : linesToBreakpoints.values()) {
+        // Update any pending breakpoints associated with this source
+
+        for (RubyLineBreakpoint breakpoint : lineToBreakpoint.values()) {
             if (breakpoint.getSourceLineLocation().getSource().equals(beingLoaded)) {
                 if (breakpoint.status == BreakpointStatus.PENDING) {
-                    breakpoint.setError();
+                    final ProbeChain probeChain = findProbeChain(breakpoint.location);
+                    if (probeChain == null) {
+                        breakpoint.setError();
+                    } else {
+                        breakpoint.attach(probeChain);
+                    }
                 }
             }
         }
-
         loadedSources.add(source);
         beingLoaded = null;
-
     }
 
     /**
-     * Notifies the manager about creation of a newly created probe chain associated with a proxy
-     * for an AST node at a specific location in the source text.
+     * Returns a {@link ProbeChain} associated with source that starts on a specified line; if there
+     * are more than one, return the one with the first character location.
      */
-    public void registerProbeChain(SourceSection sourceSection, ProbeChain probeChain) {
-
-        assert sourceSection.getSource().equals(beingLoaded);
+    private ProbeChain findProbeChain(SourceLineLocation lineLocation) {
+        ProbeChain probeChain = null;
+        final Set<ProbeChain> probeChains = lineToProbeChains.get(lineLocation);
+        if (probeChains != null) {
+            assert probeChains.size() > 0;
+            for (ProbeChain chain : probeChains) {
+                if (probeChain == null) {
+                    probeChain = chain;
+                } else if (chain.getProbedSourceSection().getCharIndex() < probeChain.getProbedSourceSection().getCharIndex()) {
+                    probeChain = chain;
+                }
+            }
+        }
+        return probeChain;
+    }
 
-        // Remember this probe chain, indexed by line number
-        final SourceLineLocation lineLocation = new SourceLineLocation(sourceSection.getSource(), sourceSection.getStartLine());
-        linesToProbeChains.put(lineLocation, probeChain);
+    /**
+     * Remove a probe from a line location and retire it permanently.
+     */
+    public void retireLineProbe(SourceLineLocation location, RubyLineProbe probe) {
+        final RubyLineBreakpoint breakpoint = lineToBreakpoint.get(location);
+        lineToBreakpoint.remove(location);
+        breakpoint.retire(probe);
+    }
 
-        final RubyLineBreakpoint breakpoint = linesToBreakpoints.get(lineLocation);
-        if (breakpoint != null && breakpoint.location.equals(lineLocation)) {
-            // We only register while we're loading;
-            // While we're loading, there should only be pending breakpoints for this source
-            assert breakpoint.status == BreakpointStatus.PENDING;
-
-            // Found a line/probeChain where a pending breakpoint should be set
-            breakpoint.attach(probeChain);
-        }
+    @Override
+    public LineBreakpoint[] getBreakpoints() {
+        return lineToBreakpoint.values().toArray(new LineBreakpoint[0]);
     }
 
     @Override
     public RubyLineBreakpoint setBreakpoint(SourceLineLocation lineLocation) {
 
-        RubyLineBreakpoint breakpoint = linesToBreakpoints.get(lineLocation);
+        RubyLineBreakpoint breakpoint = lineToBreakpoint.get(lineLocation);
 
         if (breakpoint != null) {
             switch (breakpoint.status) {
-                case ATTACHED:
+                case ACTIVE:
                     throw new RuntimeException("Breakpoint already set at line " + lineLocation);
 
                 case PENDING:
@@ -175,10 +324,10 @@
                     assert false;
             }
         } else {
-            breakpoint = new RubyLineBreakpoint(lineLocation, new RubyBreakBeforeProbe(context));
-            linesToBreakpoints.put(lineLocation, breakpoint);
+            breakpoint = new RubyLineBreakpoint(lineLocation, new RubyBreakBeforeLineProbe(context, lineLocation, false));
+            lineToBreakpoint.put(lineLocation, breakpoint);
 
-            final ProbeChain probeChain = linesToProbeChains.get(lineLocation);
+            final ProbeChain probeChain = findProbeChain(lineLocation);
             if (probeChain != null) {
                 breakpoint.attach(probeChain);
             }
@@ -193,30 +342,12 @@
     }
 
     @Override
-    public LineBreakpoint[] getBreakpoints() {
-        return linesToBreakpoints.values().toArray(new LineBreakpoint[0]);
-    }
-
-    @Override
-    public void removeBreakpoint(SourceLineLocation lineLocation) {
-        final RubyLineBreakpoint breakpoint = linesToBreakpoints.get(lineLocation);
-        if (breakpoint == null) {
-            throw new RuntimeException("No break/proc located at line " + lineLocation);
-        }
-        linesToBreakpoints.remove(lineLocation);
-        breakpoint.retire();
-    }
-
-    /**
-     * Sets a Ruby proc of no arguments to be run before a specified line is executed.
-     */
-    public void setLineProc(SourceLineLocation lineLocation, RubyProc proc) {
-
-        RubyLineBreakpoint breakpoint = linesToBreakpoints.get(lineLocation);
+    public LineBreakpoint setOneShotBreakpoint(SourceLineLocation lineLocation) {
+        RubyLineBreakpoint breakpoint = lineToBreakpoint.get(lineLocation);
 
         if (breakpoint != null) {
             switch (breakpoint.status) {
-                case ATTACHED:
+                case ACTIVE:
                     throw new RuntimeException("Breakpoint already set at line " + lineLocation);
 
                 case PENDING:
@@ -227,18 +358,165 @@
                     assert false;
             }
         } else {
-            breakpoint = new RubyLineBreakpoint(lineLocation, new RubyProcBeforeProbe(context, proc));
-            linesToBreakpoints.put(lineLocation, breakpoint);
+            breakpoint = new RubyLineBreakpoint(lineLocation, new RubyBreakBeforeLineProbe(context, lineLocation, true));
+            lineToBreakpoint.put(lineLocation, breakpoint);
+
+            final ProbeChain probeChain = findProbeChain(lineLocation);
+            if (probeChain != null) {
+                breakpoint.attach(probeChain);
+            }
+        }
+
+        return breakpoint;
+    }
+
+    public boolean hasBreakpoint(SourceLineLocation lineLocation) {
+        return lineToBreakpoint.get(lineLocation) != null;
+    }
+
+    @Override
+    public void removeBreakpoint(SourceLineLocation lineLocation) {
+        final RubyLineBreakpoint breakpoint = lineToBreakpoint.get(lineLocation);
+        if (breakpoint == null) {
+            throw new RuntimeException("No break/proc located at line " + lineLocation);
+        }
+        lineToBreakpoint.remove(lineLocation);
+        breakpoint.retire();
+    }
+
+    private void removeOneShotBreakpoints() {
+        for (Entry<SourceLineLocation, RubyLineBreakpoint> entry : lineToBreakpoint.entrySet()) {
+            final RubyLineBreakpoint breakpoint = entry.getValue();
+            if (breakpoint.probe.isOneShot()) {
+                lineToBreakpoint.remove(entry.getKey());
+                breakpoint.retire();
+            }
+        }
+    }
+
+    /**
+     * Prepare to execute a "Continue":
+     * <ul>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at a node to which a breakpoint is attached, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * </ul>
+     */
+    public void setContinue() {
+        // Nothing to do here; "Continue" is the default, which should be restored after each halt.
+    }
+
+    /**
+     * Prepare to execute a "Step":
+     * <ul>
+     * <li>User breakpoints are disabled.</li>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at a "Statement" node, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * This status persists only through one execution, and reverts to
+     * {@link ExecutionMode#CONTINUE}.
+     * </ul>
+     */
+    public void setStep(int stepCount) {
+        assert executionMode == ExecutionMode.CONTINUE;
+        disableLineBreakpoints();
+        setStepping(true);
+        unfinishedStepCount = stepCount;
+        setMode(ExecutionMode.STEP);
+    }
 
-            final ProbeChain probeChain = linesToProbeChains.get(lineLocation);
+    /**
+     * Prepare to execute a "Next":
+     * <ul>
+     * <li>Execution will continue until either:
+     * <ol>
+     * <li>execution arrives at a "Statement" node when not nested in one or more function/method
+     * calls, <strong>or:</strong></li>
+     * <li>execution arrives at a node to which a breakpoint is attached and when nested in one or
+     * more function/method calls, <strong>or:</strong></li>
+     * <li>execution completes.</li>
+     * </ol>
+     * This status persists only through one execution, and reverts to
+     * {@link ExecutionMode#CONTINUE}.
+     * </ul>
+     */
+    public void setNext(int nextCount) {
+        assert executionMode == ExecutionMode.CONTINUE;
+        disableLineBreakpoints();
+        setStepping(true);
+        unfinishedNextCount = nextCount;
+        setMode(ExecutionMode.NEXT);
+    }
+
+    private void disableLineBreakpoints() {
+        for (RubyLineBreakpoint breakpoint : lineToBreakpoint.values()) {
+            if (breakpoint.status == BreakpointStatus.ACTIVE) {
+                breakpoint.disable();
+            }
+        }
+    }
+
+    private void enableLineBreakpoints() {
+        for (RubyLineBreakpoint breakpoint : lineToBreakpoint.values()) {
+            if (breakpoint.status == BreakpointStatus.DISABLED) {
+                breakpoint.enable();
+            }
+        }
+    }
+
+    private void setStepping(boolean isStepping) {
+        // Set the "stepping" flag on every statement probe.
+        for (ProbeChain probeChain : srcToProbeChain.values()) {
+            if (probeChain.isMarkedAs(NodePhylum.STATEMENT)) {
+                probeChain.setStepping(isStepping);
+            }
+        }
+    }
+
+    private void setMode(ExecutionMode mode) {
+        if (TRACE) {
+            OUT.println("DebugManager: " + executionMode.toString() + "-->" + mode.toString());
+        }
+        executionMode = mode;
+    }
+
+    /**
+     * Sets a Ruby proc of no arguments to be run before a specified line is executed.
+     */
+    public void setLineProc(SourceLineLocation lineLocation, RubyProc proc) {
+
+        RubyLineBreakpoint breakpoint = lineToBreakpoint.get(lineLocation);
+
+        if (breakpoint != null) {
+            switch (breakpoint.status) {
+                case ACTIVE:
+                    throw new RuntimeException("Breakpoint already set at line " + lineLocation);
+
+                case PENDING:
+                case ERROR:
+                    throw new RuntimeException("Breakpoint already pending at line " + lineLocation);
+
+                default:
+                    assert false;
+            }
+        } else {
+            breakpoint = new RubyLineBreakpoint(lineLocation, new RubyProcBeforeLineProbe(context, lineLocation, proc));
+            lineToBreakpoint.put(lineLocation, breakpoint);
+
+            final ProbeChain probeChain = findProbeChain(lineLocation);
             if (probeChain != null) {
                 breakpoint.attach(probeChain);
             }
         }
     }
 
+    // TODO (mlvdv) rework locals (watchpoints) to work like breaks; I doubt it is even correct now
     /**
-     * Registers the chain of probes associated with a method local variable in the AST.
+     * Registers the chain of probes associated with a method local variable assignment in the AST.
      */
     public void registerLocalDebugProxy(UniqueMethodIdentifier methodIdentifier, String localName, ProbeChain probeChain) {
         final MethodLocal methodLocal = new MethodLocal(methodIdentifier, localName);
@@ -258,7 +536,7 @@
         if (probe != null) {
             throw new RuntimeException("Breakpoint already set on method local " + methodLocal);
         }
-        probe = new RubyBreakAfterProbe(context);
+        probe = new RubyBreakAfterLocalProbe(context, methodLocal);
         localsToAttachedBreakpoints.put(methodLocal, probe);
         probeChain.appendProbe(probe);
     }
@@ -277,7 +555,7 @@
         if (probe != null) {
             throw new RuntimeException("Assignment proc already set on method local " + methodLocal);
         }
-        probe = new RubyProcAfterProbe(context, proc);
+        probe = new RubyProcAfterLocalProbe(context, methodLocal, proc);
         localsToAttachedBreakpoints.put(methodLocal, probe);
         probeChain.appendProbe(probe);
     }
@@ -295,24 +573,72 @@
         localsToAttachedBreakpoints.remove(methodLocal);
     }
 
-    /**
-     * Receives notification of a suspended execution context; execution resumes when this method
-     * returns.
-     * 
-     * @param astNode a guest language AST node that represents the current execution site, assumed
-     *            not to be any kind of {@link InstrumentationNode},
-     * @param frame execution frame at the site where execution suspended
-     */
     public void haltedAt(Node astNode, MaterializedFrame frame) {
+        switch (executionMode) {
+            case CONTINUE:
+            case NEXT_NESTED:
+                // User breakpoints should already be enabled
+                // Stepping should be false
+                nextNestedInCallNode = null;
+                break;
+            case STEP:
+                unfinishedStepCount--;
+                if (unfinishedStepCount > 0) {
+                    return;
+                }
+                // Revert to default mode.
+                enableLineBreakpoints();
+                setStepping(false);
+                break;
+            case NEXT:
+                unfinishedNextCount--;
+                if (unfinishedNextCount > 0) {
+                    return;
+                }
+                // Revert to default mode.
+                enableLineBreakpoints();
+                setStepping(false);
+                break;
+            default:
+                assert false;  // Should not happen
+                break;
+
+        }
+        // Clean up, just in cased the one-shot breakpoints got confused
+        removeOneShotBreakpoints();
+
+        setMode(ExecutionMode.CONTINUE);
+
+        // Return control to the debug client
         context.haltedAt(astNode, frame);
+
     }
 
-    private static final class RubyLineBreakpoint implements DebugManager.LineBreakpoint, Comparable {
+    private RubyProbe createReplacement(RubyProbe probe) {
+        // Should be a specialized replacement for any kind of probe created.
+        // Ugly, but there's no other way to reset the parent pointer and reuse a probe node.
+        if (probe instanceof RubyBreakBeforeLineProbe) {
+            final RubyBreakBeforeLineProbe oldProbe = (RubyBreakBeforeLineProbe) probe;
+            return new RubyBreakBeforeLineProbe(context, oldProbe.getLineLocation(), oldProbe.isOneShot());
+        }
+        if (probe instanceof RubyProcBeforeLineProbe) {
+            final RubyProcBeforeLineProbe oldProbe = (RubyProcBeforeLineProbe) probe;
+            return new RubyProcBeforeLineProbe(context, oldProbe.getLineLocation(), oldProbe.getProc());
+        }
+        assert false;
+        return null;
+    }
+
+    /**
+     * A breakpoint of the sort that would be created by a client, with a life-cycle represented by
+     * {@link BreakpointStatus}.
+     */
+    private final class RubyLineBreakpoint implements DebugManager.LineBreakpoint, Comparable {
 
         private final SourceLineLocation location;
 
         private RubyProbe probe;  // non-null until RETIRED, but may get replaced.
-        private ProbeChain probeChain = null;  // only non-null when ATTACHED
+        private ProbeChain probeChain = null;
         private BreakpointStatus status = BreakpointStatus.PENDING;
 
         public RubyLineBreakpoint(SourceLineLocation location, RubyProbe probe) {
@@ -332,7 +658,11 @@
 
         @Override
         public String getDebugStatus() {
-            return status == null ? "<none>" : status.name;
+            String result = status == null ? "<none>" : status.name;
+            if (probe.isOneShot()) {
+                result = result + ", " + "One-Shot";
+            }
+            return result;
         }
 
         private void attach(ProbeChain chain) {
@@ -341,27 +671,29 @@
             probeChain = chain;
             probeChain.appendProbe(probe);
 
-            status = BreakpointStatus.ATTACHED;
+            status = BreakpointStatus.ACTIVE;
+        }
+
+        private void disable() {
+            assert status == BreakpointStatus.ACTIVE;
+
+            probeChain.removeProbe(probe);
+            status = BreakpointStatus.DISABLED;
+        }
+
+        private void enable() {
+            assert status == BreakpointStatus.DISABLED;
+
+            // Can't re-attach to probe chain, because can't re-assign parent.
+            probe = createReplacement(probe);
+            probeChain.appendProbe(probe);
+            status = BreakpointStatus.ACTIVE;
         }
 
         private void setPending() {
-            switch (status) {
-                case ATTACHED:
-                    detach();
-                    // TODO (mlvdv) replace the probe
-                    status = BreakpointStatus.PENDING;
-                    break;
-                case ERROR:
-                    status = BreakpointStatus.PENDING;
-                    break;
-                case PENDING:
-                    break;
-                case RETIRED:
-                    assert false;
-                    break;
-                default:
-                    assert false;
-            }
+            assert status == BreakpointStatus.ERROR;
+
+            status = BreakpointStatus.PENDING;
         }
 
         public void setError() {
@@ -370,18 +702,9 @@
             status = BreakpointStatus.ERROR;
         }
 
-        private void detach() {
-            assert status == BreakpointStatus.ATTACHED;
-
-            probeChain.removeProbe(probe);
-            probeChain = null;
-
-            status = BreakpointStatus.PENDING;
-        }
-
         private void retire() {
 
-            if (probeChain != null) {
+            if (status == BreakpointStatus.ACTIVE) {
                 probeChain.removeProbe(probe);
             }
             probe = null;
@@ -389,6 +712,151 @@
 
             status = BreakpointStatus.RETIRED;
         }
+
+        private void retire(RubyProbe retiredProbe) {
+
+            assert this.probe == retiredProbe;
+            retire();
+        }
+    }
+
+    private static final class CallRecord {
+
+        final SourceSection section;
+        @SuppressWarnings("unused") final String name;
+        final CallRecord predecessor;
+
+        public CallRecord(SourceSection section, String name, CallRecord predecessor) {
+            this.section = section;
+            this.name = name;
+            this.predecessor = predecessor;
+        }
+    }
+
+    private CallRecord callStack = null;
+
+    public void notifyCallEntry(Node astNode, String name) {
+        if (TRACE) {
+            OUT.println("DebugManager: ENTER \"" + name + "\" " + nodeToString(astNode));
+        }
+        if (executionMode == ExecutionMode.NEXT && nextNestedInCallNode == null) {
+            // In "Next" mode, where we have been "stepping", but are about to enter a call.
+            // Switch modes to be like "Continue" until/if return from this call
+            nextNestedInCallNode = astNode;
+            enableLineBreakpoints();
+            setStepping(false);
+            setMode(ExecutionMode.NEXT_NESTED);
+        }
+
+        callStack = new CallRecord(astNode.getSourceSection(), name, callStack);
+    }
+
+    public void notifyCallExit(Node astNode, String name) {
+        if (TRACE) {
+            OUT.println("DebugManager: EXIT \"" + name + "\" " + nodeToString(astNode));
+        }
+
+        if (executionMode == ExecutionMode.NEXT_NESTED) {
+            assert nextNestedInCallNode != null;
+            if (nextNestedInCallNode == astNode) {
+                // In "Next" mode while nested in a function/method call, but about to return.
+                // Switch modes to be like "Step" until/if enter another function/method call.
+                nextNestedInCallNode = null;
+                disableLineBreakpoints();
+                setStepping(true);
+                setMode(ExecutionMode.NEXT);
+            }
+        }
+
+        final SourceSection section = astNode.getSourceSection();
+        if (section instanceof NullSourceSection) {
+            if (TRACE) {
+                OUT.println("Ignoring call exit \"" + name + "\" " + nodeToString(astNode));
+            }
+        }
+        callStack = callStack.predecessor;
+    }
+
+    /**
+     * Sets a one-shot breakpoint to halt just after the completion of the call site at the top of
+     * the current call stack.
+     */
+    public boolean setReturnBreakpoint() {
+        if (callStack == null) {
+            return false;
+        }
+        final SourceLineLocation lineLocation = new SourceLineLocation(callStack.section);
+        RubyLineBreakpoint breakpoint = lineToBreakpoint.get(lineLocation);
+        if (breakpoint != null) {
+            return true;
+        }
+        final ProbeChain probeChain = findProbeChain(lineLocation);
+        if (probeChain != null) {
+            breakpoint = new RubyLineBreakpoint(lineLocation, new RubyBreakAfterLineProbe(context, lineLocation, true));
+            lineToBreakpoint.put(lineLocation, breakpoint);
+            breakpoint.attach(probeChain);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Notifies that a new execution is about to start, i.e. running a program or an eval.
+     */
+    @SuppressWarnings("static-method")
+    public void startExecution(String name) {
+        if (TRACE) {
+            OUT.println("RubyDebugManager: START " + name);
+        }
+        // TODO (mlvdv) push the current call stack onto a stack; start new empty call stack
+    }
+
+    /**
+     * Notifies that the current execution has ended.
+     */
+    public void endExecution(String name) {
+        if (TRACE) {
+            OUT.println("RubyDebugManager: END " + name);
+        }
+
+        // TODO (mlvdv) pop the current call stack, restore previous
+
+        switch (executionMode) {
+            case CONTINUE:
+            case NEXT_NESTED:
+                // User breakpoints should already be enabled
+                // Stepping should be false
+                nextNestedInCallNode = null;
+                break;
+            case STEP:
+                // Revert to default mode.
+                enableLineBreakpoints();
+                setStepping(false);
+                unfinishedStepCount = 0;
+                break;
+            case NEXT:
+                // Revert to default mode.
+                enableLineBreakpoints();
+                setStepping(false);
+                unfinishedNextCount = 0;
+                break;
+            default:
+                assert false;  // Should not happen
+                break;
+        }
+        // Clean up, just in cased the one-shot breakpoints got confused
+        removeOneShotBreakpoints();
+
+        setMode(ExecutionMode.CONTINUE);
+    }
+
+    @SuppressWarnings("static-method")
+    private String nodeToString(Node astNode) {
+        final SourceSection sourceSection = astNode.getSourceSection();
+        if (sourceSection != null) {
+            return Integer.toString(sourceSection.getStartLine()) + ":" + astNode;
+        }
+        return astNode.toString();
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyLineProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2013, 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+/**
+ * A Ruby probe situated at a Ruby "line".
+ */
+public abstract class RubyLineProbe extends RubyProbe {
+
+    protected final SourceLineLocation location;
+
+    /**
+     * Creates a probe that will cause a halt just before child execution starts; a {@code oneShot}
+     * probe will remove itself the first time it halts.
+     */
+    public RubyLineProbe(RubyContext context, SourceLineLocation location, boolean oneShot) {
+        super(context, oneShot);
+        this.location = location;
+    }
+
+    public SourceLineLocation getLineLocation() {
+        return location;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyLocalProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2013, 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.ruby.runtime.*;
+
+/**
+ * A Ruby probe situated at a Ruby local assignment.
+ */
+public abstract class RubyLocalProbe extends RubyProbe {
+
+    protected final MethodLocal local;
+
+    public RubyLocalProbe(RubyContext context, MethodLocal local, boolean oneShot) {
+        super(context, oneShot);
+        this.local = local;
+    }
+
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -18,14 +18,24 @@
  */
 public abstract class RubyProbe extends InstrumentationProbeNode.DefaultProbeNode {
 
+    protected final boolean oneShot;
+
     protected final RubyContext context;
 
-    public RubyProbe(RubyContext context) {
+    /**
+     * OneShot is this a one-shot (self-removing) probe?
+     */
+    public RubyProbe(RubyContext context, boolean oneShot) {
+        super(context);
+        this.oneShot = oneShot;
         this.context = context;
-        assert context != null;
     }
 
-    public RubyContext getContext() {
-        return context;
+    /**
+     * Is this a one-shot (self-removing) probe? If so, it will remove itself the first time
+     * activated.
+     */
+    public boolean isOneShot() {
+        return oneShot;
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterLineProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,60 @@
+/*
+ * 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+
+/**
+ * A probe for instrumenting a Ruby program with a Ruby procedure to run on the return value from
+ * node execution at a line.
+ */
+public final class RubyProcAfterLineProbe extends RubyLineProbe {
+
+    private final RubyProc proc;
+
+    public RubyProcAfterLineProbe(RubyContext context, SourceLineLocation location, RubyProc proc) {
+        super(context, location, false);
+        this.proc = proc;
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame) {
+        proc.call(frame.pack());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, boolean result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, int result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, double result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        proc.call(frame.pack());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterLocalProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2013 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+
+/**
+ * A probe for instrumenting a Ruby program with a Ruby procedure to run on the return value from a
+ * local assignment.
+ */
+public final class RubyProcAfterLocalProbe extends RubyLocalProbe {
+
+    private final RubyProc proc;
+
+    public RubyProcAfterLocalProbe(RubyContext context, MethodLocal local, RubyProc proc) {
+        super(context, local, false);
+        this.proc = proc;
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame) {
+        proc.call(frame.pack());
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, boolean result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, int result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, double result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        proc.call(frame.pack(), result);
+    }
+
+    @Override
+    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
+        proc.call(frame.pack());
+    }
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright (c) 2013 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 com.oracle.truffle.ruby.runtime.debug;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.ruby.runtime.*;
-import com.oracle.truffle.ruby.runtime.core.*;
-
-/**
- * A probe for instrumenting a Ruby program with a Ruby procedure to run on the return value from
- * node execution.
- */
-public final class RubyProcAfterProbe extends RubyProbe {
-
-    private final RubyProc proc;
-
-    public RubyProcAfterProbe(RubyContext context, RubyProc proc) {
-        super(context);
-        this.proc = proc;
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame) {
-        proc.call(frame.pack());
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, boolean result) {
-        proc.call(frame.pack(), result);
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, int result) {
-        proc.call(frame.pack(), result);
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, double result) {
-        proc.call(frame.pack(), result);
-    }
-
-    @Override
-    public void leave(Node astNode, VirtualFrame frame, Object result) {
-        proc.call(frame.pack(), result);
-    }
-
-    @Override
-    public void leaveExceptional(Node astNode, VirtualFrame frame, Exception e) {
-        proc.call(frame.pack());
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcBeforeLineProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2013, 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 com.oracle.truffle.ruby.runtime.debug;
+
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+
+/**
+ * A probe for instrumenting a Ruby program with a Ruby procedure to run before a calling a child
+ * node at a line.
+ */
+public final class RubyProcBeforeLineProbe extends RubyLineProbe {
+
+    private final RubyProc proc;
+
+    public RubyProcBeforeLineProbe(RubyContext context, SourceLineLocation location, RubyProc proc) {
+        super(context, location, false);
+        this.proc = proc;
+    }
+
+    @Override
+    public void enter(Node astNode, VirtualFrame frame) {
+        proc.call(frame.pack());
+    }
+
+    public RubyProc getProc() {
+        return proc;
+    }
+
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcBeforeProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-/*
- * Copyright (c) 2013 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 com.oracle.truffle.ruby.runtime.debug;
-
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.ruby.runtime.*;
-import com.oracle.truffle.ruby.runtime.core.*;
-
-/**
- * A probe for instrumenting a Ruby program with a Ruby procedure to run before a call.
- */
-public final class RubyProcBeforeProbe extends RubyProbe {
-
-    private final RubyProc proc;
-
-    public RubyProcBeforeProbe(RubyContext context, RubyProc proc) {
-        super(context);
-        this.proc = proc;
-    }
-
-    @Override
-    public void enter(Node astNode, VirtualFrame frame) {
-        proc.call(frame.pack());
-    }
-
-}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java	Wed Jan 08 22:59:53 2014 +0100
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java	Wed Jan 08 15:49:18 2014 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013 Oracle and/or its affiliates. All rights reserved. This
+ * Copyright (c) 2013, 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:
  *
@@ -26,7 +26,7 @@
     @CompilerDirectives.CompilationFinal private boolean tracingEverEnabled = false;
 
     public RubyTraceProbe(RubyContext context) {
-        super(context);
+        super(context, false);
         this.notTracingAssumption = context.getTraceManager().getNotTracingAssumption();
     }