# HG changeset patch # User Michael Van De Vanter # Date 1389218616 28800 # Node ID 1894412de0ed295c2252b886d4417bfbd846e8dd # Parent f29a358cf3da491ccc66b5b2186bf8491ec54442 Ruby: major upgrade in debugging support, mainly for navigation: step, next (passing over calls), return (from enclosing function), etc. Also a few bug fixes. diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProxyNode.java --- a/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProxyNode.java Wed Jan 08 14:00:21 2014 -0800 +++ b/graal/com.oracle.truffle.ruby.nodes/src/com/oracle/truffle/ruby/nodes/debug/RubyProxyNode.java Wed Jan 08 14:03:36 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 getPhylumMarks() { + return probeChain.getPhylumMarks(); + } + + public void markAs(NodePhylum phylum) { + probeChain.markAs(phylum); + } + } diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java --- a/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java Wed Jan 08 14:00:21 2014 -0800 +++ b/graal/com.oracle.truffle.ruby.parser/src/com/oracle/truffle/ruby/parser/Translator.java Wed Jan 08 14:03:36 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 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; } diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterLineProbe.java --- /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 14:03:36 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); + } + +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterLocalProbe.java --- /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 14:03:36 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()); + } + +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterProbe.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakAfterProbe.java Wed Jan 08 14:00:21 2014 -0800 +++ /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()); - } - -} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakBeforeLineProbe.java --- /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 14:03:36 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()); + } + } +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakBeforeProbe.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyBreakBeforeProbe.java Wed Jan 08 14:00:21 2014 -0800 +++ /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()); - } - -} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyCallProbe.java --- /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 14:03:36 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); + } +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java Wed Jan 08 14:00:21 2014 -0800 +++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyDebugManager.java Wed Jan 08 14:03:36 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. + *
    + *
  • User breakpoints are enabled.
  • + *
  • Continue until either: + *
      + *
    1. execution arrives at a node with attached user breakpoint, or:
    2. + *
    3. execution completes.
    4. + *
    + *
+ */ + CONTINUE, + + /** + * Context: per-statement stepping execution, e.g. in response to a "Step" request. + *
    + *
  • User breakpoints are disabled.
  • + *
  • Continue until either: + *
      + *
    1. execution arrives at a "Statement" node, or:
    2. + *
    3. execution completes.
    4. + *
    + *
+ */ + STEP, + + /** + * Context: per-statement stepping in response to a "Next" request and when not nested in + * any function/method call. + *
    + *
  • User breakpoints are disabled.
  • + *
  • Continue until either: + *
      + *
    1. execution arrives at a "Statement" node or:
    2. + *
    3. the program completes or:
    4. + *
    5. execution arrives at a function/method entry, in which case the mode changes to + * {@link #NEXT_NESTED} and execution continues.
    6. + *
    + *
+ */ + NEXT, + + /** + * Context: ordinary debugging execution in response to a "Next" requested and when nested + * at least one deep in function/method calls. + *
    + *
  • User breakpoints are enabled.
  • + *
  • Execute until either: + *
      + *
    1. execution arrives at a node with attached user breakpoint, or:
    2. + *
    3. execution completes, or:
    4. + *
    5. execution returns from all nested function/method calls, in which case the mode + * changes to {@link #NEXT} and execution continues.
    6. + *
    + *
+ */ + 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 linesToProbeChains = new HashMap<>(); - private final Set 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 linesToBreakpoints = new TreeMap<>(); + private Node nextNestedInCallNode = null; + + /** + * Map: SourceSection ==> probe chain associated with that source section in an AST. + */ + private final Map srcToProbeChain = new HashMap<>(); + + /** + * Map: Source lines ==> probe chains associated with source sections starting on the line. + */ + private final Map> 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 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 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 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 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 entry : lineToBreakpoint.entrySet()) { + final RubyLineBreakpoint breakpoint = entry.getValue(); + if (breakpoint.probe.isOneShot()) { + lineToBreakpoint.remove(entry.getKey()); + breakpoint.retire(); + } + } + } + + /** + * Prepare to execute a "Continue": + *
    + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at a node to which a breakpoint is attached, or:
    2. + *
    3. execution completes.
    4. + *
    + *
+ */ + public void setContinue() { + // Nothing to do here; "Continue" is the default, which should be restored after each halt. + } + + /** + * Prepare to execute a "Step": + *
    + *
  • User breakpoints are disabled.
  • + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at a "Statement" node, or:
    2. + *
    3. execution completes.
    4. + *
    + * This status persists only through one execution, and reverts to + * {@link ExecutionMode#CONTINUE}. + *
+ */ + 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": + *
    + *
  • Execution will continue until either: + *
      + *
    1. execution arrives at a "Statement" node when not nested in one or more function/method + * calls, or:
    2. + *
    3. execution arrives at a node to which a breakpoint is attached and when nested in one or + * more function/method calls, or:
    4. + *
    5. execution completes.
    6. + *
    + * This status persists only through one execution, and reverts to + * {@link ExecutionMode#CONTINUE}. + *
+ */ + 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 ? "" : status.name; + String result = status == null ? "" : 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(); } } diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyLineProbe.java --- /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 14:03:36 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; + } + +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyLocalProbe.java --- /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 14:03:36 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; + } + +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java Wed Jan 08 14:00:21 2014 -0800 +++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProbe.java Wed Jan 08 14:03:36 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; } } diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterLineProbe.java --- /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 14:03:36 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()); + } +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterLocalProbe.java --- /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 14:03:36 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()); + } +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterProbe.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcAfterProbe.java Wed Jan 08 14:00:21 2014 -0800 +++ /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()); - } -} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcBeforeLineProbe.java --- /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 14:03:36 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; + } + +} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcBeforeProbe.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyProcBeforeProbe.java Wed Jan 08 14:00:21 2014 -0800 +++ /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()); - } - -} diff -r f29a358cf3da -r 1894412de0ed graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java --- a/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java Wed Jan 08 14:00:21 2014 -0800 +++ b/graal/com.oracle.truffle.ruby.runtime/src/com/oracle/truffle/ruby/runtime/debug/RubyTraceProbe.java Wed Jan 08 14:03:36 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(); }