changeset 13918:22bf5a8ba9eb

Ruby: restore prototype debugger.
author Chris Seaton <chris.seaton@oracle.com>
date Mon, 10 Feb 2014 03:39:21 +0000
parents f2345d7c52ef
children 9d70445ea369
files graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/core/CoreMethodNodeManager.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveEnterDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveLeaveDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveLineDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveLocalDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/DebugNodes.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveEnterDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveLeaveDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveLineDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveLocalDebugProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProbe.java graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyTraceProbe.java graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/DefaultRubyNodeInstrumenter.java graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/RubyContext.java graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/MethodLocal.java graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java
diffstat 19 files changed, 804 insertions(+), 99 deletions(-) [+]
line wrap: on
line diff
--- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/core/CoreMethodNodeManager.java	Mon Feb 10 03:37:32 2014 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/core/CoreMethodNodeManager.java	Mon Feb 10 03:39:21 2014 +0000
@@ -17,6 +17,7 @@
 import com.oracle.truffle.api.nodes.*;
 import com.oracle.truffle.ruby.nodes.*;
 import com.oracle.truffle.ruby.nodes.control.*;
+import com.oracle.truffle.ruby.nodes.debug.*;
 import com.oracle.truffle.ruby.nodes.methods.arguments.*;
 import com.oracle.truffle.ruby.nodes.objects.*;
 import com.oracle.truffle.ruby.runtime.*;
@@ -74,6 +75,7 @@
         getMethods(methods, ThreadNodesFactory.getFactories());
         getMethods(methods, TimeNodesFactory.getFactories());
         getMethods(methods, TrueClassNodesFactory.getFactories());
+        getMethods(methods, DebugNodesFactory.getFactories());
         return methods;
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveEnterDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,49 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+
+public abstract class ActiveEnterDebugProbe extends RubyProbe {
+
+    private final Assumption activeAssumption;
+
+    private final InlinableMethodImplementation inlinable;
+    private final RubyRootNode inlinedRoot;
+
+    public ActiveEnterDebugProbe(RubyContext context, Assumption activeAssumption, RubyProc proc) {
+        super(context, false);
+        this.activeAssumption = activeAssumption;
+        inlinable = ((InlinableMethodImplementation) proc.getMethod().getImplementation());
+        inlinedRoot = inlinable.getCloneOfPristineRootNode();
+    }
+
+    @Override
+    public void enter(Node astNode, VirtualFrame frame) {
+        try {
+            activeAssumption.check();
+        } catch (InvalidAssumptionException e) {
+            replace(createInactive());
+            return;
+        }
+
+        final RubyArguments arguments = new RubyArguments(inlinable.getDeclarationFrame(), NilPlaceholder.INSTANCE, null, 14);
+        final VirtualFrame inlinedFrame = Truffle.getRuntime().createVirtualFrame(frame.pack(), arguments, inlinable.getFrameDescriptor());
+        inlinedRoot.execute(inlinedFrame);
+    }
+
+    protected abstract InactiveEnterDebugProbe createInactive();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveLeaveDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,49 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+
+public abstract class ActiveLeaveDebugProbe extends RubyProbe {
+
+    private final Assumption activeAssumption;
+
+    private final InlinableMethodImplementation inlinable;
+    private final RubyRootNode inlinedRoot;
+
+    public ActiveLeaveDebugProbe(RubyContext context, Assumption activeAssumption, RubyProc proc) {
+        super(context, false);
+        this.activeAssumption = activeAssumption;
+        inlinable = ((InlinableMethodImplementation) proc.getMethod().getImplementation());
+        inlinedRoot = inlinable.getCloneOfPristineRootNode();
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        try {
+            activeAssumption.check();
+        } catch (InvalidAssumptionException e) {
+            replace(createInactive());
+            return;
+        }
+
+        final RubyArguments arguments = new RubyArguments(inlinable.getDeclarationFrame(), NilPlaceholder.INSTANCE, null, result);
+        final VirtualFrame inlinedFrame = Truffle.getRuntime().createVirtualFrame(frame.pack(), arguments, inlinable.getFrameDescriptor());
+        inlinedRoot.execute(inlinedFrame);
+    }
+
+    protected abstract InactiveLeaveDebugProbe createInactive();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveLineDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,34 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+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.debug.*;
+
+public class ActiveLineDebugProbe extends ActiveEnterDebugProbe {
+
+    private final SourceLineLocation sourceLine;
+
+    public ActiveLineDebugProbe(RubyContext context, SourceLineLocation sourceLine, Assumption activeAssumption, RubyProc proc) {
+        super(context, activeAssumption, proc);
+        this.sourceLine = sourceLine;
+    }
+
+    @Override
+    protected InactiveEnterDebugProbe createInactive() {
+        final RubyContext rubyContext = (RubyContext) getContext();
+        final RubyDebugManager manager = rubyContext.getRubyDebugManager();
+        return new InactiveLineDebugProbe(rubyContext, sourceLine, manager.getAssumption(sourceLine));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/ActiveLocalDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,33 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+import com.oracle.truffle.ruby.runtime.debug.*;
+
+public class ActiveLocalDebugProbe extends ActiveLeaveDebugProbe {
+
+    private final MethodLocal methodLocal;
+
+    public ActiveLocalDebugProbe(RubyContext context, MethodLocal methodLocal, Assumption activeAssumption, RubyProc proc) {
+        super(context, activeAssumption, proc);
+        this.methodLocal = methodLocal;
+    }
+
+    @Override
+    protected InactiveLeaveDebugProbe createInactive() {
+        final RubyContext rubyContext = (RubyContext) getContext();
+        final RubyDebugManager manager = rubyContext.getRubyDebugManager();
+        return new InactiveLocalDebugProbe(rubyContext, methodLocal, manager.getAssumption(methodLocal));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/DebugNodes.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,184 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.nodes.core.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.control.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+import com.oracle.truffle.ruby.runtime.debug.*;
+import com.oracle.truffle.ruby.runtime.methods.*;
+
+@CoreClass(name = "Debug")
+public abstract class DebugNodes {
+
+    @CoreMethod(names = "break", isModuleMethod = true, needsSelf = false, needsBlock = true, appendCallNode = true, minArgs = 0, maxArgs = 3)
+    public abstract static class BreakNode extends CoreMethodNode {
+
+        public BreakNode(RubyContext context, SourceSection sourceSection) {
+            super(context, sourceSection);
+        }
+
+        public BreakNode(BreakNode prev) {
+            super(prev);
+        }
+
+        @Specialization(order = 1)
+        public NilPlaceholder debugBreak(VirtualFrame frame, Node callNode, @SuppressWarnings("unused") UndefinedPlaceholder undefined0, @SuppressWarnings("unused") UndefinedPlaceholder undefined1,
+                        @SuppressWarnings("unused") UndefinedPlaceholder block) {
+            getContext().runShell(callNode, frame.materialize());
+            return NilPlaceholder.INSTANCE;
+        }
+
+        @Specialization(order = 2)
+        public NilPlaceholder debugBreak(RubyString fileName, int line, @SuppressWarnings("unused") Node callNode, @SuppressWarnings("unused") UndefinedPlaceholder block) {
+            final RubyContext context = getContext();
+            if (context.getConfiguration().getDebug()) {
+                final Source source = context.getSourceManager().get(fileName.toString());
+                final SourceLineLocation lineLocation = new SourceLineLocation(source, line);
+                context.getRubyDebugManager().setBreakpoint(lineLocation, null);
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+        @Specialization(order = 3)
+        public NilPlaceholder debugBreak(RubyString fileName, int line, @SuppressWarnings("unused") Node callNode, RubyProc block) {
+            final RubyContext context = getContext();
+            if (context.getConfiguration().getDebug()) {
+                final Source source = context.getSourceManager().get(fileName.toString());
+                final SourceLineLocation lineLocation = new SourceLineLocation(source, line);
+                context.getRubyDebugManager().setBreakpoint(lineLocation, block);
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+        @Specialization(order = 4)
+        public NilPlaceholder debugBreak(RubySymbol methodName, RubySymbol localName, @SuppressWarnings("unused") Node callNode, @SuppressWarnings("unused") UndefinedPlaceholder block) {
+            final RubyContext context = getContext();
+            if (context.getConfiguration().getDebug()) {
+                final RubyMethod method = context.getCoreLibrary().getMainObject().getLookupNode().lookupMethod(methodName.toString());
+                final MethodLocal methodLocal = new MethodLocal(method.getUniqueIdentifier(), localName.toString());
+                context.getRubyDebugManager().setBreakpoint(methodLocal, null);
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+        @Specialization(order = 5)
+        public NilPlaceholder debugBreak(RubySymbol methodName, RubySymbol localName, @SuppressWarnings("unused") Node callNode, RubyProc block) {
+            final RubyContext context = getContext();
+            if (context.getConfiguration().getDebug()) {
+                final RubyMethod method = context.getCoreLibrary().getMainObject().getLookupNode().lookupMethod(methodName.toString());
+                final MethodLocal methodLocal = new MethodLocal(method.getUniqueIdentifier(), localName.toString());
+                context.getRubyDebugManager().setBreakpoint(methodLocal, block);
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+    }
+
+    @CoreMethod(names = "continue", isModuleMethod = true, needsSelf = false, maxArgs = 0)
+    public abstract static class ContinueNode extends CoreMethodNode {
+
+        public ContinueNode(RubyContext context, SourceSection sourceSection) {
+            super(context, sourceSection);
+        }
+
+        public ContinueNode(ContinueNode prev) {
+            super(prev);
+        }
+
+        @Specialization
+        public Object debugContinue() {
+            if (getContext().getConfiguration().getDebug()) {
+                throw new BreakShellException();
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+    }
+
+    @CoreMethod(names = "enabled?", isModuleMethod = true, needsSelf = false, maxArgs = 0)
+    public abstract static class EnabledNode extends CoreMethodNode {
+
+        public EnabledNode(RubyContext context, SourceSection sourceSection) {
+            super(context, sourceSection);
+        }
+
+        public EnabledNode(ContinueNode prev) {
+            super(prev);
+        }
+
+        @Specialization
+        public boolean enabled() {
+            return getContext().getConfiguration().getDebug();
+        }
+
+    }
+
+    @CoreMethod(names = "where", isModuleMethod = true, needsSelf = false, appendCallNode = true, minArgs = 1, maxArgs = 1)
+    public abstract static class WhereNode extends CoreMethodNode {
+
+        public WhereNode(RubyContext context, SourceSection sourceSection) {
+            super(context, sourceSection);
+        }
+
+        public WhereNode(WhereNode prev) {
+            super(prev);
+        }
+
+        @Specialization
+        public NilPlaceholder where(Node callNode) {
+            getContext().getConfiguration().getStandardOut().println(callNode.getSourceSection());
+            return NilPlaceholder.INSTANCE;
+        }
+
+    }
+
+    @CoreMethod(names = "remove", isModuleMethod = true, needsSelf = false, needsBlock = true, minArgs = 2, maxArgs = 2)
+    public abstract static class RemoveNode extends CoreMethodNode {
+
+        public RemoveNode(RubyContext context, SourceSection sourceSection) {
+            super(context, sourceSection);
+        }
+
+        public RemoveNode(RemoveNode prev) {
+            super(prev);
+        }
+
+        @Specialization
+        public NilPlaceholder debugRemove(RubyString fileName, int line) {
+            final RubyContext context = getContext();
+            if (context.getConfiguration().getDebug()) {
+                final Source source = context.getSourceManager().get(fileName.toString());
+                final SourceLineLocation lineLocation = new SourceLineLocation(source, line);
+                context.getRubyDebugManager().removeBreakpoint(lineLocation);
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+        @Specialization
+        public NilPlaceholder debugRemove(RubySymbol methodName, RubySymbol localName) {
+            final RubyContext context = getContext();
+            if (context.getConfiguration().getDebug()) {
+                final RubyMethod method = context.getCoreLibrary().getMainObject().getLookupNode().lookupMethod(methodName.toString());
+                final MethodLocal methodLocal = new MethodLocal(method.getUniqueIdentifier(), localName.toString());
+                context.getRubyDebugManager().removeBreakpoint(methodLocal);
+            }
+            return NilPlaceholder.INSTANCE;
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveEnterDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,39 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+public abstract class InactiveEnterDebugProbe extends RubyProbe {
+
+    private final Assumption inactiveAssumption;
+
+    public InactiveEnterDebugProbe(RubyContext context, Assumption inactiveAssumption) {
+        super(context, false);
+        this.inactiveAssumption = inactiveAssumption;
+    }
+
+    @Override
+    public void enter(Node astNode, VirtualFrame frame) {
+        try {
+            inactiveAssumption.check();
+        } catch (InvalidAssumptionException e) {
+            final ActiveEnterDebugProbe activeNode = createActive();
+            replace(activeNode);
+            activeNode.enter(astNode, frame);
+        }
+    }
+
+    protected abstract ActiveEnterDebugProbe createActive();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveLeaveDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,39 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+public abstract class InactiveLeaveDebugProbe extends RubyProbe {
+
+    private final Assumption inactiveAssumption;
+
+    public InactiveLeaveDebugProbe(RubyContext context, Assumption inactiveAssumption) {
+        super(context, false);
+        this.inactiveAssumption = inactiveAssumption;
+    }
+
+    @Override
+    public void leave(Node astNode, VirtualFrame frame, Object result) {
+        try {
+            inactiveAssumption.check();
+        } catch (InvalidAssumptionException e) {
+            final ActiveLeaveDebugProbe activeNode = createActive();
+            replace(activeNode);
+            activeNode.leave(astNode, frame, result);
+        }
+    }
+
+    protected abstract ActiveLeaveDebugProbe createActive();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveLineDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,33 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.debug.*;
+
+public class InactiveLineDebugProbe extends InactiveEnterDebugProbe {
+
+    private final SourceLineLocation sourceLine;
+
+    public InactiveLineDebugProbe(RubyContext context, SourceLineLocation sourceLine, Assumption inactiveAssumption) {
+        super(context, inactiveAssumption);
+        this.sourceLine = sourceLine;
+    }
+
+    @Override
+    protected ActiveEnterDebugProbe createActive() {
+        final RubyContext rubyContext = (RubyContext) getContext();
+        final RubyDebugManager manager = rubyContext.getRubyDebugManager();
+        return new ActiveLineDebugProbe(rubyContext, sourceLine, manager.getAssumption(sourceLine), manager.getBreakpoint(sourceLine));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/InactiveLocalDebugProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,32 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.debug.*;
+
+public class InactiveLocalDebugProbe extends InactiveLeaveDebugProbe {
+
+    private final MethodLocal methodLocal;
+
+    public InactiveLocalDebugProbe(RubyContext context, MethodLocal methodLocal, Assumption inactiveAssumption) {
+        super(context, inactiveAssumption);
+        this.methodLocal = methodLocal;
+    }
+
+    @Override
+    protected ActiveLeaveDebugProbe createActive() {
+        final RubyContext rubyContext = (RubyContext) getContext();
+        final RubyDebugManager manager = rubyContext.getRubyDebugManager();
+        return new ActiveLocalDebugProbe(rubyContext, methodLocal, manager.getAssumption(methodLocal), manager.getBreakpoint(methodLocal));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,41 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.nodes.instrument.*;
+import com.oracle.truffle.ruby.runtime.*;
+
+/**
+ * A "probe node" implemented specifically for the Ruby implementation; subclasses need only
+ * override those members of {@link InstrumentationProbeEvents} for which some action is needed.
+ */
+public abstract class RubyProbe extends InstrumentationProbeNode.DefaultProbeNode {
+
+    protected final boolean oneShot;
+
+    protected final 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;
+    }
+
+    /**
+     * 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.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyTraceProbe.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,49 @@
+/*
+ * 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.nodes.debug;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+import com.oracle.truffle.ruby.runtime.*;
+import com.oracle.truffle.ruby.runtime.subsystems.*;
+
+/**
+ * A "trace" probe that has no runtime cost until activated, at which time it invokes a trace
+ * message.
+ */
+public final class RubyTraceProbe extends RubyProbe {
+
+    private final Assumption notTracingAssumption;
+
+    @CompilerDirectives.CompilationFinal private boolean tracingEverEnabled = false;
+
+    public RubyTraceProbe(RubyContext context) {
+        super(context, false);
+        this.notTracingAssumption = context.getTraceManager().getNotTracingAssumption();
+    }
+
+    @Override
+    public void enter(Node astNode, VirtualFrame frame) {
+        if (!tracingEverEnabled) {
+            try {
+                notTracingAssumption.check();
+            } catch (InvalidAssumptionException e) {
+                tracingEverEnabled = true;
+            }
+        }
+        final TraceManager traceManager = context.getTraceManager();
+        if (tracingEverEnabled && traceManager.hasTraceProc()) {
+            final SourceSection sourceSection = astNode.getEncapsulatingSourceSection();
+            traceManager.trace("line", sourceSection.getSource().getName(), sourceSection.getStartLine(), 0, null, null);
+        }
+    }
+
+}
--- a/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/DefaultRubyNodeInstrumenter.java	Mon Feb 10 03:37:32 2014 +0000
+++ b/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/DefaultRubyNodeInstrumenter.java	Mon Feb 10 03:39:21 2014 +0000
@@ -9,7 +9,9 @@
  */
 package com.oracle.truffle.ruby.parser;
 
+import com.oracle.truffle.api.*;
 import com.oracle.truffle.api.nodes.instrument.*;
+import com.oracle.truffle.api.source.*;
 import com.oracle.truffle.ruby.nodes.*;
 import com.oracle.truffle.ruby.nodes.debug.*;
 import com.oracle.truffle.ruby.runtime.*;
@@ -26,17 +28,33 @@
     public DefaultRubyNodeInstrumenter() {
     }
 
-    public RubyNode instrumentAsStatement(RubyNode rubyNode) {
-        assert rubyNode != null;
-        assert !(rubyNode instanceof RubyProxyNode);
-        final RubyContext context = rubyNode.getContext();
+    public RubyNode instrumentAsStatement(RubyNode node) {
+        assert node != null;
+
+        final RubyContext context = node.getContext();
+
+        RubyProxyNode proxy;
+
+        if (node instanceof RubyProxyNode) {
+            proxy = (RubyProxyNode) node;
+        } else {
+            proxy = new RubyProxyNode(node.getContext(), node);
+            proxy.markAs(NodePhylum.STATEMENT);
+            proxy.clearSourceSection();
+            proxy.assignSourceSection(node.getSourceSection());
+        }
+
         if (context.getConfiguration().getTrace()) {
-            final RubyProxyNode proxy = new RubyProxyNode(context, rubyNode);
-            proxy.markAs(NodePhylum.STATEMENT);
             proxy.getProbeChain().appendProbe(new RubyTraceProbe(context));
-            return proxy;
         }
-        return rubyNode;
+
+        if (context.getConfiguration().getDebug()) {
+            final SourceSection sourceSection = proxy.getChild().getSourceSection();
+            final SourceLineLocation sourceLine = new SourceLineLocation(sourceSection.getSource(), sourceSection.getStartLine());
+            proxy.getProbeChain().appendProbe(new InactiveLineDebugProbe(context, sourceLine, context.getRubyDebugManager().getAssumption(sourceLine)));
+        }
+
+        return proxy;
     }
 
     public RubyNode instrumentAsCall(RubyNode node, String callName) {
@@ -44,7 +62,27 @@
     }
 
     public RubyNode instrumentAsLocalAssignment(RubyNode node, UniqueMethodIdentifier methodIdentifier, String localName) {
-        return node;
+        assert node != null;
+
+        final RubyContext context = node.getContext();
+
+        RubyProxyNode proxy;
+
+        if (node instanceof RubyProxyNode) {
+            proxy = (RubyProxyNode) node;
+        } else {
+            proxy = new RubyProxyNode(node.getContext(), node);
+            proxy.markAs(NodePhylum.STATEMENT);
+            proxy.clearSourceSection();
+            proxy.assignSourceSection(node.getSourceSection());
+        }
+
+        if (context.getConfiguration().getDebug()) {
+            final MethodLocal methodLocal = new MethodLocal(methodIdentifier, localName);
+            proxy.getProbeChain().appendProbe(new InactiveLocalDebugProbe(context, methodLocal, context.getRubyDebugManager().getAssumption(methodLocal)));
+        }
+
+        return proxy;
     }
 
 }
--- a/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java	Mon Feb 10 03:37:32 2014 +0000
+++ b/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java	Mon Feb 10 03:39:21 2014 +0000
@@ -1102,7 +1102,6 @@
 
     @Override
     public Object visitLocalAsgnNode(org.jrubyparser.ast.LocalAsgnNode node) {
-
         final SourceSection sourceSection = translate(node.getPosition());
 
         if (environment.getNeverAssignInParentScope()) {
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/RubyContext.java	Mon Feb 10 03:37:32 2014 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/RubyContext.java	Mon Feb 10 03:39:21 2014 +0000
@@ -22,6 +22,7 @@
 import com.oracle.truffle.ruby.runtime.configuration.*;
 import com.oracle.truffle.ruby.runtime.control.*;
 import com.oracle.truffle.ruby.runtime.core.*;
+import com.oracle.truffle.ruby.runtime.debug.*;
 import com.oracle.truffle.ruby.runtime.methods.*;
 import com.oracle.truffle.ruby.runtime.objects.*;
 import com.oracle.truffle.ruby.runtime.subsystems.*;
@@ -41,6 +42,7 @@
     private final FiberManager fiberManager;
     private final AtExitManager atExitManager;
     private final DebugManager debugManager;
+    private final RubyDebugManager rubyDebugManager;
     private final SourceManager sourceManager;
     private final ASTPrinter astPrinter;
 
@@ -77,6 +79,7 @@
         sourceManager = new SourceManager();
 
         debugManager = new DefaultDebugManager(this);
+        rubyDebugManager = new RubyDebugManager();
 
         // Must initialize threads before fibers
 
@@ -300,4 +303,8 @@
         return sourceManager;
     }
 
+    public RubyDebugManager getRubyDebugManager() {
+        return rubyDebugManager;
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/MethodLocal.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,65 @@
+/*
+ * 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.methods.*;
+
+/*
+ * Identifies a local in a method.
+ */
+public class MethodLocal {
+
+    private final UniqueMethodIdentifier method;
+    private final String local;
+
+    public MethodLocal(UniqueMethodIdentifier method, String local) {
+        this.method = method;
+        this.local = local;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((local == null) ? 0 : local.hashCode());
+        result = prime * result + ((method == null) ? 0 : method.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        MethodLocal other = (MethodLocal) obj;
+        if (local == null) {
+            if (other.local != null) {
+                return false;
+            }
+        } else if (!local.equals(other.local)) {
+            return false;
+        }
+        if (method == null) {
+            if (other.method != null) {
+                return false;
+            }
+        } else if (!method.equals(other.method)) {
+            return false;
+        }
+        return true;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java	Mon Feb 10 03:39:21 2014 +0000
@@ -0,0 +1,101 @@
+/*
+ * 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 java.util.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.source.*;
+import com.oracle.truffle.api.utilities.*;
+import com.oracle.truffle.ruby.runtime.core.*;
+
+public final class RubyDebugManager {
+
+    private final Map<SourceLineLocation, CyclicAssumption> lineAssumptions = new HashMap<>();
+    private final Map<SourceLineLocation, RubyProc> lineBreakpoints = new HashMap<>();
+
+    private final Map<MethodLocal, CyclicAssumption> localAssumptions = new HashMap<>();
+    private final Map<MethodLocal, RubyProc> localBreakpoints = new HashMap<>();
+
+    public void setBreakpoint(SourceLineLocation sourceLine, RubyProc proc) {
+        final CyclicAssumption assumption = lineAssumptions.get(sourceLine);
+
+        if (assumption == null) {
+            throw new RuntimeException("Breakpoint " + sourceLine + " not found");
+        } else {
+            lineBreakpoints.put(sourceLine, proc);
+            assumption.invalidate();
+        }
+    }
+
+    public void setBreakpoint(MethodLocal methodLocal, RubyProc proc) {
+        final CyclicAssumption assumption = localAssumptions.get(methodLocal);
+
+        if (assumption == null) {
+            throw new RuntimeException("Breakpoint " + methodLocal + " not found");
+        } else {
+            localBreakpoints.put(methodLocal, proc);
+            assumption.invalidate();
+        }
+    }
+
+    public void removeBreakpoint(SourceLineLocation sourceLine) {
+        final CyclicAssumption assumption = lineAssumptions.get(sourceLine);
+
+        if (assumption == null) {
+            throw new RuntimeException("Breakpoint " + sourceLine + " not found");
+        } else {
+            lineBreakpoints.remove(sourceLine);
+            assumption.invalidate();
+        }
+    }
+
+    public void removeBreakpoint(MethodLocal methodLocal) {
+        final CyclicAssumption assumption = localAssumptions.get(methodLocal);
+
+        if (assumption == null) {
+            throw new RuntimeException("Breakpoint " + methodLocal + " not found");
+        } else {
+            localBreakpoints.remove(methodLocal);
+            assumption.invalidate();
+        }
+    }
+
+    public Assumption getAssumption(SourceLineLocation sourceLine) {
+        CyclicAssumption assumption = lineAssumptions.get(sourceLine);
+
+        if (assumption == null) {
+            assumption = new CyclicAssumption(sourceLine.toString());
+            lineAssumptions.put(sourceLine, assumption);
+        }
+
+        return assumption.getAssumption();
+    }
+
+    public RubyProc getBreakpoint(SourceLineLocation sourceLine) {
+        return lineBreakpoints.get(sourceLine);
+    }
+
+    public Assumption getAssumption(MethodLocal methodLocal) {
+        CyclicAssumption assumption = localAssumptions.get(methodLocal);
+
+        if (assumption == null) {
+            assumption = new CyclicAssumption(methodLocal.toString());
+            localAssumptions.put(methodLocal, assumption);
+        }
+
+        return assumption.getAssumption();
+    }
+
+    public RubyProc getBreakpoint(MethodLocal methodLocal) {
+        return localBreakpoints.get(methodLocal);
+    }
+
+}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java	Mon Feb 10 03:37:32 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,41 +0,0 @@
-/*
- * 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.nodes.instrument.*;
-import com.oracle.truffle.ruby.runtime.*;
-
-/**
- * A "probe node" implemented specifically for the Ruby implementation; subclasses need only
- * override those members of {@link InstrumentationProbeEvents} for which some action is needed.
- */
-public abstract class RubyProbe extends InstrumentationProbeNode.DefaultProbeNode {
-
-    protected final boolean oneShot;
-
-    protected final 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;
-    }
-
-    /**
-     * Is this a one-shot (self-removing) probe? If so, it will remove itself the first time
-     * activated.
-     */
-    public boolean isOneShot() {
-        return oneShot;
-    }
-}
--- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java	Mon Feb 10 03:37:32 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/*
- * 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.*;
-import com.oracle.truffle.api.frame.*;
-import com.oracle.truffle.api.nodes.*;
-import com.oracle.truffle.ruby.runtime.*;
-import com.oracle.truffle.ruby.runtime.subsystems.*;
-
-/**
- * A "trace" probe that has no runtime cost until activated, at which time it invokes a trace
- * message.
- */
-public final class RubyTraceProbe extends RubyProbe {
-
-    private final Assumption notTracingAssumption;
-
-    @CompilerDirectives.CompilationFinal private boolean tracingEverEnabled = false;
-
-    public RubyTraceProbe(RubyContext context) {
-        super(context, false);
-        this.notTracingAssumption = context.getTraceManager().getNotTracingAssumption();
-    }
-
-    @Override
-    public void enter(Node astNode, VirtualFrame frame) {
-        if (!tracingEverEnabled) {
-            try {
-                notTracingAssumption.check();
-            } catch (InvalidAssumptionException e) {
-                tracingEverEnabled = true;
-            }
-        }
-        final TraceManager traceManager = context.getTraceManager();
-        if (tracingEverEnabled && traceManager.hasTraceProc()) {
-            final SourceSection sourceSection = astNode.getEncapsulatingSourceSection();
-            traceManager.trace("line", sourceSection.getSource().getName(), sourceSection.getStartLine(), 0, null, null);
-        }
-    }
-}