Specify expression to get more advanced results on_return callback (#8331)

This commit is contained in:
Jaroslav Tulach 2023-11-20 18:47:11 +01:00 committed by GitHub
parent 53d1f727da
commit 1138dfe147
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 119 additions and 15 deletions

View File

@ -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

View File

@ -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) {
}

View File

@ -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 {

View File

@ -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);
}
}
}

View File

@ -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