Mercurial > hg > truffle
diff truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java @ 22003:5bc7f7b867ab
Making debugger always on for each TruffleVM execution. Introducing EventConsumer to process such debugger events. Requesting each RootNode to be associated with a TruffleLanguage, so debugger can find out proper context for each Node where executions gets suspended.
author | Jaroslav Tulach <jaroslav.tulach@oracle.com> |
---|---|
date | Sat, 18 Jul 2015 18:03:36 +0200 |
parents | |
children | 02e4cf046653 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/truffle/com.oracle.truffle.api/src/com/oracle/truffle/api/debug/Debugger.java Sat Jul 18 18:03:36 2015 +0200 @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.truffle.api.debug; + +import java.io.*; +import java.util.*; + +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.impl.Accessor; +import com.oracle.truffle.api.instrument.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.source.*; +import com.oracle.truffle.api.vm.TruffleVM; + +/** + * Language-agnostic engine for running Truffle languages under debugging control. + */ +public final class Debugger { + + private static final boolean TRACE = false; + private static final String TRACE_PREFIX = "DEBUG ENGINE: "; + + private static final PrintStream OUT = System.out; + + private static final SyntaxTag STEPPING_TAG = StandardSyntaxTag.STATEMENT; + private static final SyntaxTag CALL_TAG = StandardSyntaxTag.CALL; + + private static void trace(String format, Object... args) { + if (TRACE) { + OUT.println(TRACE_PREFIX + String.format(format, args)); + } + } + + private final TruffleVM vm; + private Source lastSource; + + interface BreakpointCallback { + + /** + * Passes control to the debugger with execution suspended. + */ + void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason); + } + + interface WarningLog { + + /** + * Logs a warning that is kept until the start of the next execution. + */ + void addWarning(String warning); + } + + /** + * Implementation of line-oriented breakpoints. + */ + private final LineBreakpointFactory lineBreaks; + + /** + * Implementation of tag-oriented breakpoints. + */ + private final TagBreakpointFactory tagBreaks; + + /** + * Head of the stack of executions. + */ + private DebugExecutionContext debugContext; + + Debugger(TruffleVM vm) { + this.vm = vm; + + Source.setFileCaching(true); + + // Initialize execution context stack + debugContext = new DebugExecutionContext(null, null); + prepareContinue(); + debugContext.contextTrace("START EXEC DEFAULT"); + + final BreakpointCallback breakpointCallback = new BreakpointCallback() { + + @TruffleBoundary + public void haltedAt(Node astNode, MaterializedFrame mFrame, String haltReason) { + debugContext.halt(astNode, mFrame, true, haltReason); + } + }; + + final WarningLog warningLog = new WarningLog() { + + public void addWarning(String warning) { + assert debugContext != null; + debugContext.logWarning(warning); + } + }; + + this.lineBreaks = new LineBreakpointFactory(this, breakpointCallback, warningLog); + this.tagBreaks = new TagBreakpointFactory(this, breakpointCallback, warningLog); + } + + TruffleVM vm() { + return vm; + } + + /** + * Sets a breakpoint to halt at a source line. + * + * @param ignoreCount number of hits to ignore before halting + * @param lineLocation where to set the breakpoint (source, line number) + * @param oneShot breakpoint disposes itself after fist hit, if {@code true} + * @return a new breakpoint, initially enabled + * @throws IOException if the breakpoint can not be set. + */ + @TruffleBoundary + public Breakpoint setLineBreakpoint(int ignoreCount, LineLocation lineLocation, boolean oneShot) throws IOException { + return lineBreaks.create(ignoreCount, lineLocation, oneShot); + } + + /** + * Sets a breakpoint to halt at any node holding a specified {@link SyntaxTag}. + * + * @param ignoreCount number of hits to ignore before halting + * @param oneShot if {@code true} breakpoint removes it self after a hit + * @return a new breakpoint, initially enabled + * @throws IOException if the breakpoint already set + */ + @TruffleBoundary + public Breakpoint setTagBreakpoint(int ignoreCount, SyntaxTag tag, boolean oneShot) throws IOException { + return tagBreaks.create(ignoreCount, tag, oneShot); + } + + /** + * Gets all existing breakpoints, whatever their status, in natural sorted order. Modification + * save. + */ + @TruffleBoundary + public Collection<Breakpoint> getBreakpoints() { + final Collection<Breakpoint> result = new ArrayList<>(); + result.addAll(lineBreaks.getAll()); + result.addAll(tagBreaks.getAll()); + return result; + } + + /** + * Prepare to execute in Continue mode when guest language program execution resumes. In this + * mode: + * <ul> + * <li>Execution will continue until either: + * <ol> + * <li>execution arrives at a node to which an enabled breakpoint is attached, + * <strong>or:</strong></li> + * <li>execution completes.</li> + * </ol> + * </ul> + */ + @TruffleBoundary + void prepareContinue() { + debugContext.setStrategy(new Continue()); + } + + /** + * Prepare to execute in StepInto mode when guest language program execution resumes. In this + * mode: + * <ul> + * <li>User breakpoints are disabled.</li> + * <li>Execution will continue until either: + * <ol> + * <li>execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT + * STATMENT}, <strong>or:</strong></li> + * <li>execution completes.</li> + * </ol> + * <li>StepInto mode persists only through one resumption (i.e. {@code stepIntoCount} steps), + * and reverts by default to Continue mode.</li> + * </ul> + * + * @param stepCount the number of times to perform StepInto before halting + * @throws IllegalArgumentException if the specified number is {@code <= 0} + */ + @TruffleBoundary + void prepareStepInto(int stepCount) { + if (stepCount <= 0) { + throw new IllegalArgumentException(); + } + debugContext.setStrategy(new StepInto(stepCount)); + } + + /** + * Prepare to execute in StepOut mode when guest language program execution resumes. In this + * mode: + * <ul> + * <li>User breakpoints are enabled.</li> + * <li>Execution will continue until either: + * <ol> + * <li>execution arrives at the nearest enclosing call site on the stack, <strong>or</strong></li> + * <li>execution completes.</li> + * </ol> + * <li>StepOut mode persists only through one resumption, and reverts by default to Continue + * mode.</li> + * </ul> + */ + @TruffleBoundary + void prepareStepOut() { + debugContext.setStrategy(new StepOut()); + } + + /** + * Prepare to execute in StepOver mode when guest language program execution resumes. In this + * mode: + * <ul> + * <li>Execution will continue until either: + * <ol> + * <li>execution arrives at a node with the tag {@linkplain StandardSyntaxTag#STATEMENT + * STATEMENT} when not nested in one or more function/method calls, <strong>or:</strong></li> + * <li>execution arrives at a node to which a breakpoint is attached and when nested in one or + * more function/method calls, <strong>or:</strong></li> + * <li>execution completes.</li> + * </ol> + * <li>StepOver mode persists only through one resumption (i.e. {@code stepOverCount} steps), + * and reverts by default to Continue mode.</li> + * </ul> + * + * @param stepCount the number of times to perform StepInto before halting + * @throws IllegalArgumentException if the specified number is {@code <= 0} + */ + @TruffleBoundary + void prepareStepOver(int stepCount) { + if (stepCount <= 0) { + throw new IllegalArgumentException(); + } + debugContext.setStrategy(new StepOver(stepCount)); + } + + /** + * Creates a language-specific factory to produce instances of {@link AdvancedInstrumentRoot} + * that, when executed, computes the result of a textual expression in the language; used to + * create an + * {@linkplain Instrument#create(AdvancedInstrumentResultListener, AdvancedInstrumentRootFactory, Class, String) + * Advanced Instrument}. + * + * @param expr a guest language expression + * @param resultListener optional listener for the result of each evaluation. + * @return a new factory + * @throws IOException if the factory cannot be created, for example if the expression is badly + * formed. + */ + AdvancedInstrumentRootFactory createAdvancedInstrumentRootFactory(Probe probe, String expr, AdvancedInstrumentResultListener resultListener) throws IOException { + try { + Class<? extends TruffleLanguage> langugageClass = ACCESSOR.findLanguage(probe); + TruffleLanguage l = ACCESSOR.findLanguage(vm, langugageClass); + DebugSupportProvider dsp = ACCESSOR.getDebugSupport(l); + return dsp.createAdvancedInstrumentRootFactory(expr, resultListener); + } catch (DebugSupportException ex) { + throw new IOException(ex); + } + } + + /** + * A mode of user navigation from a current code location to another, e.g "step in" vs. + * "step over". + */ + private abstract class StepStrategy { + + private DebugExecutionContext context; + protected final String strategyName; + + protected StepStrategy() { + this.strategyName = getClass().getSimpleName(); + } + + final String getName() { + return strategyName; + } + + /** + * Reconfigure the debugger so that when execution continues the program will halt at the + * location specified by this strategy. + */ + final void enable(DebugExecutionContext c, int stackDepth) { + this.context = c; + setStrategy(stackDepth); + } + + /** + * Return the debugger to the default navigation mode. + */ + final void disable() { + unsetStrategy(); + } + + @TruffleBoundary + final void halt(Node astNode, MaterializedFrame mFrame, boolean before) { + context.halt(astNode, mFrame, before, this.getClass().getSimpleName()); + } + + @TruffleBoundary + final void replaceStrategy(StepStrategy newStrategy) { + context.setStrategy(newStrategy); + } + + @TruffleBoundary + protected final void strategyTrace(String action, String format, Object... args) { + if (TRACE) { + context.contextTrace("%s (%s) %s", action, strategyName, String.format(format, args)); + } + } + + @TruffleBoundary + protected final void suspendUserBreakpoints() { + lineBreaks.setActive(false); + tagBreaks.setActive(false); + } + + @SuppressWarnings("unused") + protected final void restoreUserBreakpoints() { + lineBreaks.setActive(true); + tagBreaks.setActive(true); + } + + /** + * Reconfigure the debugger so that when execution continues, it will do so using this mode + * of navigation. + */ + protected abstract void setStrategy(int stackDepth); + + /** + * Return to the debugger to the default mode of navigation. + */ + protected abstract void unsetStrategy(); + } + + /** + * Strategy: the null stepping strategy. + * <ul> + * <li>User breakpoints are enabled.</li> + * <li>Execution continues until either: + * <ol> + * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li> + * <li>execution completes.</li> + * </ol> + * </ul> + */ + private final class Continue extends StepStrategy { + + @Override + protected void setStrategy(int stackDepth) { + } + + @Override + protected void unsetStrategy() { + } + } + + /** + * Strategy: per-statement stepping. + * <ul> + * <li>User breakpoints are enabled.</li> + * <li>Execution continues until either: + * <ol> + * <li>execution <em>arrives</em> at a STATEMENT node, <strong>or:</strong></li> + * <li>execution <em>returns</em> to a CALL node and the call stack is smaller then when + * execution started, <strong>or:</strong></li> + * <li>execution completes.</li> + * </ol> + * </ul> + * + * @see Debugger#prepareStepInto(int) + */ + private final class StepInto extends StepStrategy { + private int unfinishedStepCount; + + StepInto(int stepCount) { + super(); + this.unfinishedStepCount = stepCount; + } + + @Override + protected void setStrategy(final int stackDepth) { + Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + // HALT: just before statement + --unfinishedStepCount; + strategyTrace("TRAP BEFORE", "unfinished steps=%d", unfinishedStepCount); + // Should run in fast path + if (unfinishedStepCount <= 0) { + halt(node, mFrame, true); + } + strategyTrace("RESUME BEFORE", ""); + } + }); + Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + --unfinishedStepCount; + strategyTrace(null, "TRAP AFTER unfinished steps=%d", unfinishedStepCount); + if (currentStackDepth() < stackDepth) { + // HALT: just "stepped out" + if (unfinishedStepCount <= 0) { + halt(node, mFrame, false); + } + } + strategyTrace("RESUME AFTER", ""); + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setBeforeTagTrap(null); + Probe.setAfterTagTrap(null); + } + } + + /** + * Strategy: execution to nearest enclosing call site. + * <ul> + * <li>User breakpoints are enabled.</li> + * <li>Execution continues until either: + * <ol> + * <li>execution arrives at a node with attached user breakpoint, <strong>or:</strong></li> + * <li>execution <em>returns</em> to a CALL node and the call stack is smaller than when + * execution started, <strong>or:</strong></li> + * <li>execution completes.</li> + * </ol> + * </ul> + * + * @see Debugger#prepareStepOut() + */ + private final class StepOut extends StepStrategy { + + @Override + protected void setStrategy(final int stackDepth) { + Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) { + + @TruffleBoundary + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + // HALT: + final int currentStackDepth = currentStackDepth(); + strategyTrace("TRAP AFTER", "stackDepth: start=%d current=%d", stackDepth, currentStackDepth); + if (currentStackDepth < stackDepth) { + halt(node, mFrame, false); + } + strategyTrace("RESUME AFTER", ""); + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setAfterTagTrap(null); + } + } + + /** + * Strategy: per-statement stepping, so long as not nested in method calls (i.e. at original + * stack depth). + * <ul> + * <li>User breakpoints are enabled.</li> + * <li>Execution continues until either: + * <ol> + * <li>execution arrives at a STATEMENT node with stack depth no more than when started + * <strong>or:</strong></li> + * <li>the program completes.</li> + * </ol> + * </ul> + */ + private final class StepOver extends StepStrategy { + private int unfinishedStepCount; + + StepOver(int stepCount) { + this.unfinishedStepCount = stepCount; + } + + @Override + protected void setStrategy(final int stackDepth) { + Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + final int currentStackDepth = currentStackDepth(); + if (currentStackDepth <= stackDepth) { + // HALT: stack depth unchanged or smaller; treat like StepInto + --unfinishedStepCount; + if (TRACE) { + strategyTrace("TRAP BEFORE", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + } + // Test should run in fast path + if (unfinishedStepCount <= 0) { + halt(node, mFrame, true); + } + } else { + // CONTINUE: Stack depth increased; don't count as a step + strategyTrace("STEP INTO", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + // Stop treating like StepInto, start treating like StepOut + replaceStrategy(new StepOverNested(unfinishedStepCount, stackDepth)); + } + strategyTrace("RESUME BEFORE", ""); + } + }); + + Probe.setAfterTagTrap(new SyntaxTagTrap(CALL_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + final int currentStackDepth = currentStackDepth(); + if (currentStackDepth < stackDepth) { + // HALT: just "stepped out" + --unfinishedStepCount; + strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth: start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + // Should run in fast path + if (unfinishedStepCount <= 0) { + halt(node, mFrame, false); + } + strategyTrace("RESUME AFTER", ""); + } + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setBeforeTagTrap(null); + Probe.setAfterTagTrap(null); + } + } + + /** + * Strategy: per-statement stepping, not into method calls, in effect while at increased stack + * depth + * <ul> + * <li>User breakpoints are enabled.</li> + * <li>Execution continues until either: + * <ol> + * <li>execution arrives at a STATEMENT node with stack depth no more than when started + * <strong>or:</strong></li> + * <li>the program completes <strong>or:</strong></li> + * </ol> + * </ul> + */ + private final class StepOverNested extends StepStrategy { + private int unfinishedStepCount; + private final int startStackDepth; + + StepOverNested(int stepCount, int startStackDepth) { + this.unfinishedStepCount = stepCount; + this.startStackDepth = startStackDepth; + } + + @Override + protected void setStrategy(final int stackDepth) { + Probe.setBeforeTagTrap(new SyntaxTagTrap(STEPPING_TAG) { + + @Override + public void tagTrappedAt(Node node, MaterializedFrame mFrame) { + final int currentStackDepth = currentStackDepth(); + if (currentStackDepth <= startStackDepth) { + // At original step depth (or smaller) after being nested + --unfinishedStepCount; + strategyTrace("TRAP AFTER", "unfinished steps=%d stackDepth start=%d current=%d", unfinishedStepCount, stackDepth, currentStackDepth); + if (unfinishedStepCount <= 0) { + halt(node, mFrame, false); + } + // TODO (mlvdv) fixme for multiple steps + strategyTrace("RESUME BEFORE", ""); + } + } + }); + } + + @Override + protected void unsetStrategy() { + Probe.setBeforeTagTrap(null); + } + } + + /** + * Information and debugging state for a single Truffle execution (which make take place over + * one or more suspended executions). This holds interaction state, for example what is + * executing (e.g. some {@link Source}), what the execution mode is ("stepping" or + * "continuing"). When not running, this holds a cache of the Truffle stack for this particular + * execution, effectively hiding the Truffle stack for any currently suspended executions (down + * the stack). + */ + private final class DebugExecutionContext { + + // Previous halted context in stack + private final DebugExecutionContext predecessor; + + // The current execution level; first is 0. + private final int level; // Number of contexts suspended below + private final Source source; + private final int contextStackBase; // Where the stack for this execution starts + private final List<String> warnings = new ArrayList<>(); + + private boolean running; + + /** + * The stepping strategy currently configured in the debugger. + */ + private StepStrategy strategy; + + /** + * Where halted; null if running. + */ + private Node haltedNode; + + /** + * Where halted; null if running. + */ + private MaterializedFrame haltedFrame; + + private DebugExecutionContext(Source executionSource, DebugExecutionContext previousContext) { + this.source = executionSource; + this.predecessor = previousContext; + this.level = previousContext == null ? 0 : previousContext.level + 1; + + // "Base" is the number of stack frames for all nested (halted) executions. + this.contextStackBase = currentStackDepth(); + this.running = true; + contextTrace("NEW CONTEXT"); + } + + /** + * Sets up a strategy for the next resumption of execution. + * + * @param stepStrategy + */ + void setStrategy(StepStrategy stepStrategy) { + if (this.strategy == null) { + this.strategy = stepStrategy; + this.strategy.enable(this, currentStackDepth()); + if (TRACE) { + contextTrace("SET MODE <none>-->" + stepStrategy.getName()); + } + } else { + strategy.disable(); + strategy = stepStrategy; + strategy.enable(this, currentStackDepth()); + contextTrace("SWITCH MODE %s-->%s", strategy.getName(), stepStrategy.getName()); + } + } + + void clearStrategy() { + if (strategy != null) { + final StepStrategy oldStrategy = strategy; + strategy.disable(); + strategy = null; + contextTrace("CLEAR MODE %s--><none>", oldStrategy.getName()); + } + } + + /** + * Handle a program halt, caused by a breakpoint, stepping strategy, or other cause. + * + * @param astNode the guest language node at which execution is halted + * @param mFrame the current execution frame where execution is halted + * @param before {@code true} if halted <em>before</em> the node, else <em>after</em>. + */ + @TruffleBoundary + void halt(Node astNode, MaterializedFrame mFrame, boolean before, String haltReason) { + assert running; + assert haltedNode == null; + assert haltedFrame == null; + + haltedNode = astNode; + haltedFrame = mFrame; + running = false; + + clearStrategy(); + + // Clean up, just in cased the one-shot breakpoints got confused + lineBreaks.disposeOneShots(); + + final int contextStackDepth = currentStackDepth() - contextStackBase; + if (TRACE) { + final String reason = haltReason == null ? "" : haltReason + ""; + final String where = before ? "BEFORE" : "AFTER"; + contextTrace("HALT %s : (%s) stack base=%d", where, reason, contextStackBase); + contextTrace("CURRENT STACK:"); + // printStack(OUT); + } + + final List<String> recentWarnings = new ArrayList<>(warnings); + warnings.clear(); + + try { + // Pass control to the debug client with current execution suspended + ACCESSOR.dispatchEvent(vm, new SuspendedEvent(Debugger.this, astNode, mFrame, recentWarnings, contextStackDepth)); + // Debug client finished normally, execution resumes + // Presume that the client has set a new strategy (or default to Continue) + running = true; + } catch (KillException e) { + contextTrace("KILL"); + throw e; + } finally { + haltedNode = null; + haltedFrame = null; + } + + } + + void logWarning(String warning) { + warnings.add(warning); + } + + /* + * private void printStack(PrintStream stream) { getFrames(); if (frames == null) { + * stream.println("<empty stack>"); } else { final Visualizer visualizer = + * provider.getVisualizer(); for (FrameDebugDescription frameDesc : frames) { final + * StringBuilder sb = new StringBuilder(" frame " + Integer.toString(frameDesc.index())); + * sb.append(":at " + visualizer.displaySourceLocation(frameDesc.node())); sb.append(":in '" + * + visualizer.displayMethodName(frameDesc.node()) + "'"); stream.println(sb.toString()); } + * } } + */ + + void contextTrace(String format, Object... args) { + if (TRACE) { + final String srcName = (source != null) ? source.getName() : "no source"; + Debugger.trace("<%d> %s (%s)", level, String.format(format, args), srcName); + } + } + } + + // TODO (mlvdv) wish there were fast-path access to stack depth + /** + * Depth of current Truffle stack, including nested executions. Includes the top/current frame, + * which the standard iterator does not count: {@code 0} if no executions. + */ + @TruffleBoundary + private static int currentStackDepth() { + final int[] count = {0}; + Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Void>() { + @Override + public Void visitFrame(FrameInstance frameInstance) { + count[0] = count[0] + 1; + return null; + } + }); + return count[0] == 0 ? 0 : count[0] + 1; + + } + + void executionStarted(Source source) { + Source execSource = source; + if (execSource == null) { + execSource = lastSource; + } else { + lastSource = execSource; + } + // Push a new execution context onto stack + debugContext = new DebugExecutionContext(execSource, debugContext); + prepareContinue(); + debugContext.contextTrace("START EXEC "); + ACCESSOR.dispatchEvent(vm, new ExecutionEvent(this)); + } + + void executionEnded() { + lineBreaks.disposeOneShots(); + tagBreaks.disposeOneShots(); + debugContext.clearStrategy(); + debugContext.contextTrace("END EXEC "); + // Pop the stack of execution contexts. + debugContext = debugContext.predecessor; + } + + private static final class AccessorDebug extends Accessor { + @Override + protected Closeable executionStart(TruffleVM vm, Debugger[] fillIn, Source s) { + final Debugger d; + if (fillIn[0] == null) { + d = fillIn[0] = new Debugger(vm); + } else { + d = fillIn[0]; + } + d.executionStarted(s); + return new Closeable() { + @Override + public void close() throws IOException { + d.executionEnded(); + } + }; + } + + @Override + protected Class<? extends TruffleLanguage> findLanguage(Probe probe) { + return super.findLanguage(probe); + } + + @Override + protected TruffleLanguage findLanguage(TruffleVM vm, Class<? extends TruffleLanguage> languageClass) { + return super.findLanguage(vm, languageClass); + } + + @Override + protected DebugSupportProvider getDebugSupport(TruffleLanguage l) { + return super.getDebugSupport(l); + } + + @Override + protected void dispatchEvent(TruffleVM vm, Object event) { + super.dispatchEvent(vm, event); + } + } + + // registers into Accessor.DEBUG + private static final AccessorDebug ACCESSOR = new AccessorDebug(); +}