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 447b8fdf576c..0cd42fa89fc0 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 @@ -195,6 +195,13 @@ type Unresolved_Symbol scope : Any scope self = get_unresolved_symbol_scope self.value + ## ADVANCED + GROUP Metadata + + Starts building an instrumentation for a given node + instrument : Instrumentor + instrument self = Instrumentor.Value (instrumentor_builtin "newBuilder" [ self.value ]) + type Error ## PRIVATE ADVANCED @@ -561,3 +568,24 @@ get_short_type_name typ = @Builtin_Method "Meta.get_short_type_name" - constructor: the constructor to get the declaring type of. get_constructor_declaring_type : Any -> Any get_constructor_declaring_type constructor = @Builtin_Method "Meta.get_constructor_declaring_type" + +instrumentor_builtin op args = @Builtin_Method "Meta.instrumentor_builtin" + +# Builder to create instrumentation for a function +type Instrumentor + Value impl + on_enter self (fn : Text -> Any | Nothing) = + instrumentor_builtin "onEnter" [ self.impl, fn ] + self + on_return self (fn : Text -> Any -> Nothing) = + instrumentor_builtin "onReturn" [ self.impl, fn ] + self + on_function self (fn : Text -> Any | Nothing) = + instrumentor_builtin "onFunction" [ self.impl, fn ] + self + on_exception self (fn : Any -> Nothing) = + instrumentor_builtin "onException" [ self.impl, fn ] + self + activate self = + instrumentor_builtin "activate" [ self.impl ] + self diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java new file mode 100644 index 000000000000..1b69839a2aff --- /dev/null +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/IdExecutionService.java @@ -0,0 +1,62 @@ +package org.enso.polyglot.debugger; + +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.interop.TruffleObject; +import java.util.UUID; + +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, TruffleObject 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 callbacks the interface to receive notifications + * @param timer the execution timer. + * @return a reference to the attached event node factory. + */ + EventBinding bind( + TruffleObject module, CallTarget entryCallTarget, Callbacks callbacks, Object timer); +} 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 deleted file mode 100644 index 994b93542eaf..000000000000 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java +++ /dev/null @@ -1,333 +0,0 @@ -package org.enso.interpreter.instrument; - -import java.util.Arrays; -import java.util.Objects; -import java.util.UUID; - -import org.enso.interpreter.instrument.profiling.ProfilingInfo; -import org.enso.interpreter.node.MethodRootNode; -import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; -import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; -import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; -import org.enso.interpreter.runtime.Module; -import org.enso.interpreter.runtime.callable.atom.AtomConstructor; -import org.enso.interpreter.runtime.callable.function.Function; -import org.enso.interpreter.runtime.callable.function.FunctionSchema; -import org.enso.interpreter.runtime.data.Type; -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 callbacks the interface to receive notifications - * @param timer the execution timer. - * @return a reference to the attached event node factory. - */ - EventBinding bind( - Module module, - CallTarget entryCallTarget, - Callbacks callbacks, - Timer timer - ); - - /** A class for notifications about functions being called in the course of execution. */ - final class ExpressionCall { - private final UUID expressionId; - private final FunctionCallInstrumentationNode.FunctionCall call; - - /** - * Creates an instance of this class. - * - * @param expressionId the expression id where function call was performed. - * @param call the actual function call data. - */ - public ExpressionCall(UUID expressionId, FunctionCallInstrumentationNode.FunctionCall call) { - this.expressionId = expressionId; - this.call = call; - } - - /** @return the id of the node performing the function call. */ - public UUID getExpressionId() { - return expressionId; - } - - /** @return the function call metadata. */ - public FunctionCallInstrumentationNode.FunctionCall getCall() { - return call; - } - } - - /** A class for notifications about identified expressions' values being computed. */ - final class ExpressionValue { - private final UUID expressionId; - private final Object value; - private final String type; - private final String cachedType; - private final FunctionCallInfo callInfo; - private final FunctionCallInfo cachedCallInfo; - private final ProfilingInfo[] profilingInfo; - private final boolean wasCached; - - /** - * Creates a new instance of this class. - * - * @param expressionId the id of the expression being computed. - * @param value the value returned by computing the expression. - * @param type the type of the returned value. - * @param cachedType the cached type of the value. - * @param callInfo the function call data. - * @param cachedCallInfo the cached call data. - * @param profilingInfo the profiling information associated with this node - * @param wasCached whether or not the value was obtained from the cache - */ - public ExpressionValue( - UUID expressionId, - Object value, - String type, - String cachedType, - FunctionCallInfo callInfo, - FunctionCallInfo cachedCallInfo, - ProfilingInfo[] profilingInfo, - boolean wasCached) { - this.expressionId = expressionId; - this.value = value; - this.type = type; - this.cachedType = cachedType; - this.callInfo = callInfo; - this.cachedCallInfo = cachedCallInfo; - this.profilingInfo = profilingInfo; - this.wasCached = wasCached; - } - - @Override - public String toString() { - String profilingInfo = Arrays.toString(this.profilingInfo); - return "ExpressionValue{" - + "expressionId=" - + expressionId - + ", value=" - + (value == null ? "null" : new MaskedString(value.toString()).applyMasking()) - + ", type='" - + type - + '\'' - + ", cachedType='" - + cachedType - + '\'' - + ", callInfo=" - + callInfo - + ", cachedCallInfo=" - + cachedCallInfo - + ", profilingInfo=" - + profilingInfo - + ", wasCached=" - + wasCached - + '}'; - } - - /** @return the id of the expression computed. */ - public UUID getExpressionId() { - return expressionId; - } - - /** @return the type of the returned value. */ - public String getType() { - return type; - } - - /** @return the cached type of the value. */ - public String getCachedType() { - return cachedType; - } - - /** @return the computed value of the expression. */ - public Object getValue() { - return value; - } - - /** @return the function call data. */ - public FunctionCallInfo getCallInfo() { - return callInfo; - } - - /** @return the function call data previously associated with the expression. */ - public FunctionCallInfo getCachedCallInfo() { - return cachedCallInfo; - } - - /** @return the profiling information associated with this expression */ - public ProfilingInfo[] getProfilingInfo() { - return profilingInfo; - } - - /** @return whether or not the expression result was obtained from the cache */ - public boolean wasCached() { - return wasCached; - } - - /** @return {@code true} when the type differs from the cached value. */ - public boolean isTypeChanged() { - return !Objects.equals(type, cachedType); - } - - /** @return {@code true} when the function call differs from the cached value. */ - public boolean isFunctionCallChanged() { - return !Objects.equals(callInfo, cachedCallInfo); - } - } - - /** Points to the definition of a runtime function. */ - record FunctionPointer(QualifiedName moduleName, QualifiedName typeName, String functionName) { - - public static FunctionPointer fromFunction(Function function) { - RootNode rootNode = function.getCallTarget().getRootNode(); - - QualifiedName moduleName; - QualifiedName typeName; - String functionName; - - switch (rootNode) { - case MethodRootNode methodNode -> { - moduleName = methodNode.getModuleScope().getModule().getName(); - typeName = methodNode.getType().getQualifiedName(); - functionName = methodNode.getMethodName(); - } - case QualifiedAccessorNode qualifiedAccessor -> { - AtomConstructor atomConstructor = qualifiedAccessor.getAtomConstructor(); - moduleName = atomConstructor.getDefinitionScope().getModule().getName(); - typeName = atomConstructor.getType().getQualifiedName(); - functionName = atomConstructor.getName(); - } - case BuiltinRootNode builtinRootNode -> { - moduleName = builtinRootNode.getModuleName(); - typeName = builtinRootNode.getTypeName(); - functionName = QualifiedName.fromString(builtinRootNode.getName()).item(); - } - default -> { - moduleName = null; - typeName = null; - functionName = rootNode.getName(); - } - } - - return new FunctionPointer(moduleName, typeName, functionName); - } - - public static int[] collectNotAppliedArguments(Function function) { - FunctionSchema functionSchema = function.getSchema(); - Object[] preAppliedArguments = function.getPreAppliedArguments(); - boolean isStatic = preAppliedArguments[0] instanceof Type; - int selfArgumentPosition = isStatic ? -1 : 0; - int[] notAppliedArguments = new int[functionSchema.getArgumentsCount()]; - int notAppliedArgumentsLength = 0; - - for (int i = 0; i < functionSchema.getArgumentsCount(); i++) { - if (!functionSchema.hasPreAppliedAt(i)) { - notAppliedArguments[notAppliedArgumentsLength] = i + selfArgumentPosition; - notAppliedArgumentsLength += 1; - } - } - - return Arrays.copyOf(notAppliedArguments, notAppliedArgumentsLength); - } - } - - /** Information about the function call. */ - record FunctionCallInfo(FunctionPointer functionPointer, int[] notAppliedArguments) { - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FunctionCallInfo that = (FunctionCallInfo) o; - return Objects.equals(functionPointer, that.functionPointer) && Arrays.equals( - notAppliedArguments, that.notAppliedArguments); - } - - @Override - public int hashCode() { - int result = Objects.hash(functionPointer); - return 31 * result + Arrays.hashCode(notAppliedArguments); - } - - /** - * Creates a new instance of this record from a function call. - * - * @param call the function call. - */ - public static FunctionCallInfo fromFunctionCall(FunctionCallInstrumentationNode.FunctionCall call) { - FunctionPointer functionPointer = FunctionPointer.fromFunction(call.getFunction()); - int[] notAppliedArguments = collectNotAppliedArguments(call); - - return new FunctionCallInfo(functionPointer, notAppliedArguments); - } - - private static int[] collectNotAppliedArguments(FunctionCallInstrumentationNode.FunctionCall call) { - Object[] arguments = call.getArguments(); - int[] notAppliedArgs = new int[arguments.length]; - int notAppliedArgsSize = 0; - boolean isStatic = arguments[0] instanceof Type; - int selfTypePosition = isStatic ? -1 : 0; - - for (int i = 0; i < arguments.length; i++) { - if (arguments[i] == null) { - notAppliedArgs[notAppliedArgsSize] = i + selfTypePosition; - notAppliedArgsSize += 1; - } - } - - return Arrays.copyOf(notAppliedArgs, notAppliedArgsSize); - } - } -} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java index a39ddb26cad5..48fbcef64f50 100644 --- a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java @@ -6,13 +6,14 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import org.enso.interpreter.service.ExecutionService; /** A storage for computed values. */ public final class RuntimeCache { private final Map> cache = new HashMap<>(); private final Map types = new HashMap<>(); - private final Map calls = new HashMap<>(); + private final Map calls = new HashMap<>(); private Map weights = new HashMap<>(); /** @@ -38,7 +39,11 @@ public Object get(UUID key) { return ref != null ? ref.get() : null; } - /** Remove the value from the cache. */ + /** + * Remove the value from the cache. + * + * @return + */ public Object remove(UUID key) { SoftReference ref = cache.remove(key); return ref == null ? null : ref.get(); @@ -78,8 +83,8 @@ public String getType(UUID key) { * @return the function call that was previously associated with this expression. */ @CompilerDirectives.TruffleBoundary - public IdExecutionService.FunctionCallInfo putCall( - UUID key, IdExecutionService.FunctionCallInfo call) { + public ExecutionService.FunctionCallInfo putCall( + UUID key, ExecutionService.FunctionCallInfo call) { if (call == null) { return calls.remove(key); } @@ -88,7 +93,7 @@ public IdExecutionService.FunctionCallInfo putCall( /** @return the cached function call associated with the expression. */ @CompilerDirectives.TruffleBoundary - public IdExecutionService.FunctionCallInfo getCall(UUID key) { + public ExecutionService.FunctionCallInfo getCall(UUID key) { return calls.get(key); } 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 index 5a684d6ec161..83f86523fb92 100644 --- 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 @@ -5,10 +5,7 @@ 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.polyglot.debugger.IdExecutionService; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.RuntimeCache; import org.enso.interpreter.instrument.UpdatesSynchronizationState; @@ -19,8 +16,12 @@ import org.enso.interpreter.runtime.callable.UnresolvedSymbol; import org.enso.interpreter.runtime.data.Type; import org.enso.interpreter.runtime.type.Constants; +import org.enso.interpreter.service.ExecutionService.ExpressionCall; +import org.enso.interpreter.service.ExecutionService.ExpressionValue; +import org.enso.interpreter.service.ExecutionService.FunctionCallInfo; import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.interop.TruffleObject; final class ExecutionCallbacks implements IdExecutionService.Callbacks { @@ -118,9 +119,10 @@ public final void updateCachedResult(UUID nodeId, Object result, boolean isPanic } @CompilerDirectives.TruffleBoundary - public final Object onFunctionReturn(UUID nodeId, FunctionCallInstrumentationNode.FunctionCall result) { - calls.put(nodeId, FunctionCallInfo.fromFunctionCall(result)); - functionCallCallback.accept(new ExpressionCall(nodeId, result)); + public final Object onFunctionReturn(UUID nodeId, TruffleObject result) { + var fnCall = (FunctionCallInstrumentationNode.FunctionCall) result; + calls.put(nodeId, FunctionCallInfo.fromFunctionCall(fnCall)); + functionCallCallback.accept(new ExpressionCall(nodeId, fnCall)); // Return cached value after capturing the enterable function call in `functionCallCallback` Object cachedResult = cache.get(nodeId); if (cachedResult != null) { 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 eb29045ccd34..86c5c74dd42d 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 @@ -6,9 +6,30 @@ import java.util.UUID; import java.util.function.Consumer; +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; +import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; +import org.enso.interpreter.node.expression.builtin.BuiltinRootNode; +import org.enso.interpreter.runtime.Module; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.callable.function.FunctionSchema; +import org.enso.interpreter.runtime.data.Type; +import org.enso.logger.masking.MaskedString; +import org.enso.pkg.QualifiedName; + import org.enso.compiler.context.SimpleUpdate; import org.enso.interpreter.instrument.Endpoint; -import org.enso.interpreter.instrument.IdExecutionService; +import org.enso.polyglot.debugger.IdExecutionService; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.NotificationHandler; import org.enso.interpreter.instrument.RuntimeCache; @@ -152,9 +173,9 @@ public void execute( MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Consumer funCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, + Consumer funCallCallback, + Consumer onComputedCallback, + Consumer onCachedCallback, Consumer onExceptionalCallback) throws ArityException, SourceNotFoundException, UnsupportedMessageException, UnsupportedTypeException { @@ -206,9 +227,9 @@ public void execute( MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Consumer funCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, + Consumer funCallCallback, + Consumer onComputedCallback, + Consumer onCachedCallback, Consumer onExceptionalCallback) throws ArityException, TypeNotFoundException, MethodNotFoundException, ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException { @@ -297,10 +318,10 @@ public Object callFunctionWithInstrument( (function instanceof Function) ? ((Function) function).getCallTarget() : null; MethodCallsCache methodCallsCache = new MethodCallsCache(); UpdatesSynchronizationState syncState = new UpdatesSynchronizationState(); - Consumer funCallCallback = (value) -> {}; - Consumer onComputedCallback = + Consumer funCallCallback = (value) -> {}; + Consumer onComputedCallback = (value) -> context.getLogger().finest("_ON_COMPUTED " + value.getExpressionId()); - Consumer onCachedCallback = + Consumer onCachedCallback = (value) -> context.getLogger().finest("_ON_CACHED_VALUE " + value.getExpressionId()); Consumer onExceptionalCallback = (value) -> context.getLogger().finest("_ON_ERROR " + value); @@ -519,4 +540,257 @@ public Object execute(VirtualFrame frame) { } } } + + /** A class for notifications about functions being called in the course of execution. */ + public static final class ExpressionCall { + private final UUID expressionId; + private final FunctionCallInstrumentationNode.FunctionCall call; + + /** + * Creates an instance of this class. + * + * @param expressionId the expression id where function call was performed. + * @param call the actual function call data. + */ + public ExpressionCall(UUID expressionId, FunctionCallInstrumentationNode.FunctionCall call) { + this.expressionId = expressionId; + this.call = call; + } + + /** @return the id of the node performing the function call. */ + public UUID getExpressionId() { + return expressionId; + } + + /** @return the function call metadata. */ + public FunctionCallInstrumentationNode.FunctionCall getCall() { + return call; + } + } + + /** A class for notifications about identified expressions' values being computed. */ + public static final class ExpressionValue { + private final UUID expressionId; + private final Object value; + private final String type; + private final String cachedType; + private final FunctionCallInfo callInfo; + private final FunctionCallInfo cachedCallInfo; + private final ProfilingInfo[] profilingInfo; + private final boolean wasCached; + + /** + * Creates a new instance of this class. + * + * @param expressionId the id of the expression being computed. + * @param value the value returned by computing the expression. + * @param type the type of the returned value. + * @param cachedType the cached type of the value. + * @param callInfo the function call data. + * @param cachedCallInfo the cached call data. + * @param profilingInfo the profiling information associated with this node + * @param wasCached whether or not the value was obtained from the cache + */ + public ExpressionValue( + UUID expressionId, + Object value, + String type, + String cachedType, + FunctionCallInfo callInfo, + FunctionCallInfo cachedCallInfo, + ProfilingInfo[] profilingInfo, + boolean wasCached) { + this.expressionId = expressionId; + this.value = value; + this.type = type; + this.cachedType = cachedType; + this.callInfo = callInfo; + this.cachedCallInfo = cachedCallInfo; + this.profilingInfo = profilingInfo; + this.wasCached = wasCached; + } + + @Override + public String toString() { + String profilingInfo = Arrays.toString(this.profilingInfo); + return "ExpressionValue{" + + "expressionId=" + + expressionId + + ", value=" + + (value == null ? "null" : new MaskedString(value.toString()).applyMasking()) + + ", type='" + + type + + '\'' + + ", cachedType='" + + cachedType + + '\'' + + ", callInfo=" + + callInfo + + ", cachedCallInfo=" + + cachedCallInfo + + ", profilingInfo=" + + profilingInfo + + ", wasCached=" + + wasCached + + '}'; + } + + /** @return the id of the expression computed. */ + public UUID getExpressionId() { + return expressionId; + } + + /** @return the type of the returned value. */ + public String getType() { + return type; + } + + /** @return the cached type of the value. */ + public String getCachedType() { + return cachedType; + } + + /** @return the computed value of the expression. */ + public Object getValue() { + return value; + } + + /** @return the function call data. */ + public FunctionCallInfo getCallInfo() { + return callInfo; + } + + /** @return the function call data previously associated with the expression. */ + public FunctionCallInfo getCachedCallInfo() { + return cachedCallInfo; + } + + /** @return the profiling information associated with this expression */ + public ProfilingInfo[] getProfilingInfo() { + return profilingInfo; + } + + /** @return whether or not the expression result was obtained from the cache */ + public boolean wasCached() { + return wasCached; + } + + /** @return {@code true} when the type differs from the cached value. */ + public boolean isTypeChanged() { + return !Objects.equals(type, cachedType); + } + + /** @return {@code true} when the function call differs from the cached value. */ + public boolean isFunctionCallChanged() { + return !Objects.equals(callInfo, cachedCallInfo); + } + } + + /** Points to the definition of a runtime function. */ + public record FunctionPointer(QualifiedName moduleName, QualifiedName typeName, String functionName) { + + public static FunctionPointer fromFunction(Function function) { + RootNode rootNode = function.getCallTarget().getRootNode(); + + QualifiedName moduleName; + QualifiedName typeName; + String functionName; + + switch (rootNode) { + case MethodRootNode methodNode -> { + moduleName = methodNode.getModuleScope().getModule().getName(); + typeName = methodNode.getType().getQualifiedName(); + functionName = methodNode.getMethodName(); + } + case QualifiedAccessorNode qualifiedAccessor -> { + AtomConstructor atomConstructor = qualifiedAccessor.getAtomConstructor(); + moduleName = atomConstructor.getDefinitionScope().getModule().getName(); + typeName = atomConstructor.getType().getQualifiedName(); + functionName = atomConstructor.getName(); + } + case BuiltinRootNode builtinRootNode -> { + moduleName = builtinRootNode.getModuleName(); + typeName = builtinRootNode.getTypeName(); + functionName = QualifiedName.fromString(builtinRootNode.getName()).item(); + } + default -> { + moduleName = null; + typeName = null; + functionName = rootNode.getName(); + } + } + + return new FunctionPointer(moduleName, typeName, functionName); + } + + public static int[] collectNotAppliedArguments(Function function) { + FunctionSchema functionSchema = function.getSchema(); + Object[] preAppliedArguments = function.getPreAppliedArguments(); + boolean isStatic = preAppliedArguments[0] instanceof Type; + int selfArgumentPosition = isStatic ? -1 : 0; + int[] notAppliedArguments = new int[functionSchema.getArgumentsCount()]; + int notAppliedArgumentsLength = 0; + + for (int i = 0; i < functionSchema.getArgumentsCount(); i++) { + if (!functionSchema.hasPreAppliedAt(i)) { + notAppliedArguments[notAppliedArgumentsLength] = i + selfArgumentPosition; + notAppliedArgumentsLength += 1; + } + } + + return Arrays.copyOf(notAppliedArguments, notAppliedArgumentsLength); + } + } + + /** Information about the function call. */ + public record FunctionCallInfo(FunctionPointer functionPointer, int[] notAppliedArguments) { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FunctionCallInfo that = (FunctionCallInfo) o; + return Objects.equals(functionPointer, that.functionPointer) && Arrays.equals( + notAppliedArguments, that.notAppliedArguments); + } + + @Override + public int hashCode() { + int result = Objects.hash(functionPointer); + return 31 * result + Arrays.hashCode(notAppliedArguments); + } + + /** + * Creates a new instance of this record from a function call. + * + * @param call the function call. + */ + public static FunctionCallInfo fromFunctionCall(FunctionCallInstrumentationNode.FunctionCall call) { + FunctionPointer functionPointer = FunctionPointer.fromFunction(call.getFunction()); + int[] notAppliedArguments = collectNotAppliedArguments(call); + + return new FunctionCallInfo(functionPointer, notAppliedArguments); + } + + private static int[] collectNotAppliedArguments(FunctionCallInstrumentationNode.FunctionCall call) { + Object[] arguments = call.getArguments(); + int[] notAppliedArgs = new int[arguments.length]; + int notAppliedArgsSize = 0; + boolean isStatic = arguments[0] instanceof Type; + int selfTypePosition = isStatic ? -1 : 0; + + for (int i = 0; i < arguments.length; i++) { + if (arguments[i] == null) { + notAppliedArgs[notAppliedArgsSize] = i + selfTypePosition; + notAppliedArgsSize += 1; + } + } + + return Arrays.copyOf(notAppliedArgs, notAppliedArgsSize); + } + } } diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 3125d8a09a89..40728ea2429c 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -2,7 +2,7 @@ package org.enso.interpreter.instrument.job import cats.implicits._ import com.oracle.truffle.api.exception.AbstractTruffleException -import org.enso.interpreter.instrument.IdExecutionService.{ +import org.enso.interpreter.service.ExecutionService.{ ExpressionCall, ExpressionValue, FunctionPointer 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 534addb25274..c49b41aa08e5 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,6 +1,8 @@ package org.enso.interpreter.instrument; +import org.enso.polyglot.debugger.IdExecutionService; + import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.RootCallTarget; @@ -35,6 +37,8 @@ import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.IdentifiedTag; +import com.oracle.truffle.api.interop.TruffleObject; + /** An instrument for getting values from AST-identified expressions. */ @TruffleInstrument.Registration( id = IdExecutionService.INSTRUMENT_ID, @@ -231,7 +235,7 @@ private static UUID getNodeId(Node node) { * Attach a new event node factory to observe identified nodes within given * function. * - * @param module module that contains the code + * @param mod module that contains the code * @param entryCallTarget the call target being observed. * @param callbacks the precomputed expression values. * @param timer the execution timer. @@ -239,11 +243,12 @@ private static UUID getNodeId(Node node) { */ @Override public EventBinding bind( - Module module, + TruffleObject mod, CallTarget entryCallTarget, Callbacks callbacks, - Timer timer + Object timer ) { + var module = (Module)mod; var builder = SourceSectionFilter.newBuilder() .tagIs(StandardTags.ExpressionTag.class, StandardTags.CallTag.class) .tagIs(IdentifiedTag.class) @@ -256,7 +261,7 @@ public EventBinding bind( builder.lineIn(SourceSectionFilter.IndexRange.between(firstFunctionLine, afterFunctionLine)); } var filter = builder.build(); - var factory = new IdEventNodeFactory(entryCallTarget, callbacks, timer); + var factory = new IdEventNodeFactory(entryCallTarget, callbacks, (Timer) timer); return env.getInstrumenter().attachExecutionEventFactory(filter, factory); } } diff --git a/engine/runtime-instrument-runtime-server/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java b/engine/runtime-instrument-runtime-server/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java index 29be8945bc17..ea25adf51f4a 100644 --- a/engine/runtime-instrument-runtime-server/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java +++ b/engine/runtime-instrument-runtime-server/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java @@ -1,14 +1,18 @@ package org.enso.interpreter.instrument; +import org.enso.polyglot.debugger.IdExecutionService; + import com.oracle.truffle.api.TruffleContext; import com.oracle.truffle.api.instrumentation.ContextsListener; import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.nodes.LanguageInfo; + import java.io.IOException; import java.net.URI; import java.util.Arrays; import java.util.Optional; + import org.enso.distribution.locking.LockManager; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.service.ExecutionService; 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 new file mode 100644 index 000000000000..0f6e88dd685c --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/InstrumentorBuiltin.java @@ -0,0 +1,165 @@ +package org.enso.interpreter.node.expression.builtin.meta; + +import java.util.UUID; + +import com.oracle.truffle.api.nodes.Node; + +import org.enso.interpreter.dsl.BuiltinMethod; +import org.enso.interpreter.instrument.Timer; +import org.enso.interpreter.runtime.EnsoContext; +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.data.EnsoObject; +import org.enso.interpreter.runtime.data.text.Text; +import org.enso.interpreter.runtime.data.vector.ArrayLikeCoerceToArrayNode; +import org.enso.interpreter.runtime.error.PanicException; +import org.enso.polyglot.debugger.IdExecutionService; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; + +@BuiltinMethod( + type = "Meta", + name = "instrumentor_builtin", + description = "Handles instrumentation operations.", + autoRegister = false) +public class InstrumentorBuiltin extends Node { + private static final class Builder implements EnsoObject { + final IdExecutionService service; + final CallTarget target; + final Module module; + Object onEnter; + Object onReturn; + Object onFunction; + Object onException; + + Builder(Module module, IdExecutionService service, CallTarget target) { + this.module = module; + this.service = service; + this.target = target; + } + } + + @Child + private ArrayLikeCoerceToArrayNode coerceToArrayNode = ArrayLikeCoerceToArrayNode.build(); + + Object execute(Text operation, Object args) { + var ctx = EnsoContext.get(this); + var arr = coerceToArrayNode.execute(args); + var ret = switch (operation.toString()) { + case "newBuilder" -> newBuilder(ctx, arr); + case "onEnter" -> onEnter((Builder) arr[0], (Function) arr[1]); + case "onReturn" -> onReturn((Builder) arr[0], (Function) arr[1]); + case "onFunction" -> onFunction((Builder) arr[0], (Function) arr[1]); + case "onException" -> onException((Builder) arr[0], (Function) arr[1]); + case "activate" -> activate((Builder) arr[0]); + default -> null; + }; + if (ret == null) { + var err = ctx.getBuiltins().error().makeUnimplemented(operation.toString()); + throw new PanicException(err, this); + } + return ret; + } + + @CompilerDirectives.TruffleBoundary + private Builder newBuilder(EnsoContext ctx, Object[] args) { + if (args.length > 0 && args[0] instanceof UnresolvedSymbol symbol) { + var fnAndType = symbol.resolveFor(this, symbol.getScope().getAssociatedType()); + if (fnAndType != null) { + var env = ctx.getEnvironment(); + var instrument = env.getInstruments().get("id-value-extractor"); + if (instrument != null) { + var service = env.lookup(instrument, IdExecutionService.class); + if (service != null) { + return new Builder(symbol.getScope().getModule(), service, fnAndType.getLeft().getCallTarget()); + } + } + } + } + return null; + } + + @CompilerDirectives.TruffleBoundary + private Object onEnter(Builder b, Function fn) { + b.onEnter = fn; + return b; + } + + @CompilerDirectives.TruffleBoundary + private Object onReturn(Builder b, Function fn) { + b.onReturn = fn; + return b; + } + + @CompilerDirectives.TruffleBoundary + private Object onFunction(Builder b, Function fn) { + b.onFunction = fn; + return b; + } + + @CompilerDirectives.TruffleBoundary + private Object onException(Builder b, Function fn) { + b.onException = fn; + return b; + } + + @CompilerDirectives.TruffleBoundary + private Object activate(Builder builder) { + class Observe implements IdExecutionService.Callbacks { + @Override + public Object findCachedResult(UUID nodeId) { + try { + if (builder.onEnter != null) { + var ret = InteropLibrary.getUncached().execute(builder.onEnter, nodeId.toString()); + return InteropLibrary.getUncached().isNull(ret) ? null : ret; + } + } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException ex) { + } + return null; + } + + @Override + public void updateCachedResult(UUID nodeId, Object result, boolean isPanic, long nanoElapsedTime) { + try { + if (builder.onReturn != null) { + InteropLibrary.getUncached().execute(builder.onReturn, nodeId.toString(), result); + } + } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException ex) { + } + } + + @Override + public Object onFunctionReturn(UUID nodeId, TruffleObject result) { + try { + if (builder.onFunction != null) { + var ret = InteropLibrary.getUncached().execute(builder.onFunction, nodeId.toString(), result); + return InteropLibrary.getUncached().isNull(ret) ? null : ret; + } + } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException ex) { + } + return null; + } + + @Override + public void onExceptionalCallback(Exception e) { + try { + if (builder.onException != null) { + InteropLibrary.getUncached().execute(builder.onReturn, (TruffleObject) e); + } + } catch (ClassCastException | UnsupportedTypeException | ArityException | UnsupportedMessageException ex) { + } + } + } + builder.service.bind( + builder.module, builder.target, new Observe(), new Timer.Disabled() + ); + return builder; + } +}