diff --git a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso index b8cdf46d294..4bdec3a6cdc 100644 --- a/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso +++ b/distribution/lib/Standard/Base/0.0.0-dev/src/Meta.enso @@ -616,14 +616,25 @@ type Instrumentor ADVANCED Registers callback to be executed when a node/expression evaluation - is over. The callback `fn` gets UUID and the computed value. Usually + is over. The callback `fn` gets UUID and the computed value (or value + of `expression` if specified). Usually the value is _cached_ and returned from `on_enter` callback next time the same expression is evaluated. + > Example + Specify `expression` to _"inline evaluate"_ it. + see_a_b uuid:Text ~result = + if uuid == "expected-uuid" then + IO.println "evalutated to "+result.to_text + + Meta.meta .fn . instrument . on_return fn=see_a_b expression="a+b" . activate + Arguments: - fn: The callback function accepting UUID and computed value - on_return self (fn : Text -> Any -> Nothing) = - new = instrumentor_builtin "onReturn" [ self.impl, fn ] + - expression: Expression to evaluate on_return - by default Nothing - + e.g. to provide the return value of the function + on_return self (fn : Text -> Any -> Nothing) (expression : Text|Nothing)=Nothing = + new = instrumentor_builtin "onReturn" [ self.impl, fn, expression ] Instrumentor.Value new ## PRIVATE diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java index 007a9e1d75e..c5fed517978 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/Instrumentor.java @@ -21,6 +21,7 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks { private final Module module; private final Object onEnter; private final Object onReturn; + private final Object onReturnExpr; private final Object onCall; private final EventBinding handle; @@ -30,16 +31,18 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks { this.target = target; this.onEnter = null; this.onReturn = null; + this.onReturnExpr = null; this.onCall = null; this.handle = null; } - Instrumentor(Instrumentor orig, Object onEnter, Object onReturn, Object onCall, boolean activate) { + Instrumentor(Instrumentor orig, Object onEnter, Object onReturn, Object onReturnExpr, Object onCall, boolean activate) { this.module = orig.module; this.service = orig.service; this.target = orig.target; this.onEnter = onEnter != null ? onEnter : orig.onEnter; this.onReturn = onReturn != null ? onReturn : orig.onReturn; + this.onReturnExpr = onReturnExpr != null ? onReturnExpr : orig.onReturnExpr; this.onCall = onCall != null ? onCall : orig.onCall; this.handle = !activate ? null : service.bind( module, target, this, new Timer.Disabled() @@ -73,7 +76,12 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks { public void updateCachedResult(IdExecutionService.Info info) { try { if (onReturn != null) { - InteropLibrary.getUncached().execute(onReturn, info.getId().toString(), info.getResult()); + var iop = InteropLibrary.getUncached(); + var result = onReturnExpr == null || !iop.isString(onReturnExpr) ? + info.getResult() + : + InstrumentorEvalNode.asSuspendedEval(onReturnExpr, info); + iop.execute(onReturn, info.getId().toString(), result); } } catch (InteropException ignored) { } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorBuiltin.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorBuiltin.java index a9681276da4..fe45e841d5a 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorBuiltin.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorBuiltin.java @@ -1,20 +1,18 @@ package org.enso.interpreter.node.expression.builtin.meta; -import com.oracle.truffle.api.nodes.Node; - import org.enso.interpreter.dsl.BuiltinMethod; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.vector.ArrayLikeAtNode; -import org.enso.interpreter.runtime.data.vector.ArrayLikeCoerceToArrayNode; import org.enso.interpreter.runtime.data.vector.ArrayLikeLengthNode; import org.enso.interpreter.runtime.error.PanicException; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.nodes.Node; @BuiltinMethod( type = "Meta", @@ -33,7 +31,7 @@ public class InstrumentorBuiltin extends Node { if (atNode.executeAt(args, 0) instanceof Instrumentor b) { ret = switch (op) { case "onEnter" -> onEnter(b, atNode.executeAt(args, 1)); - case "onReturn" -> onReturn(b, atNode.executeAt(args, 1)); + case "onReturn" -> onReturn(b, atNode.executeAt(args, 1), atNode.executeAt(args, 2)); case "onCall" -> onCall(b, atNode.executeAt(args, 1)); case "activate" -> activate(b, atNode.executeAt(args, 1)); case "deactivate" -> b.deactivate(); @@ -70,16 +68,16 @@ public class InstrumentorBuiltin extends Node { @CompilerDirectives.TruffleBoundary private Object onEnter(Instrumentor b, Object arg) { if (arg instanceof Function fn) { - return new Instrumentor(b, fn, null, null, false); + return new Instrumentor(b, fn, null, null, null, false); } else { return null; } } @CompilerDirectives.TruffleBoundary - private Object onReturn(Instrumentor b, Object arg) { + private Object onReturn(Instrumentor b, Object arg, Object expr) { if (arg instanceof Function fn) { - return new Instrumentor(b, null, fn, null, false); + return new Instrumentor(b, null, fn, expr, null, false); } else { return null; } @@ -88,7 +86,7 @@ public class InstrumentorBuiltin extends Node { @CompilerDirectives.TruffleBoundary private Object onCall(Instrumentor b, Object arg) { if (arg instanceof Function fn) { - return new Instrumentor(b, null, null, fn, false); + return new Instrumentor(b, null, null, null, fn, false); } else { return null; } @@ -97,7 +95,7 @@ public class InstrumentorBuiltin extends Node { @CompilerDirectives.TruffleBoundary private Object activate(Instrumentor b, Object arg) { if (arg instanceof Function fn) { - var builder = new Instrumentor(b, null, null, null, true); + var builder = new Instrumentor(b, null, null, null, null, true); var ctx = EnsoContext.get(this); return ctx.getResourceManager().register(builder, fn); } else { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorEvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorEvalNode.java new file mode 100644 index 00000000000..abc6b8da9ec --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorEvalNode.java @@ -0,0 +1,54 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import com.oracle.truffle.api.RootCallTarget; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.nodes.RootNode; +import org.enso.interpreter.EnsoLanguage; +import org.enso.interpreter.runtime.callable.Annotation; +import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; +import org.enso.interpreter.runtime.callable.argument.CallArgumentInfo; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.polyglot.debugger.IdExecutionService; + +final class InstrumentorEvalNode extends RootNode { + private static final FunctionSchema SUSPENDED_EVAL = + new FunctionSchema( + FunctionSchema.CallerFrameAccess.NONE, + new ArgumentDefinition[] { + new ArgumentDefinition(0, "expr", null, null, ArgumentDefinition.ExecutionMode.EXECUTE), + new ArgumentDefinition(1, "info", null, null, ArgumentDefinition.ExecutionMode.EXECUTE) + }, + new boolean[] {true, true}, + new CallArgumentInfo[0], + new Annotation[0]); + private static final RootCallTarget CALL = new InstrumentorEvalNode().getCallTarget(); + + private InstrumentorEvalNode() { + super(EnsoLanguage.get(null)); + } + + static Function asSuspendedEval(Object expr, IdExecutionService.Info info) { + return new Function(CALL, null, SUSPENDED_EVAL, new Object[] {expr, info}, new Object[0]); + } + + @Override + public String getName() { + return "Instrumentor.eval"; + } + + @Override + public Object execute(VirtualFrame frame) { + var args = Function.ArgumentsHelper.getPositionalArguments(frame.getArguments()); + try { + var expr = InteropLibrary.getUncached().asString(args[0]); + var info = (IdExecutionService.Info) args[1]; + return info.eval(expr); + } catch (UnsupportedMessageException ex) { + throw new PanicException(args[0], this); + } + } +} diff --git a/test/Tests/src/Semantic/Instrumentor_Spec.enso b/test/Tests/src/Semantic/Instrumentor_Spec.enso index 4e130f4fad3..5be768af2d0 100644 --- a/test/Tests/src/Semantic/Instrumentor_Spec.enso +++ b/test/Tests/src/Semantic/Instrumentor_Spec.enso @@ -14,11 +14,12 @@ fib2 n f=1 s=1 = spec = Test.group "Instrument fibonacci" <| + a_plus_b_uuid = "00000000-aaaa-bbbb-0000-000000000000" # UUID for a+b + Test.specify "collect and filter on return updates" <| b = Vector.new_builder collect uuid:Text result = - a_plus_b_uuid = "00000000-aaaa-bbbb-0000-000000000000" # UUID for a+b if uuid == a_plus_b_uuid then b.append result Nothing @@ -44,6 +45,38 @@ spec = # no more instrumenting after finalize b.to_vector.length . should_equal 1 + Test.specify "access local variables " <| + b = Vector.new_builder + + collect uuid:Text ~result = + if uuid == a_plus_b_uuid then + case result of + v : Vector -> b.append_vector_range v + anything -> Test.fail <| "Should be a number: " + anything.to_text + Nothing + + expr = "[a, b, a-b]" + instrumenter = Meta.meta .fib . instrument . on_return collect expression=expr . activate + + instrumenter . with _-> + result = fib 10 + + v = b.to_vector + + v.length . should_equal 3 + result . should_equal 89 + v . should_equal [55, 34, 21] + + instrumenter.finalize + # no op: + instrumenter.finalize + + result = fib 10 + result . should_equal 89 + + # no more instrumenting after finalize + b.to_vector.length . should_equal 3 + Test.specify "replay with caches and specify different result" <| replay uuid:Text = case uuid of "00000000-ffff-bbbb-0000-000000000000" -> 42