diff --git a/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java b/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java index 26a49e62b638..2e99ef639f2e 100644 --- a/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java +++ b/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java @@ -6,18 +6,23 @@ import com.oracle.truffle.api.debug.DebuggerTags; import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.EventContext; import com.oracle.truffle.api.instrumentation.ExecutionEventNode; +import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; import com.oracle.truffle.api.instrumentation.Instrumenter; import com.oracle.truffle.api.instrumentation.SourceSectionFilter; +import com.oracle.truffle.api.instrumentation.StandardTags; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.nodes.RootNode; import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.enso.compiler.context.FramePointer; +import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.expression.builtin.debug.DebugBreakpointNode; import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.node.expression.debug.CaptureResultScopeNode; @@ -65,7 +70,7 @@ protected void onCreate(Env env) { ctx -> ctx.getInstrumentedNode() instanceof DebugBreakpointNode ? new ReplExecutionEventNodeImpl( - ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class)) + false, ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class)) : null); } else { env.getLogger(ReplDebuggerInstrument.class) @@ -80,23 +85,16 @@ protected void onCreate(Env env) { throw new RuntimeException(e); } } - if (!"".equals(env.getOptions().get(FN_OPTION))) { - SourceSectionFilter filter = - SourceSectionFilter.newBuilder().tagIs(DebuggerTags.AlwaysHalt.class).build(); - + var replAtMethodName = env.getOptions().get(FN_OPTION); + if (!"".equals(replAtMethodName)) { DebuggerMessageHandler handler = new DebuggerMessageHandler(); try { MessageEndpoint client = env.startServer(URI.create(DebugServerInfo.URI), handler); if (client != null) { handler.setClient(client); Instrumenter instrumenter = env.getInstrumenter(); - instrumenter.attachExecutionEventFactory( - filter, - ctx -> - ctx.getInstrumentedNode() instanceof DebugBreakpointNode - ? new ReplExecutionEventNodeImpl( - ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class)) - : null); + var replAtMethod = new AtTheEndOfFirstMethod(handler, env, instrumenter); + replAtMethod.activate(replAtMethodName); } else { env.getLogger(ReplDebuggerInstrument.class) .warning("ReplDebuggerInstrument was initialized, " + "but no client connected"); @@ -132,12 +130,17 @@ private static class ReplExecutionEventNodeImpl extends ExecutionEventNode private EventContext eventContext; private DebuggerMessageHandler handler; private TruffleLogger logger; + private final boolean atExit; private ReplExecutionEventNodeImpl( - EventContext eventContext, DebuggerMessageHandler handler, TruffleLogger logger) { + boolean atExit, + EventContext eventContext, + DebuggerMessageHandler handler, + TruffleLogger logger) { this.eventContext = eventContext; this.handler = handler; this.logger = logger; + this.atExit = atExit; } private Object getValue(MaterializedFrame frame, FramePointer ptr) { @@ -216,7 +219,14 @@ protected void onEnter(VirtualFrame frame) { // Note [Safe Access to State in the Debugger Instrument] monadicState = Function.ArgumentsHelper.getState(frame.getArguments()); nodeState = new ReplExecutionEventNodeState(lastReturn, lastScope); - startSession(); + startSessionImpl(); + } + + @Override + public void onReturnValue(VirtualFrame frame, Object result) { + if (atExit) { + startSession(getRootNode(), frame); + } } /* Note [Safe Access to State in the Debugger Instrument] @@ -240,8 +250,23 @@ protected Object onUnwind(VirtualFrame frame, Object info) { return nodeState.getLastReturn(); } + private void startSession(RootNode root, VirtualFrame frame) { + CallerInfo lastScope = Function.ArgumentsHelper.getCallerInfo(frame.getArguments()); + if (lastScope == null && root instanceof EnsoRootNode enso) { + lastScope = + new CallerInfo(frame.materialize(), enso.getLocalScope(), enso.getModuleScope()); + } + if (lastScope != null) { + var lastReturn = EnsoContext.get(this).getNothing(); + // Note [Safe Access to State in the Debugger Instrument] + monadicState = Function.ArgumentsHelper.getState(frame.getArguments()); + nodeState = new ReplExecutionEventNodeState(lastReturn, lastScope); + startSessionImpl(); + } + } + @CompilerDirectives.TruffleBoundary - private void startSession() { + private void startSessionImpl() { if (handler.hasClient()) { handler.startSession(this); } else { @@ -277,4 +302,35 @@ CallerInfo getLastScope() { } } } + + private final class AtTheEndOfFirstMethod implements ExecutionEventNodeFactory { + private final Instrumenter instr; + private final DebuggerMessageHandler handler; + private final TruffleInstrument.Env env; + private EventBinding firstMethod; + + AtTheEndOfFirstMethod(DebuggerMessageHandler h, TruffleInstrument.Env env, Instrumenter instr) { + this.instr = instr; + this.handler = h; + this.env = env; + } + + final void activate(String methodName) { + var b = SourceSectionFilter.newBuilder(); + b.tagIs(StandardTags.RootBodyTag.class); + b.rootNameIs( + (n) -> { + return methodName.equals(n); + }); + var anyMethod = b.build(); + this.firstMethod = instr.attachExecutionEventFactory(anyMethod, this); + } + + @Override + public ExecutionEventNode create(EventContext ctx) { + firstMethod.dispose(); + var log = env.getLogger(ReplExecutionEventNodeImpl.class); + return new ReplExecutionEventNodeImpl(true, ctx, handler, log); + } + } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/DebugServerTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/DebugServerTest.scala index 4160678cf324..504f8c008ec1 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/DebugServerTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/interpreter/test/instrument/DebugServerTest.scala @@ -15,7 +15,7 @@ class DebugServerTest override def contextModifiers: Option[Context#Builder => Context#Builder] = Some(b => { - b.option(DebugServerInfo.FN_OPTION, "main") + b.option(DebugServerInfo.FN_OPTION, "Test.main") }) override def specify(implicit @@ -27,7 +27,7 @@ class DebugServerTest """ |import Standard.Base.Runtime.Debug | - |main = Debug.breakpoint + |main = "hi" |""".stripMargin setSessionManager(executor => executor.exit()) eval(code) @@ -36,13 +36,11 @@ class DebugServerTest "be able to execute arbitrary code in the caller scope" in { val code = """ - |import Standard.Base.Runtime.Debug |import Standard.Base.Data.Numbers | |main = | x = 1 | y = 2 - | Debug.breakpoint |""".stripMargin var evalResult: Either[Exception, ObjectRepresentation] = null @@ -57,11 +55,10 @@ class DebugServerTest "be able to define its local variables" in { val code = """ - |import Standard.Base.Runtime.Debug + |import Standard.Base.Data.Numbers | |main = | x = 10 - | Debug.breakpoint |""".stripMargin setSessionManager { executor => executor.evaluate("y = x + 1") @@ -75,14 +72,12 @@ class DebugServerTest "be able to list local variables in its scope" in { val code = """ - |import Standard.Base.Runtime.Debug + |import Standard.Base.Data.Numbers | |main = | x = 10 | y = 20 | z = x + y - | - | Debug.breakpoint |""".stripMargin var scopeResult: Map[String, ObjectRepresentation] = Map() setSessionManager { executor => @@ -100,14 +95,12 @@ class DebugServerTest "be able to list bindings it has created" in { val code = """ - |import Standard.Base.Runtime.Debug + |import Standard.Base.Data.Numbers | |main = | x = 10 | y = 20 | z = x + y - | - | Debug.breakpoint |""".stripMargin var scopeResult: Map[String, ObjectRepresentation] = Map() setSessionManager { executor => @@ -126,10 +119,10 @@ class DebugServerTest "handle errors gracefully" in { val code = """ - |import Standard.Base.Runtime.Debug + |import Standard.Base.Data.Numbers | |main = - | Debug.breakpoint + | "hi" |""".stripMargin var evalResult: Either[Exception, ObjectRepresentation] = null @@ -167,11 +160,10 @@ class DebugServerTest "attach language stack traces to the exception" in { val code = """ - |import Standard.Base.Runtime.Debug |import Standard.Base.Panic.Panic | |main = - | Debug.breakpoint + | "hi" |""".stripMargin var evalResult: Either[Exception, ObjectRepresentation] = null @@ -188,7 +180,7 @@ class DebugServerTest val traceMethodNames = lastException.getStackTrace.map(_.getMethodName) traceMethodNames should contain("Panic.throw") - traceMethodNames should contain("Debug.breakpoint") + traceMethodNames should contain("Test::Test::main") } } }