diff --git a/build.sbt b/build.sbt index 96c6a256a5a4..4a78eb743875 100644 --- a/build.sbt +++ b/build.sbt @@ -1495,6 +1495,7 @@ lazy val `runtime-instrument-common` = lazy val `runtime-instrument-id-execution` = (project in file("engine/runtime-instrument-id-execution")) .settings( + frgaalJavaCompilerSetting, inConfig(Compile)(truffleRunOptionsSettings), instrumentationSettings ) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java index 16d3fd03a420..994b93542eaf 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java @@ -1,13 +1,9 @@ package org.enso.interpreter.instrument; -import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.instrumentation.EventBinding; -import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; -import com.oracle.truffle.api.nodes.RootNode; import java.util.Arrays; import java.util.Objects; import java.util.UUID; -import java.util.function.Consumer; + import org.enso.interpreter.instrument.profiling.ProfilingInfo; import org.enso.interpreter.node.MethodRootNode; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; @@ -21,40 +17,69 @@ import org.enso.logger.masking.MaskedString; import org.enso.pkg.QualifiedName; +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.instrumentation.EventBinding; +import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; +import com.oracle.truffle.api.nodes.RootNode; + public interface IdExecutionService { String INSTRUMENT_ID = "id-value-extractor"; + public interface Callbacks { + /** Finds out previously computed result for given id. If + * a result is returned, then the execution of given node is skipped + * and the value is returned back. + * + * @param nodeId identification of the node to be computed + * @return {@code null} should the execution of the node be performed; + * any other value to skip the execution and return the value as a + * result. + */ + Object findCachedResult(UUID nodeId); + + /** Notifies when an execution of a node is over. + * @param nodeId identification of the node to be computed + * @param result the just computed result + * @param isPanic was the result a panic? + * @param nanoElapsedTime how long it took to compute the result? + */ + void updateCachedResult(UUID nodeId, Object result, boolean isPanic, long nanoElapsedTime); + + /** Notification when a returned value is a function. + * + * @param nodeId identification of the node to be computed + * @param result info about function call + * @return {@code null} should the execution of the node be performed; + * any other value to skip the execution and return the value as a + * result. + */ + Object onFunctionReturn(UUID nodeId, FunctionCallInstrumentationNode.FunctionCall result); + + /** Notification on an exception. + * @param e the reported exception + */ + void onExceptionalCallback(Exception e); + } + + /** * Attach a new event node factory to observe identified nodes within given function. * * @param module module that contains the code * @param entryCallTarget the call target being observed. - * @param cache the precomputed expression values. - * @param methodCallsCache the storage tracking the executed method calls. - * @param syncState the synchronization state of runtime updates. + * @param callbacks the interface to receive notifications * @param timer the execution timer. - * @param nextExecutionItem the next item scheduled for execution. - * @param functionCallCallback the consumer of function call events. - * @param onComputedCallback the consumer of the computed value events. - * @param onCachedCallback the consumer of the cached value events. - * @param onExceptionalCallback the consumer of the exceptional events. * @return a reference to the attached event node factory. */ EventBinding bind( Module module, CallTarget entryCallTarget, - RuntimeCache cache, - MethodCallsCache methodCallsCache, - UpdatesSynchronizationState syncState, - Timer timer, - UUID nextExecutionItem, - Consumer functionCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, - Consumer onExceptionalCallback); + Callbacks callbacks, + Timer timer + ); /** A class for notifications about functions being called in the course of execution. */ - class ExpressionCall { + final class ExpressionCall { private final UUID expressionId; private final FunctionCallInstrumentationNode.FunctionCall call; @@ -81,7 +106,7 @@ public FunctionCallInstrumentationNode.FunctionCall getCall() { } /** A class for notifications about identified expressions' values being computed. */ - class ExpressionValue { + final class ExpressionValue { private final UUID expressionId; private final Object value; private final String type; diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java new file mode 100644 index 000000000000..5a684d6ec161 --- /dev/null +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionCallbacks.java @@ -0,0 +1,164 @@ +package org.enso.interpreter.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +import org.enso.interpreter.instrument.IdExecutionService; +import org.enso.interpreter.instrument.IdExecutionService.ExpressionCall; +import org.enso.interpreter.instrument.IdExecutionService.ExpressionValue; +import org.enso.interpreter.instrument.IdExecutionService.FunctionCallInfo; +import org.enso.interpreter.instrument.MethodCallsCache; +import org.enso.interpreter.instrument.RuntimeCache; +import org.enso.interpreter.instrument.UpdatesSynchronizationState; +import org.enso.interpreter.instrument.profiling.ExecutionTime; +import org.enso.interpreter.instrument.profiling.ProfilingInfo; +import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; +import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode; +import org.enso.interpreter.runtime.callable.UnresolvedSymbol; +import org.enso.interpreter.runtime.data.Type; +import org.enso.interpreter.runtime.type.Constants; + +import com.oracle.truffle.api.CompilerDirectives; + +final class ExecutionCallbacks implements IdExecutionService.Callbacks { + + private final UUID nextExecutionItem; + private final RuntimeCache cache; + private final MethodCallsCache methodCallsCache; + private final UpdatesSynchronizationState syncState; + private final Map calls = new HashMap<>(); + private final Consumer onCachedCallback; + private final Consumer onComputedCallback; + private final Consumer functionCallCallback; + private final Consumer onExceptionalCallback; + + /** Creates callbacks instance. + * + * @param cache the precomputed expression values. + * @param methodCallsCache the storage tracking the executed updateCachedResult calls. + * @param syncState the synchronization state of runtime updates. + * @param nextExecutionItem the next item scheduled for execution. + * @param functionCallCallback the consumer of function call events. + * @param onComputedCallback the consumer of the computed value events. + * @param onCachedCallback the consumer of the cached value events. + * @param onExceptionalCallback the consumer of the exceptional events. + */ + ExecutionCallbacks( + UUID nextExecutionItem, + RuntimeCache cache, MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, + Consumer onCachedCallback, Consumer onComputedCallback, + Consumer functionCallCallback, Consumer onExceptionalCallback + ) { + this.nextExecutionItem = nextExecutionItem; + this.cache = cache; + this.methodCallsCache = methodCallsCache; + this.syncState = syncState; + this.onCachedCallback = onCachedCallback; + this.onComputedCallback = onComputedCallback; + this.functionCallCallback = functionCallCallback; + this.onExceptionalCallback = onExceptionalCallback; + } + + @CompilerDirectives.TruffleBoundary + public final Object findCachedResult(UUID nodeId) { + // Add a flag to say it was cached. + // An array of `ProfilingInfo` in the value update. + Object result = cache.get(nodeId); + // When executing the call stack we need to capture the FunctionCall of the next (top) stack + // item in the `functionCallCallback`. We allow to execute the cached `stackTop` value to be + // able to continue the stack execution, and unwind later from the `onReturnValue` callback. + if (result != null && !nodeId.equals(nextExecutionItem)) { + var value = new ExpressionValue( + nodeId, + result, + cache.getType(nodeId), + typeOf(result), + calls.get(nodeId), + cache.getCall(nodeId), + new ProfilingInfo[]{ExecutionTime.empty()}, + true + ); + onCachedCallback.accept(value); + return result; + } + return null; + } + + @CompilerDirectives.TruffleBoundary + public final void updateCachedResult(UUID nodeId, Object result, boolean isPanic, long nanoTimeElapsed) { + String resultType = typeOf(result); + String cachedType = cache.getType(nodeId); + FunctionCallInfo call = functionCallInfoById(nodeId); + FunctionCallInfo cachedCall = cache.getCall(nodeId); + ProfilingInfo[] profilingInfo = new ProfilingInfo[]{new ExecutionTime(nanoTimeElapsed)}; + + ExpressionValue expressionValue + = new ExpressionValue(nodeId, result, resultType, cachedType, call, cachedCall, profilingInfo, false); + syncState.setExpressionUnsync(nodeId); + syncState.setVisualizationUnsync(nodeId); + + // Panics are not cached because a panic can be fixed by changing seemingly unrelated code, + // like imports, and the invalidation mechanism can not always track those changes and + // appropriately invalidate all dependent expressions. + if (!isPanic) { + cache.offer(nodeId, result); + cache.putCall(nodeId, call); + } + cache.putType(nodeId, resultType); + + passExpressionValueToCallback(expressionValue); + if (isPanic) { + // We mark the node as executed so that it is not reported as not executed call after the + // program execution is complete. If we clear the call from the cache instead, it will mess + // up the `typeChanged` field of the expression update. + methodCallsCache.setExecuted(nodeId); + } + } + + @CompilerDirectives.TruffleBoundary + public final Object onFunctionReturn(UUID nodeId, FunctionCallInstrumentationNode.FunctionCall result) { + calls.put(nodeId, FunctionCallInfo.fromFunctionCall(result)); + functionCallCallback.accept(new ExpressionCall(nodeId, result)); + // Return cached value after capturing the enterable function call in `functionCallCallback` + Object cachedResult = cache.get(nodeId); + if (cachedResult != null) { + return cachedResult; + } + methodCallsCache.setExecuted(nodeId); + return null; + } + + @CompilerDirectives.TruffleBoundary + @Override + public final void onExceptionalCallback(Exception e) { + onExceptionalCallback.accept(e); + } + + @CompilerDirectives.TruffleBoundary + private void passExpressionValueToCallback(ExpressionValue expressionValue) { + onComputedCallback.accept(expressionValue); + } + + @CompilerDirectives.TruffleBoundary + private FunctionCallInfo functionCallInfoById(UUID nodeId) { + return calls.get(nodeId); + } + + private String typeOf(Object value) { + String resultType; + if (value instanceof UnresolvedSymbol) { + resultType = Constants.UNRESOLVED_SYMBOL; + } else { + var typeOfNode = TypeOfNode.getUncached(); + Object typeResult = typeOfNode.execute(value); + if (typeResult instanceof Type t) { + resultType = t.getQualifiedName().toString(); + } else { + resultType = null; + } + } + return resultType; + } +} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java index 84950b7400e0..eb29045ccd34 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -1,27 +1,19 @@ package org.enso.interpreter.service; -import com.oracle.truffle.api.CallTarget; -import com.oracle.truffle.api.CompilerDirectives; -import com.oracle.truffle.api.TruffleLogger; -import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.instrumentation.EventBinding; -import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; -import com.oracle.truffle.api.interop.ArityException; -import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.UnknownIdentifierException; -import com.oracle.truffle.api.interop.UnsupportedMessageException; -import com.oracle.truffle.api.interop.UnsupportedTypeException; -import com.oracle.truffle.api.nodes.Node; -import com.oracle.truffle.api.nodes.RootNode; -import com.oracle.truffle.api.source.SourceSection; +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Consumer; + import org.enso.compiler.context.SimpleUpdate; import org.enso.interpreter.instrument.Endpoint; import org.enso.interpreter.instrument.IdExecutionService; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.NotificationHandler; import org.enso.interpreter.instrument.RuntimeCache; -import org.enso.interpreter.instrument.UpdatesSynchronizationState; import org.enso.interpreter.instrument.Timer; +import org.enso.interpreter.instrument.UpdatesSynchronizationState; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.node.expression.builtin.text.util.TypeToDisplayTextNodeGen; import org.enso.interpreter.runtime.EnsoContext; @@ -31,28 +23,37 @@ import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.state.State; -import org.enso.interpreter.service.error.TypeNotFoundException; import org.enso.interpreter.service.error.FailedToApplyEditsException; import org.enso.interpreter.service.error.MethodNotFoundException; import org.enso.interpreter.service.error.ModuleNotFoundException; import org.enso.interpreter.service.error.SourceNotFoundException; +import org.enso.interpreter.service.error.TypeNotFoundException; import org.enso.lockmanager.client.ConnectedLockManager; import org.enso.polyglot.LanguageInfo; import org.enso.polyglot.MethodNames; import org.enso.text.editing.JavaEditorAdapter; import org.enso.text.editing.model; -import java.io.File; -import java.io.IOException; -import java.util.Optional; -import java.util.UUID; -import java.util.function.Consumer; +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.TruffleLogger; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.instrumentation.EventBinding; +import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.nodes.RootNode; +import com.oracle.truffle.api.source.SourceSection; /** * A service allowing externally-triggered code execution, registered by an instance of the * language. */ -public class ExecutionService { +public final class ExecutionService { private static final String MAIN_METHOD = "main"; private final EnsoContext context; @@ -161,21 +162,17 @@ public void execute( if (src == null) { throw new SourceNotFoundException(call.getFunction().getName()); } + var callbacks = new ExecutionCallbacks( + nextExecutionItem, cache, methodCallsCache, syncState, + onCachedCallback, onComputedCallback, funCallCallback, onExceptionalCallback + ); Optional> eventNodeFactory = - idExecutionInstrument.map( - service -> - service.bind( - module, - call.getFunction().getCallTarget(), - cache, - methodCallsCache, - syncState, - this.timer, - nextExecutionItem, - funCallCallback, - onComputedCallback, - onCachedCallback, - onExceptionalCallback)); + idExecutionInstrument.map(service -> service.bind( + module, + call.getFunction().getCallTarget(), + callbacks, + this.timer + )); Object p = context.getThreadManager().enter(); try { execute.getCallTarget().call(call); @@ -308,21 +305,17 @@ public Object callFunctionWithInstrument( Consumer onExceptionalCallback = (value) -> context.getLogger().finest("_ON_ERROR " + value); + var callbacks = new ExecutionCallbacks( + nextExecutionItem, cache, methodCallsCache, syncState, + onCachedCallback, onComputedCallback, funCallCallback, onExceptionalCallback + ); Optional> eventNodeFactory = - idExecutionInstrument.map( - service -> - service.bind( - module, - entryCallTarget, - cache, - methodCallsCache, - syncState, - this.timer, - nextExecutionItem, - funCallCallback, - onComputedCallback, - onCachedCallback, - onExceptionalCallback)); + idExecutionInstrument.map(service -> service.bind( + module, + entryCallTarget, + callbacks, + this.timer + )); Object p = context.getThreadManager().enter(); try { return call.getCallTarget().call(function, arguments); diff --git a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java index e20ec922b957..534addb25274 100644 --- a/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java @@ -1,5 +1,6 @@ package org.enso.interpreter.instrument; + import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.RootCallTarget; @@ -8,32 +9,31 @@ import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.FrameInstanceVisitor; import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.instrumentation.*; +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.SourceSectionFilter; +import com.oracle.truffle.api.instrumentation.StandardTags; +import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.nodes.Node; -import java.util.HashMap; -import java.util.Map; + import java.util.UUID; -import java.util.function.Consumer; -import org.enso.interpreter.instrument.profiling.ExecutionTime; -import org.enso.interpreter.instrument.profiling.ProfilingInfo; + import org.enso.interpreter.node.ClosureRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; -import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode; import org.enso.interpreter.runtime.Module; -import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.control.TailCallException; -import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.error.DataflowError; import org.enso.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.error.PanicSentinel; import org.enso.interpreter.runtime.state.State; import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.IdentifiedTag; -import org.enso.interpreter.runtime.type.Constants; /** An instrument for getting values from AST-identified expressions. */ @TruffleInstrument.Registration( @@ -57,381 +57,206 @@ protected void onCreate(Env env) { /** Factory for creating new id event nodes. */ private static class IdEventNodeFactory implements ExecutionEventNodeFactory { - private final CallTarget entryCallTarget; - private final Consumer functionCallCallback; - private final Consumer onComputedCallback; - private final Consumer onCachedCallback; - private final Consumer onExceptionalCallback; - private final RuntimeCache cache; - private final MethodCallsCache methodCallsCache; - private final UpdatesSynchronizationState syncState; - private final UUID nextExecutionItem; - private final Map calls = new HashMap<>(); - private final Timer timer; - - /** - * Creates a new event node factory. - * - * @param entryCallTarget the call target being observed. - * @param cache the precomputed expression values. - * @param methodCallsCache the storage tracking the executed method calls. - * @param syncState the synchronization state of runtime updates. - * @param nextExecutionItem the next item scheduled for execution. - * @param functionCallCallback the consumer of function call events. - * @param onComputedCallback the consumer of the computed value events. - * @param onCachedCallback the consumer of the cached value events. - * @param onExceptionalCallback the consumer of the exceptional events. - * @param timer the timer for timing execution - */ - public IdEventNodeFactory( - CallTarget entryCallTarget, - RuntimeCache cache, - MethodCallsCache methodCallsCache, - UpdatesSynchronizationState syncState, - UUID nextExecutionItem, // The expression ID - Consumer functionCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, - Consumer onExceptionalCallback, - Timer timer) { - this.entryCallTarget = entryCallTarget; - this.cache = cache; - this.methodCallsCache = methodCallsCache; - this.syncState = syncState; - this.nextExecutionItem = nextExecutionItem; - this.functionCallCallback = functionCallCallback; - this.onComputedCallback = onComputedCallback; - this.onCachedCallback = onCachedCallback; - this.onExceptionalCallback = onExceptionalCallback; - this.timer = timer; - } - - @Override - public ExecutionEventNode create(EventContext context) { - return new IdExecutionEventNode(context, entryCallTarget, cache, methodCallsCache, syncState, - nextExecutionItem, calls, functionCallCallback, onComputedCallback, onCachedCallback, onExceptionalCallback, timer); - } - } - - /** The execution event node class used by this instrument. */ - private static class IdExecutionEventNode extends ExecutionEventNode { - private final EventContext context; private final CallTarget entryCallTarget; - private final Consumer functionCallCallback; - private final Consumer onComputedCallback; - private final Consumer onCachedCallback; - private final Consumer onExceptionalCallback; - private final RuntimeCache cache; - private final MethodCallsCache callsCache; - private final UpdatesSynchronizationState syncState; - private final UUID nextExecutionItem; - private final Map calls; + private final Callbacks callbacks; private final Timer timer; - private long nanoTimeElapsed = 0; - private @Child TypeOfNode typeOfNode = TypeOfNode.build(); /** - * Creates a new event node. + * Creates a new event node factory. * * @param entryCallTarget the call target being observed. - * @param cache the precomputed expression values. - * @param methodCallsCache the storage tracking the executed method calls. - * @param syncState the synchronization state of runtime updates. - * @param nextExecutionItem the next item scheduled for execution. - * @param functionCallCallback the consumer of function call events. - * @param onComputedCallback the consumer of the computed value events. - * @param onCachedCallback the consumer of the cached value events. - * @param onExceptionalCallback the consumer of the exceptional events. + * @param callbacks communication with users * @param timer the timer for timing execution */ - public IdExecutionEventNode( - EventContext context, - CallTarget entryCallTarget, - RuntimeCache cache, - MethodCallsCache methodCallsCache, - UpdatesSynchronizationState syncState, - UUID nextExecutionItem, // The expression ID - Map calls, - Consumer functionCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, - Consumer onExceptionalCallback, - Timer timer) { - this.context = context; + IdEventNodeFactory( + CallTarget entryCallTarget, + Callbacks callbacks, + Timer timer + ) { this.entryCallTarget = entryCallTarget; - this.cache = cache; - this.calls = calls; - this.callsCache = methodCallsCache; - this.syncState = syncState; - this.nextExecutionItem = nextExecutionItem; - this.functionCallCallback = functionCallCallback; - this.onComputedCallback = onComputedCallback; - this.onCachedCallback = onCachedCallback; - this.onExceptionalCallback = onExceptionalCallback; + this.callbacks = callbacks; this.timer = timer; } @Override - public Object onUnwind(VirtualFrame frame, Object info) { - return info; - } - - @Override - public void onEnter(VirtualFrame frame) { - if (!isTopFrame(entryCallTarget)) { - return; - } - onEnterImpl(); - } - - @CompilerDirectives.TruffleBoundary - private void onEnterImpl() { - UUID nodeId = getNodeId(context.getInstrumentedNode()); - - // Add a flag to say it was cached. - // An array of `ProfilingInfo` in the value update. - - Object result = cache.get(nodeId); - // When executing the call stack we need to capture the FunctionCall of the next (top) stack - // item in the `functionCallCallback`. We allow to execute the cached `stackTop` value to be - // able to continue the stack execution, and unwind later from the `onReturnValue` callback. - if (result != null && !nodeId.equals(nextExecutionItem)) { - - onCachedCallback.accept( - new ExpressionValue( - nodeId, - result, - cache.getType(nodeId), - typeOf(result), - calls.get(nodeId), - cache.getCall(nodeId), - new ProfilingInfo[] {ExecutionTime.empty()}, - true)); - throw context.createUnwind(result); - } - - nanoTimeElapsed = timer.getTime(); + public ExecutionEventNode create(EventContext context) { + return new IdExecutionEventNode(context); } /** - * Triggered when a node (either a function call sentry or an identified expression) finishes - * execution. - * - * @param frame the current execution frame. - * @param result the result of executing the node this method was triggered for. + * The execution event node class used by this instrument. */ - @Override - public void onReturnValue(VirtualFrame frame, Object result) { - nanoTimeElapsed = timer.getTime() - nanoTimeElapsed; - if (!isTopFrame(entryCallTarget)) { - return; - } - Node node = context.getInstrumentedNode(); + private class IdExecutionEventNode extends ExecutionEventNode { - if (node instanceof FunctionCallInstrumentationNode - && result instanceof FunctionCallInstrumentationNode.FunctionCall functionCall) { - UUID nodeId = ((FunctionCallInstrumentationNode) node).getId(); - onFunctionReturn(nodeId, functionCall, context); - } else if (node instanceof ExpressionNode) { - onExpressionReturn(result, node, context); - } - } + private final EventContext context; + private long nanoTimeElapsed = 0; - @Override - public void onReturnExceptional(VirtualFrame frame, Throwable exception) { - if (exception instanceof TailCallException) { - onTailCallReturn(exception, Function.ArgumentsHelper.getState(frame.getArguments())); - } else if (exception instanceof PanicException panicException) { - onReturnValue(frame, new PanicSentinel(panicException, context.getInstrumentedNode())); - } else if (exception instanceof AbstractTruffleException) { - onReturnValue(frame, exception); + /** + * Creates a new event node. + * + * @param context location where the node is being inserted + */ + IdExecutionEventNode(EventContext context) { + this.context = context; } - } - - private void onExpressionReturn(Object result, Node node, EventContext context) throws ThreadDeath { - boolean isPanic = result instanceof AbstractTruffleException && !(result instanceof DataflowError); - UUID nodeId = ((ExpressionNode) node).getId(); - String resultType = typeOf(result); - String cachedType = cache.getType(nodeId); - FunctionCallInfo call = functionCallInfoById(nodeId); - FunctionCallInfo cachedCall = cache.getCall(nodeId); - ProfilingInfo[] profilingInfo = new ProfilingInfo[] {new ExecutionTime(nanoTimeElapsed)}; - - ExpressionValue expressionValue = - new ExpressionValue(nodeId, result, resultType, cachedType, call, cachedCall, profilingInfo, false); - syncState.setExpressionUnsync(nodeId); - syncState.setVisualizationUnsync(nodeId); + @Override + public Object onUnwind(VirtualFrame frame, Object info) { + return info; + } - // Panics are not cached because a panic can be fixed by changing seemingly unrelated code, - // like imports, and the invalidation mechanism can not always track those changes and - // appropriately invalidate all dependent expressions. - if (!isPanic) { - cache.offer(nodeId, result); - cache.putCall(nodeId, call); + @Override + public void onEnter(VirtualFrame frame) { + if (!isTopFrame(entryCallTarget)) { + return; + } + onEnterImpl(); } - cache.putType(nodeId, resultType); - passExpressionValueToCallback(expressionValue); - if (isPanic) { - // We mark the node as executed so that it is not reported as not executed call after the - // program execution is complete. If we clear the call from the cache instead, it will mess - // up the `typeChanged` field of the expression update. - callsCache.setExecuted(nodeId); - throw context.createUnwind(result); + @CompilerDirectives.TruffleBoundary + private void onEnterImpl() { + UUID nodeId = getNodeId(context.getInstrumentedNode()); + var result = callbacks.findCachedResult(nodeId); + if (result != null) { + throw context.createUnwind(result); + } + nanoTimeElapsed = timer.getTime(); } - } - private String typeOf(Object value) { - String resultType; - if (value instanceof UnresolvedSymbol) { - resultType = Constants.UNRESOLVED_SYMBOL; - } else { - Object typeResult = typeOfNode.execute(value); - if (typeResult instanceof Type t) { - resultType = t.getQualifiedName().toString(); - } else { - resultType = null; + /** + * Triggered when a node (either a function call sentry or an identified + * expression) finishes execution. + * + * @param frame the current execution frame. + * @param result the result of executing the node this method was + * triggered for. + */ + @Override + public void onReturnValue(VirtualFrame frame, Object result) { + nanoTimeElapsed = timer.getTime() - nanoTimeElapsed; + if (!isTopFrame(entryCallTarget)) { + return; + } + Node node = context.getInstrumentedNode(); + + if (node instanceof FunctionCallInstrumentationNode + && result instanceof FunctionCallInstrumentationNode.FunctionCall functionCall) { + UUID nodeId = ((FunctionCallInstrumentationNode) node).getId(); + var cachedResult = callbacks.onFunctionReturn(nodeId, functionCall); + if (cachedResult != null) { + throw context.createUnwind(cachedResult); } + } else if (node instanceof ExpressionNode) { + onExpressionReturn(result, node, context, nanoTimeElapsed); + } } - return resultType; - } - @CompilerDirectives.TruffleBoundary - private void passExpressionValueToCallback(ExpressionValue expressionValue) { - onComputedCallback.accept(expressionValue); - } + @Override + public void onReturnExceptional(VirtualFrame frame, Throwable exception) { + if (exception instanceof TailCallException) { + onTailCallReturn(exception, Function.ArgumentsHelper.getState(frame.getArguments())); + } else if (exception instanceof PanicException panicException) { + onReturnValue(frame, new PanicSentinel(panicException, context.getInstrumentedNode())); + } else if (exception instanceof AbstractTruffleException) { + onReturnValue(frame, exception); + } + } - @CompilerDirectives.TruffleBoundary - private FunctionCallInfo functionCallInfoById(UUID nodeId) { - return calls.get(nodeId); - } + private void onExpressionReturn(Object result, Node node, EventContext context, long howLong) throws ThreadDeath { + boolean isPanic = result instanceof AbstractTruffleException && !(result instanceof DataflowError); + UUID nodeId = ((ExpressionNode) node).getId(); - @CompilerDirectives.TruffleBoundary - private void onFunctionReturn(UUID nodeId, FunctionCallInstrumentationNode.FunctionCall result, EventContext context) throws ThreadDeath { - calls.put(nodeId, FunctionCallInfo.fromFunctionCall(result)); - functionCallCallback.accept(new ExpressionCall(nodeId, result)); - // Return cached value after capturing the enterable function call in `functionCallCallback` - Object cachedResult = cache.get(nodeId); - if (cachedResult != null) { - throw context.createUnwind(cachedResult); + callbacks.updateCachedResult(nodeId, result, isPanic, howLong); + if (isPanic) { + throw context.createUnwind(result); } - callsCache.setExecuted(nodeId); - } + } - @CompilerDirectives.TruffleBoundary - private void onTailCallReturn(Throwable exception, State state) { + @CompilerDirectives.TruffleBoundary + private void onTailCallReturn(Throwable exception, State state) { try { - TailCallException tailCallException = (TailCallException) exception; - FunctionCallInstrumentationNode.FunctionCall functionCall = - new FunctionCallInstrumentationNode.FunctionCall( - tailCallException.getFunction(), - state, - tailCallException.getArguments()); - Object result = InteropLibrary.getFactory().getUncached().execute(functionCall); - onReturnValue(null, result); + TailCallException tailCallException = (TailCallException) exception; + FunctionCallInstrumentationNode.FunctionCall functionCall + = new FunctionCallInstrumentationNode.FunctionCall( + tailCallException.getFunction(), + state, + tailCallException.getArguments()); + Object result = InteropLibrary.getFactory().getUncached().execute(functionCall); + onReturnValue(null, result); } catch (InteropException e) { - onExceptionalCallback.accept(e); + callbacks.onExceptionalCallback(e); } - } - - /** - * Checks if we're not inside a recursive call, i.e. the {@link #entryCallTarget} only appears - * in the stack trace once. - * - * @return {@code true} if it's not a recursive call, {@code false} otherwise. - */ - private boolean isTopFrame(CallTarget entryCallTarget) { - Object result = - Truffle.getRuntime() - .iterateFrames( - new FrameInstanceVisitor() { - boolean seenFirst = false; - - @Override - public Object visitFrame(FrameInstance frameInstance) { - CallTarget ct = frameInstance.getCallTarget(); - if (ct != entryCallTarget) { - return null; - } - if (seenFirst) { - return new Object(); - } else { - seenFirst = true; - return null; - } - } - }); - return result == null; - } + } - private UUID getNodeId(Node node) { - if (node instanceof ExpressionNode) { - return ((ExpressionNode) node).getId(); + /** + * Checks if we're not inside a recursive call, i.e. the + * {@link #entryCallTarget} only appears in the stack trace once. + * + * @return {@code true} if it's not a recursive call, {@code false} + * otherwise. + */ + private boolean isTopFrame(CallTarget entryCallTarget) { + Object result = Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor() { + boolean seenFirst = false; + + @Override + public Object visitFrame(FrameInstance frameInstance) { + CallTarget ct = frameInstance.getCallTarget(); + if (ct != entryCallTarget) { + return null; + } + if (seenFirst) { + return new Object(); + } else { + seenFirst = true; + return null; + } + } + }); + return result == null; } - if (node instanceof FunctionCallInstrumentationNode) { - return ((FunctionCallInstrumentationNode) node).getId(); + + private static UUID getNodeId(Node node) { + return switch (node) { + case ExpressionNode n -> n.getId(); + case FunctionCallInstrumentationNode n -> n.getId(); + case null -> null; + default -> null; + }; } - return null; } } /** - * Attach a new event node factory to observe identified nodes within given function. + * Attach a new event node factory to observe identified nodes within given + * function. * * @param module module that contains the code * @param entryCallTarget the call target being observed. - * @param cache the precomputed expression values. - * @param methodCallsCache the storage tracking the executed method calls. - * @param syncState the synchronization state of runtime updates. + * @param callbacks the precomputed expression values. * @param timer the execution timer. - * @param nextExecutionItem the next item scheduled for execution. - * @param functionCallCallback the consumer of function call events. - * @param onComputedCallback the consumer of the computed value events. - * @param onCachedCallback the consumer of the cached value events. - * @param onExceptionalCallback the consumer of the exceptional events. * @return a reference to the attached event node factory. */ @Override public EventBinding bind( - Module module, - CallTarget entryCallTarget, - RuntimeCache cache, - MethodCallsCache methodCallsCache, - UpdatesSynchronizationState syncState, - Timer timer, - UUID nextExecutionItem, - Consumer functionCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, - Consumer onExceptionalCallback) { + Module module, + CallTarget entryCallTarget, + Callbacks callbacks, + Timer timer + ) { var builder = SourceSectionFilter.newBuilder() - .tagIs(StandardTags.ExpressionTag.class, StandardTags.CallTag.class) - .tagIs(IdentifiedTag.class) - .tagIsNot(AvoidIdInstrumentationTag.class) - .sourceIs(module::isModuleSource); + .tagIs(StandardTags.ExpressionTag.class, StandardTags.CallTag.class) + .tagIs(IdentifiedTag.class) + .tagIsNot(AvoidIdInstrumentationTag.class) + .sourceIs(module::isModuleSource); if (entryCallTarget instanceof RootCallTarget r && r.getRootNode() instanceof ClosureRootNode c && c.getSourceSection() != null) { final int firstFunctionLine = c.getSourceSection().getStartLine(); final int afterFunctionLine = c.getSourceSection().getEndLine() + 1; builder.lineIn(SourceSectionFilter.IndexRange.between(firstFunctionLine, afterFunctionLine)); } - SourceSectionFilter filter = builder.build(); - - return env.getInstrumenter() - .attachExecutionEventFactory( - filter, - new IdEventNodeFactory( - entryCallTarget, - cache, - methodCallsCache, - syncState, - nextExecutionItem, - functionCallCallback, - onComputedCallback, - onCachedCallback, - onExceptionalCallback, - timer)); + var filter = builder.build(); + var factory = new IdEventNodeFactory(entryCallTarget, callbacks, timer); + return env.getInstrumenter().attachExecutionEventFactory(filter, factory); } - }