diff --git a/docs/language-server/protocol-language-server.md b/docs/language-server/protocol-language-server.md index 49cb4bb12416..2c35ce831d9b 100644 --- a/docs/language-server/protocol-language-server.md +++ b/docs/language-server/protocol-language-server.md @@ -3788,11 +3788,33 @@ interface ExecutionContextExecutionStatusNotification { ### `executionContext/executeExpression` -This message allows the client to execute an arbitrary expression on a given -node. It behaves like oneshot +This message allows the client to execute an arbitrary expression in a context +of a given node. It behaves like putting a breakpoint after the expression with +`expressionId` and executing the provided `expression`. All the local and global +symbols that are available for the `expressionId` will be available when +executing the `expression`. The result of the evaluation will be delivered as a +visualization result on a binary connection. You can think of it as a oneshot [`executionContext/attachVisualization`](#executioncontextattachvisualization) -visualization request, meaning that the visualization expression will be -executed only once. +visualization request, meaning that the expression will be executed once. + +For example, given the current code: + +```python +main = + operator1 = 42 + operator2 = operator1 + 1 + +fun1 x = x.to_text +``` + +- You can execute an expression in the context of a function body. In this case, + the `expressionId` should point to the body of a function. E.g. in the context + of `main` available symbols are `operator1`, `operator2` and `fun1`. +- Execute expression in the context of a local binding. E.g. in the context of + `operator2 = operator1 + 1` available symbols are `operator1`, `operator2` and + `fun1`. +- Execute expression in the context of arbitrary expression. E.g. in the context + of `operator1 + 1` available symbols are `operator1` and `fun1`. - **Type:** Request - **Direction:** Client -> Server @@ -3803,9 +3825,10 @@ executed only once. ```typescript interface ExecutionContextExecuteExpressionParameters { + executionContextId: UUID; visualizationId: UUID; expressionId: UUID; - visualizationConfig: VisualizationConfiguration; + expression: string; } ``` @@ -3821,11 +3844,8 @@ type ExecutionContextExecuteExpressionResult = null; `executionContext/canModify` capability for this context. - [`ContextNotFoundError`](#contextnotfounderror) when context can not be found by provided id. -- [`ModuleNotFoundError`](#modulenotfounderror) to signal that the module with - the visualization cannot be found. - [`VisualizationExpressionError`](#visualizationexpressionerror) to signal that - the expression specified in the `VisualizationConfiguration` cannot be - evaluated. + the provided expression cannot be evaluated. ### `executionContext/attachVisualization` diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/visualization/ExecuteExpressionHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/visualization/ExecuteExpressionHandler.scala index 83f1d1afb390..ad2f3a038969 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/visualization/ExecuteExpressionHandler.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/requesthandler/visualization/ExecuteExpressionHandler.scala @@ -40,9 +40,10 @@ class ExecuteExpressionHandler( ) => contextRegistry ! ContextRegistryProtocol.ExecuteExpression( clientId, + params.executionContextId, params.visualizationId, params.expressionId, - params.visualizationConfig + params.expression ) val cancellable = context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout) diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala index 682f6bb69e08..7d31dca98b49 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistry.scala @@ -261,8 +261,13 @@ final class ContextRegistry( sender() ! AccessDenied } - case ExecuteExpression(clientId, visualizationId, expressionId, cfg) => - val contextId = cfg.executionContextId + case ExecuteExpression( + clientId, + contextId, + visualizationId, + expressionId, + expression + ) => if (store.hasContext(clientId, contextId)) { store.getListener(contextId).foreach { listener => listener ! RegisterOneshotVisualization( @@ -272,17 +277,18 @@ final class ContextRegistry( ) } val handler = context.actorOf( - AttachVisualizationHandler.props( + ExecuteExpressionHandler.props( runtimeFailureMapper, timeout, runtime ) ) handler.forward( - Api.AttachVisualization( + Api.ExecuteExpression( + contextId, visualizationId, expressionId, - cfg.toApi + expression ) ) } else { diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala index 9e7b3f3a8938..417d79433945 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/ContextRegistryProtocol.scala @@ -9,7 +9,7 @@ import org.enso.languageserver.filemanager.{FileSystemFailure, Path} import org.enso.languageserver.libraries.LibraryComponentGroup import org.enso.languageserver.runtime.ExecutionApi.ContextId import org.enso.languageserver.session.JsonSession -import org.enso.logger.masking.ToLogString +import org.enso.logger.masking.{MaskedString, ToLogString} import org.enso.text.editing.model import java.util.UUID @@ -422,14 +422,14 @@ object ContextRegistryProtocol { * @param clientId the requester id * @param visualizationId an identifier of a visualization * @param expressionId an identifier of an expression which is visualised - * @param visualizationConfig a configuration object for properties of the - * visualization + * @param expression the expression to execute */ case class ExecuteExpression( clientId: ClientId, + executionContextId: UUID, visualizationId: UUID, expressionId: UUID, - visualizationConfig: VisualizationConfiguration + expression: String ) extends ToLogString { /** @inheritdoc */ @@ -437,8 +437,8 @@ object ContextRegistryProtocol { "ExecuteExpression(" + s"clientId=$clientId," + s"visualizationId=$visualizationId," + - s"expressionId=$expressionId,visualizationConfig=" + - visualizationConfig.toLogString(shouldMask) + + s"expressionId=$expressionId,expression=" + + MaskedString(expression).toLogString(shouldMask) + ")" } diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationApi.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationApi.scala index b4adc18e9e94..2428029a3a99 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationApi.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/VisualizationApi.scala @@ -14,9 +14,10 @@ object VisualizationApi { extends Method("executionContext/executeExpression") { case class Params( + executionContextId: UUID, visualizationId: UUID, expressionId: UUID, - visualizationConfig: VisualizationConfiguration + expression: String ) implicit val hasParams: HasParams.Aux[this.type, ExecuteExpression.Params] = diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ExecuteExpressionHandler.scala b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ExecuteExpressionHandler.scala new file mode 100644 index 000000000000..0e740d691cd1 --- /dev/null +++ b/engine/language-server/src/main/scala/org/enso/languageserver/runtime/handler/ExecuteExpressionHandler.scala @@ -0,0 +1,81 @@ +package org.enso.languageserver.runtime.handler + +import akka.actor.{Actor, ActorRef, Cancellable, Props} +import akka.pattern.pipe +import com.typesafe.scalalogging.LazyLogging +import org.enso.languageserver.requesthandler.RequestTimeout +import org.enso.languageserver.runtime.{ + ContextRegistryProtocol, + RuntimeFailureMapper +} +import org.enso.languageserver.util.UnhandledLogging +import org.enso.polyglot.runtime.Runtime.Api + +import java.util.UUID + +import scala.concurrent.duration.FiniteDuration + +/** A request handler for execute expression commands. + * + * @param runtimeFailureMapper mapper for runtime failures + * @param timeout request timeout + * @param runtime reference to the runtime connector + */ +class ExecuteExpressionHandler( + runtimeFailureMapper: RuntimeFailureMapper, + timeout: FiniteDuration, + runtime: ActorRef +) extends Actor + with LazyLogging + with UnhandledLogging { + + import context.dispatcher + + override def receive: Receive = requestStage + + private def requestStage: Receive = { case msg: Api.ExecuteExpression => + runtime ! Api.Request(UUID.randomUUID(), msg) + val cancellable = + context.system.scheduler.scheduleOnce(timeout, self, RequestTimeout) + context.become(responseStage(sender(), cancellable)) + } + + private def responseStage( + replyTo: ActorRef, + cancellable: Cancellable + ): Receive = { + case RequestTimeout => + replyTo ! RequestTimeout + context.stop(self) + + case Api.Response(_, Api.VisualizationAttached()) => + replyTo ! ContextRegistryProtocol.VisualizationAttached + cancellable.cancel() + context.stop(self) + + case Api.Response(_, error: Api.Error) => + runtimeFailureMapper.mapApiError(error).pipeTo(replyTo) + cancellable.cancel() + context.stop(self) + } + +} + +object ExecuteExpressionHandler { + + /** Creates configuration object used to create a [[ExecuteExpressionHandler]]. + * + * @param runtimeFailureMapper mapper for runtime failures + * @param timeout request timeout + * @param runtime reference to the runtime connector + */ + def props( + runtimeFailureMapper: RuntimeFailureMapper, + timeout: FiniteDuration, + runtime: ActorRef + ): Props = + Props( + new ExecuteExpressionHandler(runtimeFailureMapper, timeout, runtime) + ) + +} diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala index f18129954596..01568c26c142 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ContextRegistryTest.scala @@ -631,21 +631,21 @@ class ContextRegistryTest extends BaseServerTest { // attach visualization val visualizationId = UUID.randomUUID() val expressionId = UUID.randomUUID() - val config = - VisualizationConfiguration(contextId, "Test.Main", ".to_json.to_text") client.send( json.executionContextExecuteExpressionRequest( 2, + contextId, visualizationId, expressionId, - config + "expression" ) ) val requestId2 = runtimeConnectorProbe.receiveN(1).head match { case Api.Request( requestId, - Api.AttachVisualization( + Api.ExecuteExpression( + `contextId`, `visualizationId`, `expressionId`, _ @@ -662,63 +662,6 @@ class ContextRegistryTest extends BaseServerTest { client.expectJson(json.ok(2)) } - "return ModuleNotFound error when executing expression" in { - val client = getInitialisedWsClient() - - // create context - client.send(json.executionContextCreateRequest(1)) - val (requestId, contextId) = - runtimeConnectorProbe.receiveN(1).head match { - case Api.Request(requestId, Api.CreateContextRequest(contextId)) => - (requestId, contextId) - case msg => - fail(s"Unexpected message: $msg") - } - runtimeConnectorProbe.lastSender ! Api.Response( - requestId, - Api.CreateContextResponse(contextId) - ) - client.expectJson(json.executionContextCreateResponse(1, contextId)) - - // attach visualization - val visualizationId = UUID.randomUUID() - val expressionId = UUID.randomUUID() - val config = - VisualizationConfiguration(contextId, "Test.Main", ".to_json.to_text") - client.send( - json.executionContextExecuteExpressionRequest( - 2, - visualizationId, - expressionId, - config - ) - ) - val requestId2 = - runtimeConnectorProbe.receiveN(1).head match { - case Api.Request( - requestId, - Api.AttachVisualization( - `visualizationId`, - `expressionId`, - _ - ) - ) => - requestId - case msg => - fail(s"Unexpected message: $msg") - } - runtimeConnectorProbe.lastSender ! Api.Response( - requestId2, - Api.ModuleNotFound(config.visualizationModule) - ) - client.expectJson( - json.executionContextModuleNotFound( - 2, - config.visualizationModule - ) - ) - } - "successfully attach visualization" in { val client = getInitialisedWsClient() diff --git a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala index b9197a5be2ff..28949af809ee 100644 --- a/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala +++ b/engine/language-server/src/test/scala/org/enso/languageserver/websocket/json/ExecutionContextJsonMessages.scala @@ -7,6 +7,8 @@ import org.enso.languageserver.runtime.{ VisualizationExpression } +import java.util.UUID + object ExecutionContextJsonMessages { def localCall(expressionId: Api.ExpressionId) = @@ -109,67 +111,23 @@ object ExecutionContextJsonMessages { def executionContextExecuteExpressionRequest( reqId: Int, + executionContextId: UUID, visualizationId: Api.VisualizationId, expressionId: Api.ExpressionId, - configuration: VisualizationConfiguration + expression: String ) = - configuration.expression match { - case VisualizationExpression.Text(module, expression) => - json""" - { "jsonrpc": "2.0", - "method": "executionContext/executeExpression", - "id": $reqId, - "params": { - "visualizationId": $visualizationId, - "expressionId": $expressionId, - "visualizationConfig": { - "executionContextId": ${configuration.executionContextId}, - "visualizationModule": $module, - "expression": $expression - } - } - } - """ - case VisualizationExpression.ModuleMethod(methodPointer, Vector()) => - json""" - { "jsonrpc": "2.0", - "method": "executionContext/executeExpression", - "id": $reqId, - "params": { - "visualizationId": $visualizationId, - "expressionId": $expressionId, - "visualizationConfig": { - "executionContextId": ${configuration.executionContextId}, - "expression": { - "module": ${methodPointer.module}, - "definedOnType": ${methodPointer.definedOnType}, - "name": ${methodPointer.name} - } - } - } - } - """ - case VisualizationExpression.ModuleMethod(methodPointer, arguments) => - json""" - { "jsonrpc": "2.0", - "method": "executionContext/executeExpression", - "id": $reqId, - "params": { - "visualizationId": $visualizationId, - "expressionId": $expressionId, - "visualizationConfig": { - "executionContextId": ${configuration.executionContextId}, - "expression": { - "module": ${methodPointer.module}, - "definedOnType": ${methodPointer.definedOnType}, - "name": ${methodPointer.name} - }, - "positionalArgumentsExpressions": $arguments - } - } - } - """ - } + json""" + { "jsonrpc": "2.0", + "method": "executionContext/executeExpression", + "id": $reqId, + "params": { + "executionContextId": $executionContextId, + "visualizationId": $visualizationId, + "expressionId": $expressionId, + "expression": $expression + } + } + """ def executionContextAttachVisualizationRequest( reqId: Int, diff --git a/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/ExecutedVisualization.java b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/ExecutedVisualization.java new file mode 100644 index 000000000000..1ee8449bb9ef --- /dev/null +++ b/engine/polyglot-api/src/main/java/org/enso/polyglot/debugger/ExecutedVisualization.java @@ -0,0 +1,19 @@ +package org.enso.polyglot.debugger; + +import java.util.UUID; + +/** + * The result of executed oneshot visualization expression. + * + * @param result the execution result. {@code null} if the execution resulted in exception. + * @param error the execution error. {@code null} if the execution was successful. + * @param visualizationId the visualization id. + * @param expressionId the id of expression that provides the execution scope. + * @param expressionValue the value of the expression that provides the execution scope. + */ +public record ExecutedVisualization( + Object result, + Throwable error, + UUID visualizationId, + UUID expressionId, + Object expressionValue) {} 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 index 0ff5f28732bd..868650ea1963 100644 --- 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 @@ -9,36 +9,60 @@ public interface IdExecutionService { String INSTRUMENT_ID = "id-value-extractor"; + public abstract class Info { + + /** @return UUID of the node, never {@code null}. */ + public abstract UUID getId(); + + /** @return associated result or {@code null} if there is no associated result. */ + public abstract Object getResult(); + + /** @return {@code true} when the result is panic, {@code false} otherwise. */ + public abstract boolean isPanic(); + + /** + * @return time (in nanoseconds) needed to compute the result or {@code -1} when not available. + */ + public abstract long getElapsedTime(); + + /** + * Evaluates given code in the context of current UUID location. + * + * @param code the Enso code to evaluate. + * @return result of the evaluation. + */ + public abstract Object eval(String code); + } + 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 + * @param info info with UUID 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); + Object findCachedResult(Info info); /** * 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? + * @param info info with node id, {@link Info#getResult()}, {@link Info#isPanic()} and {@link + * Info#getElapsedTime()} */ - void updateCachedResult(UUID nodeId, Object result, boolean isPanic, long nanoElapsedTime); + void updateCachedResult(Info info); /** * Notification when a returned value is a function. * - * @param nodeId identification of the node to be computed - * @param result info about function call + * @param info with identification of the node and {@link Info#getResult()} 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); + Object onFunctionReturn(Info info); } /** diff --git a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala index 84cda98ba9a0..815cb079236d 100644 --- a/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala +++ b/engine/polyglot-api/src/main/scala/org/enso/polyglot/runtime/Runtime.scala @@ -128,6 +128,10 @@ object Runtime { value = classOf[Api.AttachVisualization], name = "attachVisualization" ), + new JsonSubTypes.Type( + value = classOf[Api.ExecuteExpression], + name = "executeExpression" + ), new JsonSubTypes.Type( value = classOf[Api.VisualizationAttached], name = "visualizationAttached" @@ -1561,6 +1565,13 @@ object Runtime { */ final case class InitializedNotification() extends ApiResponse + final case class ExecuteExpression( + contextId: ContextId, + visualizationId: VisualizationId, + expressionId: ExpressionId, + expression: String + ) extends ApiRequest + /** A request sent from the client to the runtime server, to create a new * visualization for an expression identified by `expressionId`. * diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala index bae4dd0c4c8b..016d812de2ef 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Compiler.scala @@ -68,7 +68,7 @@ class Compiler( private val importResolver: ImportResolver = new ImportResolver(this) private val irCachingEnabled = !context.isIrCachingDisabled private val useGlobalCacheLocations = context.isUseGlobalCacheLocations - private val isInteractiveMode = context.isInteractiveMode() + private val isInteractiveMode = context.isInteractiveMode private val output: PrintStream = if (config.outputRedirect.isDefined) new PrintStream(config.outputRedirect.get) @@ -112,7 +112,7 @@ class Compiler( } /** @return the package repository instance. */ - def getPackageRepository(): PackageRepository = + def getPackageRepository: PackageRepository = context.getPackageRepository /** Processes the provided language sources, registering any bindings in the @@ -142,7 +142,7 @@ class Compiler( shouldCompileDependencies: Boolean, useGlobalCacheLocations: Boolean ): Future[java.lang.Boolean] = { - getPackageRepository().getMainProjectPackage match { + getPackageRepository.getMainProjectPackage match { case None => context.log( Level.SEVERE, @@ -269,7 +269,7 @@ class Compiler( ) { val importedModulesLoadedFromSource = importedModules .filter(isLoadedFromSource) - .map(context.getModuleName(_)) + .map(context.getModuleName) context.log( Compiler.defaultLogLevel, "{0} imported module caches were invalided, forcing invalidation of {1}. [{2}]", @@ -279,7 +279,7 @@ class Compiler( importedModulesLoadedFromSource.take(10).mkString("", ",", "...") ) ) - context.updateModule(module, _.invalidateCache) + context.updateModule(module, _.invalidateCache()) parseModule(module) runImportsAndExportsResolution(module, generateCode) } else { @@ -458,9 +458,9 @@ class Compiler( private def isModuleInRootPackage(module: Module): Boolean = { if (!context.isInteractive(module)) { val pkg = PackageRepositoryUtils - .getPackageOf(getPackageRepository(), module.getSourceFile) + .getPackageOf(getPackageRepository, module.getSourceFile) .toScala - pkg.contains(getPackageRepository().getMainProjectPackage.get) + pkg.contains(getPackageRepository.getMainProjectPackage.get) } else false } @@ -573,7 +573,7 @@ class Compiler( "Parsing module [{0}].", context.getModuleName(module) ) - context.updateModule(module, _.resetScope) + context.updateModule(module, _.resetScope()) if (irCachingEnabled && !context.isInteractive(module)) { if (context.deserializeModule(this, module)) { @@ -604,7 +604,7 @@ class Compiler( "Loading module [{0}] from source.", context.getModuleName(module) ) - context.updateModule(module, _.resetScope) + context.updateModule(module, _.resetScope()) val moduleContext = ModuleContext( module = module, @@ -695,10 +695,10 @@ class Compiler( .build() val tree = ensoCompiler.parse(source.getCharacters) - ensoCompiler.generateIRInline(tree).flatMap { ir => + ensoCompiler.generateIRInline(tree).map { ir => val compilerOutput = runCompilerPhasesInline(ir, newContext) runErrorHandlingInline(compilerOutput, source, newContext) - Some((newContext, compilerOutput, source)) + (newContext, compilerOutput, source) } } @@ -743,14 +743,6 @@ class Compiler( def parseInline(source: Source): Tree = ensoCompiler.parse(source.getCharacters()) - /** Parses the metadata of the provided language sources. - * - * @param source the code to parse - * @return the source metadata - */ -// def parseMeta(source: CharSequence): IDMap = -// Parser().splitMeta(source.toString)._2 - /** Enhances the provided IR with import/export statements for the provided list * of fully qualified names of modules. The statements are considered to be "synthetic" i.e. compiler-generated. * That way one can access modules using fully qualified names. @@ -859,7 +851,7 @@ class Compiler( * for inline evaluation * @return the output result of the */ - def runCompilerPhasesInline( + private def runCompilerPhasesInline( ir: Expression, inlineContext: InlineContext ): Expression = { @@ -873,12 +865,12 @@ class Compiler( * @param source the original source code. * @param inlineContext the inline compilation context. */ - def runErrorHandlingInline( + private def runErrorHandlingInline( ir: Expression, source: Source, inlineContext: InlineContext ): Unit = - if (config.isStrictErrors) { + if (inlineContext.compilerConfig.isStrictErrors) { val errors = GatherDiagnostics .runExpression(ir, inlineContext) .unsafeGetMetadata( @@ -896,7 +888,7 @@ class Compiler( * * @param modules the modules to check against errors */ - def runErrorHandling( + private def runErrorHandling( modules: List[Module] ): Unit = { if (config.isStrictErrors) { @@ -922,7 +914,7 @@ class Compiler( * @param module the module for which to gather diagnostics * @return the diagnostics from the module */ - def gatherDiagnostics(module: Module): List[Diagnostic] = { + private def gatherDiagnostics(module: Module): List[Diagnostic] = { GatherDiagnostics .runModule( context.getIr(module), diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala index 7399de190469..6035ccab60a6 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -1,6 +1,6 @@ package org.enso.compiler.data -import org.enso.compiler.{PackageRepository} +import org.enso.compiler.PackageRepository import org.enso.compiler.PackageRepository.ModuleMap import org.enso.compiler.context.CompilerContext.Module import org.enso.compiler.core.Implicits.AsMetadata @@ -58,7 +58,7 @@ case class BindingsMap( override def restoreFromSerialization( compiler: Compiler ): Option[BindingsMap] = { - val packageRepository = compiler.getPackageRepository() + val packageRepository = compiler.getPackageRepository this.toConcrete(packageRepository.getModuleMap) } @@ -1012,7 +1012,7 @@ object BindingsMap { override def restoreFromSerialization( compiler: Compiler ): Option[Resolution] = { - val moduleMap = compiler.getPackageRepository().getModuleMap + val moduleMap = compiler.getPackageRepository.getModuleMap this.target.toConcrete(moduleMap).map(t => this.copy(target = t)) } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala index 38c4665753b3..5164febb00be 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala @@ -454,7 +454,7 @@ case object FullyQualifiedNames extends IRPass { override def restoreFromSerialization( compiler: Compiler ): Option[PartiallyResolvedFQN] = { - val packageRepository = compiler.getPackageRepository() + val packageRepository = compiler.getPackageRepository moduleRef .toConcrete(packageRepository.getModuleMap) .map(ResolvedModule(_)) diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/ExecuteExpressionCommand.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/ExecuteExpressionCommand.java new file mode 100644 index 000000000000..cc0996b01b71 --- /dev/null +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/command/ExecuteExpressionCommand.java @@ -0,0 +1,50 @@ +package org.enso.interpreter.instrument.command; + +import java.util.UUID; +import org.enso.interpreter.instrument.execution.RuntimeContext; +import org.enso.interpreter.instrument.job.ExecuteExpressionJob; +import org.enso.interpreter.instrument.job.ExecuteJob; +import org.enso.polyglot.runtime.Runtime$Api$VisualizationAttached; +import scala.Option; +import scala.concurrent.ExecutionContext; +import scala.concurrent.Future; +import scala.runtime.BoxedUnit; + +/** The command that handles the execute expression request. */ +public final class ExecuteExpressionCommand extends ContextCmd { + + private final UUID contextId; + private final UUID visualizationId; + private final UUID expressionId; + private final String expression; + + /** + * Create the {@link ExecuteExpressionCommand}. + * + * @param maybeRequestId the request id. + * @param contextId the execution context id. + * @param visualizationId the visualization id. + * @param expressionId the expression providing the execution scope. + * @param expression the expression to execute. + */ + public ExecuteExpressionCommand( + Option maybeRequestId, + UUID contextId, + UUID visualizationId, + UUID expressionId, + String expression) { + super(contextId, maybeRequestId); + this.contextId = contextId; + this.visualizationId = visualizationId; + this.expressionId = expressionId; + this.expression = expression; + } + + @Override + public Future executeCmd(RuntimeContext ctx, ExecutionContext ec) { + reply(new Runtime$Api$VisualizationAttached(), ctx); + return ctx.jobProcessor() + .run(new ExecuteExpressionJob(contextId, visualizationId, expressionId, expression)) + .flatMap(executable -> ctx.jobProcessor().run(ExecuteJob.apply(executable)), ec); + } +} diff --git a/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java new file mode 100644 index 000000000000..2ef4f30f3582 --- /dev/null +++ b/engine/runtime-instrument-common/src/main/java/org/enso/interpreter/instrument/job/ExecuteExpressionJob.java @@ -0,0 +1,58 @@ +package org.enso.interpreter.instrument.job; + +import com.oracle.truffle.api.TruffleLogger; +import java.util.UUID; +import java.util.logging.Level; +import org.enso.interpreter.instrument.Visualization; +import org.enso.interpreter.instrument.execution.Executable; +import org.enso.interpreter.instrument.execution.RuntimeContext; +import org.enso.interpreter.util.ScalaConversions; + +/** The job that schedules the execution of the expression. */ +public class ExecuteExpressionJob extends Job { + + private final UUID contextId; + private final UUID visualizationId; + private final UUID expressionId; + private final String expression; + + /** + * Create the {@link ExecuteExpressionJob}. + * + * @param contextId the execution context id. + * @param visualizationId the visualization id. + * @param expressionId the expression providing the execution scope. + * @param expression the expression to execute. + */ + public ExecuteExpressionJob( + UUID contextId, UUID visualizationId, UUID expressionId, String expression) { + super(ScalaConversions.cons(contextId, ScalaConversions.nil()), false, false); + this.contextId = contextId; + this.visualizationId = visualizationId; + this.expressionId = expressionId; + this.expression = expression; + } + + @Override + public Executable run(RuntimeContext ctx) { + TruffleLogger logger = ctx.executionService().getLogger(); + long lockTimestamp = ctx.locking().acquireContextLock(contextId); + + try { + Visualization visualization = + new Visualization.OneshotExpression(visualizationId, expressionId, contextId, expression); + ctx.contextManager().upsertVisualization(contextId, visualization); + + var stack = ctx.contextManager().getStack(contextId); + return new Executable(contextId, stack); + } finally { + ctx.locking().releaseContextLock(contextId); + logger.log( + Level.FINEST, + "Kept context lock [{0}] for {1} milliseconds.", + new Object[] { + this.getClass().getSimpleName(), System.currentTimeMillis() - lockTimestamp + }); + } + } +} 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 2365f9096793..ccec934ea0bc 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 @@ -1,14 +1,15 @@ package org.enso.interpreter.service; +import com.oracle.truffle.api.CompilerDirectives; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.function.Consumer; - -import org.enso.polyglot.debugger.IdExecutionService; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.RuntimeCache; import org.enso.interpreter.instrument.UpdatesSynchronizationState; +import org.enso.interpreter.instrument.Visualization; +import org.enso.interpreter.instrument.VisualizationHolder; import org.enso.interpreter.instrument.profiling.ExecutionTime; import org.enso.interpreter.instrument.profiling.ProfilingInfo; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; @@ -19,12 +20,13 @@ 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; +import org.enso.polyglot.debugger.ExecutedVisualization; +import org.enso.polyglot.debugger.IdExecutionService; +import scala.collection.Iterator; final class ExecutionCallbacks implements IdExecutionService.Callbacks { + private final VisualizationHolder visualizationHolder; private final UUID nextExecutionItem; private final RuntimeCache cache; private final MethodCallsCache methodCallsCache; @@ -33,8 +35,10 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { private final Consumer onCachedCallback; private final Consumer onComputedCallback; private final Consumer functionCallCallback; + private final Consumer onExecutedVisualizationCallback; - /** Creates callbacks instance. + /** + * Creates callbacks instance. * * @param cache the precomputed expression values. * @param methodCallsCache the storage tracking the executed updateCachedResult calls. @@ -45,11 +49,16 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { * @param onCachedCallback the consumer of the cached value events. */ ExecutionCallbacks( - UUID nextExecutionItem, - RuntimeCache cache, MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, - Consumer onCachedCallback, Consumer onComputedCallback, - Consumer functionCallCallback - ) { + VisualizationHolder visualizationHolder, + UUID nextExecutionItem, + RuntimeCache cache, + MethodCallsCache methodCallsCache, + UpdatesSynchronizationState syncState, + Consumer onCachedCallback, + Consumer onComputedCallback, + Consumer functionCallCallback, + Consumer onExecutedVisualizationCallback) { + this.visualizationHolder = visualizationHolder; this.nextExecutionItem = nextExecutionItem; this.cache = cache; this.methodCallsCache = methodCallsCache; @@ -57,46 +66,46 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks { this.onCachedCallback = onCachedCallback; this.onComputedCallback = onComputedCallback; this.functionCallCallback = functionCallCallback; + this.onExecutedVisualizationCallback = onExecutedVisualizationCallback; } - @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); + @Override + public Object findCachedResult(IdExecutionService.Info info) { + UUID nodeId = info.getId(); + Object result = getCachedResult(nodeId); + + if (result != null) { + executeOneshotExpressions(nodeId, result, info); + } + // 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); + callOnCachedCallback(nodeId, result); return result; } + return null; } - @CompilerDirectives.TruffleBoundary - public final void updateCachedResult(UUID nodeId, Object result, boolean isPanic, long nanoTimeElapsed) { + @Override + public void updateCachedResult(IdExecutionService.Info info) { + Object result = info.getResult(); String resultType = typeOf(result); + UUID nodeId = info.getId(); String cachedType = cache.getType(nodeId); FunctionCallInfo call = functionCallInfoById(nodeId); FunctionCallInfo cachedCall = cache.getCall(nodeId); - ProfilingInfo[] profilingInfo = new ProfilingInfo[]{new ExecutionTime(nanoTimeElapsed)}; + ProfilingInfo[] profilingInfo = new ProfilingInfo[] {new ExecutionTime(info.getElapsedTime())}; - ExpressionValue expressionValue - = new ExpressionValue(nodeId, result, resultType, cachedType, call, cachedCall, profilingInfo, false); + ExpressionValue expressionValue = + new ExpressionValue( + nodeId, result, resultType, cachedType, call, cachedCall, profilingInfo, false); syncState.setExpressionUnsync(nodeId); syncState.setVisualizationUnsync(nodeId); + boolean isPanic = info.isPanic(); // 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. @@ -106,7 +115,8 @@ public final void updateCachedResult(UUID nodeId, Object result, boolean isPanic } cache.putType(nodeId, resultType); - passExpressionValueToCallback(expressionValue); + callOnComputedCallback(expressionValue); + executeOneshotExpressions(nodeId, result, info); 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 @@ -116,8 +126,11 @@ public final void updateCachedResult(UUID nodeId, Object result, boolean isPanic } @CompilerDirectives.TruffleBoundary - public final Object onFunctionReturn(UUID nodeId, TruffleObject result) { - var fnCall = (FunctionCallInstrumentationNode.FunctionCall) result; + @Override + public Object onFunctionReturn(IdExecutionService.Info info) { + FunctionCallInstrumentationNode.FunctionCall fnCall = + (FunctionCallInstrumentationNode.FunctionCall) info.getResult(); + UUID nodeId = info.getId(); calls.put(nodeId, FunctionCallInfo.fromFunctionCall(fnCall)); functionCallCallback.accept(new ExpressionCall(nodeId, fnCall)); // Return cached value after capturing the enterable function call in `functionCallCallback` @@ -130,10 +143,63 @@ public final Object onFunctionReturn(UUID nodeId, TruffleObject result) { } @CompilerDirectives.TruffleBoundary - private void passExpressionValueToCallback(ExpressionValue expressionValue) { + private void callOnComputedCallback(ExpressionValue expressionValue) { onComputedCallback.accept(expressionValue); } + @CompilerDirectives.TruffleBoundary + private void callOnCachedCallback(UUID nodeId, Object result) { + ExpressionValue expressionValue = + new ExpressionValue( + nodeId, + result, + cache.getType(nodeId), + typeOf(result), + calls.get(nodeId), + cache.getCall(nodeId), + new ProfilingInfo[] {ExecutionTime.empty()}, + true); + + onCachedCallback.accept(expressionValue); + } + + private void executeOneshotExpressions(UUID nodeId, Object result, IdExecutionService.Info info) { + Iterator visualizations = findVisualizations(nodeId); + while (visualizations.hasNext()) { + Visualization visualization = visualizations.next(); + + if (visualization instanceof Visualization.OneshotExpression oneshotExpression) { + Object visualizationResult = null; + Throwable visualizationError = null; + try { + visualizationResult = info.eval(oneshotExpression.expression()); + } catch (Exception exception) { + visualizationError = exception; + } + + ExecutedVisualization executedVisualization = + new ExecutedVisualization( + visualizationResult, visualizationError, visualization.id(), nodeId, result); + callOnExecutedVisualizationCallback(executedVisualization); + } + } + } + + @CompilerDirectives.TruffleBoundary + private void callOnExecutedVisualizationCallback(ExecutedVisualization executedVisualization) { + onExecutedVisualizationCallback.accept(executedVisualization); + } + + @CompilerDirectives.TruffleBoundary + private Object getCachedResult(UUID nodeId) { + return cache.get(nodeId); + } + + @CompilerDirectives.TruffleBoundary + private Iterator findVisualizations(UUID nodeId) { + return visualizationHolder.find(nodeId).iterator(); + } + @CompilerDirectives.TruffleBoundary private FunctionCallInfo functionCallInfoById(UUID nodeId) { return calls.get(nodeId); 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 2803400045da..962062b00c6b 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,47 +1,48 @@ package org.enso.interpreter.service; -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; + +import java.io.File; +import java.io.IOException; import java.util.Arrays; import java.util.Objects; +import java.util.Optional; import java.util.UUID; import java.util.function.Consumer; import java.util.logging.Level; -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.polyglot.debugger.IdExecutionService; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.NotificationHandler; import org.enso.interpreter.instrument.RuntimeCache; import org.enso.interpreter.instrument.Timer; import org.enso.interpreter.instrument.UpdatesSynchronizationState; +import org.enso.interpreter.instrument.VisualizationHolder; +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.node.expression.builtin.text.util.TypeToDisplayTextNodeGen; import org.enso.interpreter.runtime.EnsoContext; 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.interpreter.runtime.error.PanicException; import org.enso.interpreter.runtime.scope.ModuleScope; @@ -52,26 +53,15 @@ import org.enso.interpreter.service.error.SourceNotFoundException; import org.enso.interpreter.service.error.TypeNotFoundException; import org.enso.lockmanager.client.ConnectedLockManager; +import org.enso.logger.masking.MaskedString; +import org.enso.pkg.QualifiedName; import org.enso.polyglot.LanguageInfo; import org.enso.polyglot.MethodNames; +import org.enso.polyglot.debugger.ExecutedVisualization; +import org.enso.polyglot.debugger.IdExecutionService; import org.enso.text.editing.JavaEditorAdapter; import org.enso.text.editing.model; -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. @@ -87,7 +77,6 @@ public final class ExecutionService { private final ExecuteRootNode execute = new ExecuteRootNode(); private final CallRootNode call = new CallRootNode(); private final InvokeMemberRootNode invoke = new InvokeMemberRootNode(); - private final Timer timer; /** @@ -168,6 +157,7 @@ public void initializeLanguageServerConnection(Endpoint endpoint) { * @param onCachedCallback the consumer of the cached value events. */ public void execute( + VisualizationHolder visualizationHolder, Module module, FunctionCallInstrumentationNode.FunctionCall call, RuntimeCache cache, @@ -176,23 +166,32 @@ public void execute( UUID nextExecutionItem, Consumer funCallCallback, Consumer onComputedCallback, - Consumer onCachedCallback - ) throws ArityException, SourceNotFoundException, UnsupportedMessageException, UnsupportedTypeException { + Consumer onCachedCallback, + Consumer onExecutedVisualizationCallback) + throws ArityException, + SourceNotFoundException, + UnsupportedMessageException, + UnsupportedTypeException { SourceSection src = call.getFunction().getSourceSection(); if (src == null) { throw new SourceNotFoundException(call.getFunction().getName()); } - var callbacks = new ExecutionCallbacks( - nextExecutionItem, cache, methodCallsCache, syncState, - onCachedCallback, onComputedCallback, funCallCallback - ); + var callbacks = + new ExecutionCallbacks( + visualizationHolder, + nextExecutionItem, + cache, + methodCallsCache, + syncState, + onCachedCallback, + onComputedCallback, + funCallCallback, + onExecutedVisualizationCallback); Optional> eventNodeFactory = - idExecutionInstrument.map(service -> service.bind( - module, - call.getFunction().getCallTarget(), - callbacks, - this.timer - )); + idExecutionInstrument.map( + service -> + service.bind( + module, call.getFunction().getCallTarget(), callbacks, this.timer)); Object p = context.getThreadManager().enter(); try { execute.getCallTarget().call(call); @@ -221,22 +220,27 @@ public void execute( String moduleName, String typeName, String methodName, + VisualizationHolder visualizationHolder, RuntimeCache cache, MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Consumer< - ExecutionService.ExpressionCall> funCallCallback, + Consumer funCallCallback, Consumer onComputedCallback, - Consumer onCachedCallback - ) - throws ArityException, TypeNotFoundException, MethodNotFoundException, - ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException { + Consumer onCachedCallback, + Consumer onExecutedVisualizationCallback) + throws ArityException, + TypeNotFoundException, + MethodNotFoundException, + ModuleNotFoundException, + UnsupportedMessageException, + UnsupportedTypeException { Module module = context.findModule(moduleName).orElseThrow(() -> new ModuleNotFoundException(moduleName)); FunctionCallInstrumentationNode.FunctionCall call = prepareFunctionCall(module, typeName, methodName); execute( + visualizationHolder, module, call, cache, @@ -245,20 +249,18 @@ public void execute( nextExecutionItem, funCallCallback, onComputedCallback, - onCachedCallback - ); + onCachedCallback, + onExecutedVisualizationCallback); } /** * Evaluates an expression in the scope of the provided module. * * @param module the module providing a scope for the expression - * @param expression the expression to evaluated + * @param expression the expression to evaluate * @return a result of evaluation */ - public Object evaluateExpression(Module module, String expression) - throws UnsupportedMessageException, ArityException, UnknownIdentifierException, - UnsupportedTypeException { + public Object evaluateExpression(Module module, String expression) { Object p = context.getThreadManager().enter(); try { return invoke.getCallTarget().call(module, expression); @@ -290,11 +292,10 @@ public String toDisplayString(Object receiver) { * @param argument the argument applied to the function * @return the result of calling the function */ - public Object callFunction(Object fn, Object argument) - throws UnsupportedTypeException, ArityException, UnsupportedMessageException { + public Object callFunction(Object fn, Object argument) { Object p = context.getThreadManager().enter(); try { - return call.getCallTarget().call(fn, new Object[] { argument }); + return call.getCallTarget().call(fn, new Object[] {argument}); } finally { context.getThreadManager().leave(p); } @@ -310,8 +311,11 @@ public Object callFunction(Object fn, Object argument) * @return the result of calling the function */ public Object callFunctionWithInstrument( - RuntimeCache cache, Module module, Object function, Object... arguments) - throws UnsupportedTypeException, ArityException, UnsupportedMessageException { + VisualizationHolder visualizationHolder, + RuntimeCache cache, + Module module, + Object function, + Object... arguments) { UUID nextExecutionItem = null; CallTarget entryCallTarget = (function instanceof Function) ? ((Function) function).getCallTarget() : null; @@ -322,18 +326,22 @@ public Object callFunctionWithInstrument( (value) -> context.getLogger().finest("_ON_COMPUTED " + value.getExpressionId()); Consumer onCachedCallback = (value) -> context.getLogger().finest("_ON_CACHED_VALUE " + value.getExpressionId()); - - var callbacks = new ExecutionCallbacks( - nextExecutionItem, cache, methodCallsCache, syncState, - onCachedCallback, onComputedCallback, funCallCallback - ); + Consumer onExecutedVisualizationCallback = (value) -> {}; + + var callbacks = + new ExecutionCallbacks( + visualizationHolder, + nextExecutionItem, + cache, + methodCallsCache, + syncState, + onCachedCallback, + onComputedCallback, + funCallCallback, + onExecutedVisualizationCallback); Optional> eventNodeFactory = - idExecutionInstrument.map(service -> service.bind( - module, - entryCallTarget, - callbacks, - this.timer - )); + idExecutionInstrument.map( + service -> service.bind(module, entryCallTarget, callbacks, this.timer)); Object p = context.getThreadManager().enter(); try { return call.getCallTarget().call(function, arguments); @@ -451,12 +459,11 @@ public String getExceptionMessage(PanicException panic) { var iop = InteropLibrary.getUncached(); var p = context.getThreadManager().enter(); try { - // Invoking a member on an Atom that does not have a method `to_display_text` will not, contrary to what is + // Invoking a member on an Atom that does not have a method `to_display_text` will not contrary to what is // expected from the documentation, throw an `UnsupportedMessageException`. // Instead it will crash with some internal assertion deep inside runtime. Hence the check. if (iop.isMemberInvocable(panic.getPayload(), "to_display_text")) { - return iop.asString( - iop.invokeMember(panic.getPayload(), "to_display_text")); + return iop.asString(iop.invokeMember(panic.getPayload(), "to_display_text")); } else throw UnsupportedMessageException.create(); } catch (UnsupportedMessageException | ArityException @@ -530,11 +537,15 @@ private static final class InvokeMemberRootNode extends RootNode { @Override public Object execute(VirtualFrame frame) { - var module = frame.getArguments()[0]; - var expression = frame.getArguments()[1]; + Object[] arguments = frame.getArguments(); + Object module = arguments[0]; + Object expression = arguments[1]; try { return iop.invokeMember(module, MethodNames.Module.EVAL_EXPRESSION, expression); - } catch (UnknownIdentifierException | UnsupportedTypeException | ArityException | UnsupportedMessageException ex) { + } catch (UnknownIdentifierException + | UnsupportedTypeException + | ArityException + | UnsupportedMessageException ex) { throw raise(RuntimeException.class, ex); } } @@ -686,7 +697,8 @@ public boolean isFunctionCallChanged() { } /** Points to the definition of a runtime function. */ - public record FunctionPointer(QualifiedName moduleName, QualifiedName typeName, String functionName) { + public record FunctionPointer( + QualifiedName moduleName, QualifiedName typeName, String functionName) { public static FunctionPointer fromFunction(Function function) { RootNode rootNode = function.getCallTarget().getRootNode(); @@ -753,8 +765,8 @@ public boolean equals(Object o) { return false; } FunctionCallInfo that = (FunctionCallInfo) o; - return Objects.equals(functionPointer, that.functionPointer) && Arrays.equals( - notAppliedArguments, that.notAppliedArguments); + return Objects.equals(functionPointer, that.functionPointer) + && Arrays.equals(notAppliedArguments, that.notAppliedArguments); } @Override @@ -768,14 +780,16 @@ public int hashCode() { * * @param call the function call. */ - public static FunctionCallInfo fromFunctionCall(FunctionCallInstrumentationNode.FunctionCall 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) { + private static int[] collectNotAppliedArguments( + FunctionCallInstrumentationNode.FunctionCall call) { Object[] arguments = call.getArguments(); int[] notAppliedArgs = new int[arguments.length]; int notAppliedArgsSize = 0; diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/CacheInvalidation.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/CacheInvalidation.scala index 8b209a7ebab3..8c501ddfbba0 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/CacheInvalidation.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/CacheInvalidation.scala @@ -153,8 +153,9 @@ object CacheInvalidation { command: Command, indexes: Set[IndexSelector] = Set() ): Unit = - visualizations.foreach { visualization => - run(visualization.cache, command, indexes) + visualizations.collect { + case visualization: Visualization.AttachedVisualization => + run(visualization.cache, command, indexes) } /** Run a cache invalidation instruction on an execution stack. diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/ExecutionContextManager.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/ExecutionContextManager.scala index 490f6b5d3011..ca10d4d142ea 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/ExecutionContextManager.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/ExecutionContextManager.scala @@ -34,18 +34,6 @@ class ExecutionContextManager { contexts -= id } - /** Gets a context with a given id. - * - * @param id the context id. - * @return the context with the given id, if exists. - */ - def get(id: ContextId): Option[ContextId] = - synchronized { - for { - _ <- contexts.get(id) - } yield id - } - /** Gets a stack for a given context id. * * @param id the context id. @@ -116,6 +104,16 @@ class ExecutionContextManager { state.visualizations.upsert(visualization) } + /** Gets a context with a given id. + * + * @param id the context id. + * @return the context with the given id, if exists. + */ + def getVisualizationHolder(id: ContextId): VisualizationHolder = + synchronized { + contexts.get(id).map(_.visualizations).getOrElse(new VisualizationHolder) + } + /** Get visualizations of all execution contexts. */ def getAllVisualizations: Iterable[Visualization] = synchronized { diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/Visualization.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/Visualization.scala index a84a5d39bf2c..d1c31dfc3169 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/Visualization.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/Visualization.scala @@ -2,25 +2,47 @@ package org.enso.interpreter.instrument import org.enso.interpreter.runtime.Module import org.enso.polyglot.runtime.Runtime.Api.{ + ContextId, ExpressionId, VisualizationConfiguration, VisualizationId } -/** An object containing visualization data. - * - * @param id the unique identifier of visualization - * @param expressionId the identifier of expression that the visualization is - * attached to - * @param callback the callable expression used to generate visualization data - */ -case class Visualization( - id: VisualizationId, - expressionId: ExpressionId, - cache: RuntimeCache, - module: Module, - config: VisualizationConfiguration, - visualizationExpressionId: Option[ExpressionId], - callback: AnyRef, - arguments: Vector[AnyRef] -) +sealed trait Visualization { + def id: VisualizationId + def expressionId: ExpressionId +} +object Visualization { + + /** An object containing visualization data. + * + * @param id the unique identifier of visualization + * @param expressionId the identifier of expression that the visualization is + * attached to + * @param callback the callable expression used to generate visualization data + */ + case class AttachedVisualization( + id: VisualizationId, + expressionId: ExpressionId, + cache: RuntimeCache, + module: Module, + config: VisualizationConfiguration, + visualizationExpressionId: Option[ExpressionId], + callback: AnyRef, + arguments: Vector[AnyRef] + ) extends Visualization + + /** An expression that will be executed in the local scope. + * + * @param id the unique identifier of visualization + * @param expressionId the identifier of expression that provides the execution scope + * @param executionContextId the identifier of the execution context + * @param expression the expression to execute + */ + case class OneshotExpression( + id: VisualizationId, + expressionId: ExpressionId, + executionContextId: ContextId, + expression: String + ) extends Visualization +} diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/VisualizationHolder.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/VisualizationHolder.scala index 0127f0d31636..60ea14868f42 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/VisualizationHolder.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/VisualizationHolder.scala @@ -7,7 +7,7 @@ import scala.collection.mutable /** A mutable holder of all visualizations attached to an execution context. */ -class VisualizationHolder() { +class VisualizationHolder { private val visualizationMap: mutable.Map[ExpressionId, List[Visualization]] = mutable.Map.empty.withDefaultValue(List.empty) @@ -50,8 +50,14 @@ class VisualizationHolder() { * @param module the qualified module name * @return a list of matching visualization */ - def findByModule(module: QualifiedName): Iterable[Visualization] = - visualizationMap.values.flatten.filter(_.module.getName == module) + def findByModule( + module: QualifiedName + ): Iterable[Visualization.AttachedVisualization] = + visualizationMap.values.flatten.collect { + case visualization: Visualization.AttachedVisualization + if visualization.module.getName == module => + visualization + } /** Returns a visualization with the provided id. * @@ -69,6 +75,6 @@ class VisualizationHolder() { object VisualizationHolder { /** Returns an empty visualization holder. */ - def empty = new VisualizationHolder() + def empty = new VisualizationHolder } diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/CommandFactory.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/CommandFactory.scala index 3513b9a44909..2a14a40c67a3 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/CommandFactory.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/command/CommandFactory.scala @@ -37,6 +37,15 @@ object CommandFactory { case payload: Api.AttachVisualization => new AttachVisualizationCmd(request.requestId, payload) + case payload: Api.ExecuteExpression => + new ExecuteExpressionCommand( + request.requestId, + payload.contextId, + payload.visualizationId, + payload.expressionId, + payload.expression + ) + case payload: Api.DetachVisualization => new DetachVisualizationCmd(request.requestId, payload) diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala index 0ab083317a0e..deb389b90315 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/EnsureCompiledJob.scala @@ -508,10 +508,14 @@ final class EnsureCompiledJob( private def getCacheMetadata( visualization: Visualization - ): Option[CachePreferenceAnalysis.Metadata] = { - val module = visualization.module - module.getIr.getMetadata(CachePreferenceAnalysis) - } + ): Option[CachePreferenceAnalysis.Metadata] = + visualization match { + case visualization: Visualization.AttachedVisualization => + val module = visualization.module + module.getIr.getMetadata(CachePreferenceAnalysis) + case _: Visualization.OneshotExpression => + None + } /** Get all project modules in the current compiler scope. */ private def getProjectModulesInScope(implicit 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 586b63cea1f2..7252ae5a7f75 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,11 +2,7 @@ package org.enso.interpreter.instrument.job import cats.implicits._ import com.oracle.truffle.api.exception.AbstractTruffleException -import org.enso.interpreter.service.ExecutionService.{ - ExpressionCall, - ExpressionValue, - FunctionPointer -} +import org.enso.interpreter.instrument._ import org.enso.interpreter.instrument.execution.{ Completion, ErrorResolver, @@ -14,7 +10,6 @@ import org.enso.interpreter.instrument.execution.{ RuntimeContext } import org.enso.interpreter.instrument.profiling.ExecutionTime -import org.enso.interpreter.instrument._ import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode.FunctionCall import org.enso.interpreter.node.expression.builtin.meta.TypeOfNode import org.enso.interpreter.runtime.`type`.{Types, TypesGen} @@ -26,8 +21,14 @@ import org.enso.interpreter.runtime.error.{ WarningsLibrary, WithWarnings } +import org.enso.interpreter.service.ExecutionService.{ + ExpressionCall, + ExpressionValue, + FunctionPointer +} import org.enso.interpreter.service.error._ import org.enso.polyglot.LanguageInfo +import org.enso.polyglot.debugger.ExecutedVisualization import org.enso.polyglot.runtime.Runtime.Api import org.enso.polyglot.runtime.Runtime.Api.{ContextId, ExecutionResult} @@ -92,23 +93,60 @@ object ProgramExecutionSupport { cache, syncState ) => + val onExecutedVisualizationCallback: Consumer[ExecutedVisualization] = { + executedVisualization => + val visualizationResult = + Either.cond( + executedVisualization.error() eq null, + executedVisualization.result(), + executedVisualization.error() + ) + sendVisualizationUpdate( + visualizationResult, + contextId, + syncState, + executedVisualization.visualizationId(), + executedVisualization.expressionId(), + executedVisualization.expressionValue() + ) + } + ctx.executionService.execute( module.toString, cons.item, function, + ctx.contextManager.getVisualizationHolder(contextId), cache, methodCallsCache, syncState, callStack.headOption.map(_.expressionId).orNull, callablesCallback, onComputedValueCallback, - onCachedValueCallback + onCachedValueCallback, + onExecutedVisualizationCallback ) case ExecutionFrame( ExecutionItem.CallData(expressionId, callData), cache, syncState ) => + val onExecutedVisualizationCallback: Consumer[ExecutedVisualization] = { + executedVisualization => + val visualizationResult = + Either.cond( + executedVisualization.error() eq null, + executedVisualization.result(), + executedVisualization.error() + ) + sendVisualizationUpdate( + visualizationResult, + contextId, + syncState, + executedVisualization.visualizationId(), + executedVisualization.expressionId(), + executedVisualization.expressionValue() + ) + } val module = ctx.executionService.getContext .findModuleByExpressionId(expressionId) @@ -116,6 +154,7 @@ object ProgramExecutionSupport { new ModuleNotFoundForExpressionIdException(expressionId) ) ctx.executionService.execute( + ctx.contextManager.getVisualizationHolder(contextId), module, callData, cache, @@ -124,7 +163,8 @@ object ProgramExecutionSupport { callStack.headOption.map(_.expressionId).orNull, callablesCallback, onComputedValueCallback, - onCachedValueCallback + onCachedValueCallback, + onExecutedVisualizationCallback ) } @@ -417,7 +457,6 @@ object ProgramExecutionSupport { * @param value the computed value * @param ctx the runtime context */ - @com.oracle.truffle.api.CompilerDirectives.TruffleBoundary private def sendVisualizationUpdates( contextId: ContextId, syncState: UpdatesSynchronizationState, @@ -429,70 +468,82 @@ object ProgramExecutionSupport { contextId, value.getExpressionId ) - visualizations.foreach { visualization => - sendVisualizationUpdate( - contextId, - syncState, - visualization, - value.getExpressionId, - value.getValue - ) + visualizations.collect { + case visualization: Visualization.AttachedVisualization => + executeAndSendVisualizationUpdate( + contextId, + syncState, + visualization, + value.getExpressionId, + value.getValue + ) } } } + private def executeVisualization( + contextId: ContextId, + visualization: Visualization.AttachedVisualization, + expressionId: UUID, + expressionValue: AnyRef + )(implicit ctx: RuntimeContext): Either[Throwable, AnyRef] = + Either + .catchNonFatal { + val logger = ctx.executionService.getLogger + logger.log( + Level.FINEST, + "Executing visualization [{0}] on expression [{1}] of [{2}]...", + Array[Object]( + visualization.id, + expressionId, + Try(TypeOfNode.getUncached.execute(expressionValue)) + .getOrElse(expressionValue.getClass) + ) + ) + ctx.executionService.callFunctionWithInstrument( + ctx.contextManager.getVisualizationHolder(contextId), + visualization.cache, + visualization.module, + visualization.callback, + expressionValue +: visualization.arguments: _* + ) + } + /** Compute the visualization of the expression value and send an update. * * @param contextId an identifier of an execution context - * @param visualization the visualization data + * @param visualizationId the id of the visualization * @param expressionId the id of expression to visualise * @param expressionValue the value of expression to visualise * @param ctx the runtime context */ - def sendVisualizationUpdate( + private def sendVisualizationUpdate( + visualizationResult: Either[Throwable, AnyRef], contextId: ContextId, syncState: UpdatesSynchronizationState, - visualization: Visualization, + visualizationId: UUID, expressionId: UUID, expressionValue: AnyRef )(implicit ctx: RuntimeContext): Unit = { - val errorOrVisualizationData = - Either - .catchNonFatal { - ctx.executionService.getLogger.log( - Level.FINEST, - "Executing visualization [{0}] on expression [{1}] of [{2}]...", - Array[Object]( - visualization.config, - expressionId, - Try(TypeOfNode.getUncached.execute(expressionValue)) - .getOrElse(expressionValue.getClass) - ) - ) - ctx.executionService.callFunctionWithInstrument( - visualization.cache, - visualization.module, - visualization.callback, - expressionValue +: visualization.arguments: _* - ) - } - .flatMap(visualizationResultToBytes) - val result = errorOrVisualizationData match { + val result = visualizationResultToBytes(visualizationResult) match { case Left(_: ThreadInterruptedException) => Completion.Interrupted case Left(error) => val message = Option(error.getMessage).getOrElse(error.getClass.getSimpleName) - val typeOfNode = Try(TypeOfNode.getUncached.execute(expressionValue)) - if (!typeOfNode.map(TypesGen.isPanicSentinel).getOrElse(false)) { + if (!TypesGen.isPanicSentinel(expressionValue)) { + val typeOfNode = + Option(TypeOfNode.getUncached.execute(expressionValue)) + .getOrElse(expressionValue.getClass) ctx.executionService.getLogger.log( Level.WARNING, - "Execution of visualization [{0}] on value [{1}] of [{2}] failed.", + "Execution of visualization [{0}] on value [{1}] of [{2}] failed. {3}", Array[Object]( - visualization.config, + visualizationId, expressionId, - typeOfNode.getOrElse(expressionValue.getClass), + typeOfNode, + message, error ) ) @@ -501,7 +552,7 @@ object ProgramExecutionSupport { Api.Response( Api.VisualizationEvaluationFailed( contextId, - visualization.id, + visualizationId, expressionId, message, getDiagnosticOutcome.lift(error) @@ -520,7 +571,7 @@ object ProgramExecutionSupport { Api.Response( Api.VisualizationUpdate( Api.VisualizationContext( - visualization.id, + visualizationId, contextId, expressionId ), @@ -535,20 +586,56 @@ object ProgramExecutionSupport { } } + /** Compute the visualization of the expression value and send an update. + * + * @param contextId an identifier of an execution context + * @param visualization the visualization data + * @param expressionId the id of expression to visualise + * @param expressionValue the value of expression to visualise + * @param ctx the runtime context + */ + def executeAndSendVisualizationUpdate( + contextId: ContextId, + syncState: UpdatesSynchronizationState, + visualization: Visualization, + expressionId: UUID, + expressionValue: AnyRef + )(implicit ctx: RuntimeContext): Unit = + visualization match { + case visualization: Visualization.AttachedVisualization => + val visualizationResult = executeVisualization( + contextId, + visualization, + expressionId, + expressionValue + ) + sendVisualizationUpdate( + visualizationResult, + contextId, + syncState, + visualization.id, + expressionId, + expressionValue + ) + case _: Visualization.OneshotExpression => + } + /** Convert the result of Enso visualization function to a byte array. * - * @param value the result of Enso visualization function + * @param visualizationResult the result of Enso visualization function * @return either a byte array representing the visualization result or an * error */ private def visualizationResultToBytes( - value: AnyRef - ): Either[VisualizationException, Array[Byte]] = { - Option(VisualizationResult.visualizationResultToBytes(value)).toRight( - new VisualizationException( - s"Cannot encode ${value.getClass} to byte array." + visualizationResult: Either[Throwable, AnyRef] + ): Either[Throwable, Array[Byte]] = { + visualizationResult.flatMap { value => + Option(VisualizationResult.visualizationResultToBytes(value)).toRight( + new VisualizationException( + s"Cannot encode ${value.getClass} to byte array." + ) ) - ) + } } /** Extract the method call information from the provided expression value. diff --git a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala index 68da0525617a..9b6932c65ef3 100644 --- a/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala +++ b/engine/runtime-instrument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala @@ -79,7 +79,7 @@ class UpsertVisualizationJob( case Right(EvaluationResult(module, callable, arguments)) => val visualization = - UpsertVisualizationJob.updateVisualization( + UpsertVisualizationJob.updateAttachedVisualization( visualizationId, expressionId, module, @@ -96,7 +96,7 @@ class UpsertVisualizationJob( ) cachedValue match { case Some(value) => - ProgramExecutionSupport.sendVisualizationUpdate( + ProgramExecutionSupport.executeAndSendVisualizationUpdate( config.executionContextId, stack.headOption.get.syncState, visualization, @@ -185,30 +185,39 @@ object UpsertVisualizationJob { */ def upsertVisualization( visualization: Visualization - )(implicit ctx: RuntimeContext, logger: TruffleLogger): Unit = { - val visualizationConfig = visualization.config - val expressionId = visualization.expressionId - val visualizationId = visualization.id - val maybeCallable = - evaluateVisualizationExpression( - visualizationConfig.visualizationModule, - visualizationConfig.expression - ) + )(implicit ctx: RuntimeContext, logger: TruffleLogger): Unit = + visualization match { + case visualization: Visualization.AttachedVisualization => + val visualizationConfig = visualization.config + val expressionId = visualization.expressionId + val visualizationId = visualization.id + val maybeCallable = + evaluateVisualizationExpression( + visualizationConfig.visualizationModule, + visualizationConfig.expression + ) + + maybeCallable.foreach { result => + updateAttachedVisualization( + visualizationId, + expressionId, + result.module, + visualizationConfig, + result.callback, + result.arguments + ) + val stack = + ctx.contextManager.getStack(visualizationConfig.executionContextId) + requireVisualizationSynchronization(stack, expressionId) + } + + case visualization: Visualization.OneshotExpression => + ctx.contextManager.upsertVisualization( + visualization.executionContextId, + visualization + ) - maybeCallable.foreach { result => - updateVisualization( - visualizationId, - expressionId, - result.module, - visualizationConfig, - result.callback, - result.arguments - ) - val stack = - ctx.contextManager.getStack(visualizationConfig.executionContextId) - requireVisualizationSynchronization(stack, expressionId) } - } /** Find module by name. * @@ -462,7 +471,7 @@ object UpsertVisualizationJob { * @param ctx the runtime context * @return the re-evaluated visualization */ - private def updateVisualization( + private def updateAttachedVisualization( visualizationId: Api.VisualizationId, expressionId: Api.ExpressionId, module: Module, @@ -472,16 +481,17 @@ object UpsertVisualizationJob { )(implicit ctx: RuntimeContext, logger: TruffleLogger): Visualization = { val visualizationExpressionId = findVisualizationExpressionId(module, visualizationConfig.expression) - val visualization = Visualization( - visualizationId, - expressionId, - new RuntimeCache(), - module, - visualizationConfig, - visualizationExpressionId, - callback, - arguments - ) + val visualization = + Visualization.AttachedVisualization( + visualizationId, + expressionId, + new RuntimeCache(), + module, + visualizationConfig, + visualizationExpressionId, + callback, + arguments + ) val writeLockTimestamp = ctx.locking.acquireWriteCompilationLock() try { invalidateCaches(visualization) @@ -575,15 +585,19 @@ object UpsertVisualizationJob { * * @param visualization the visualization to update */ - private def setCacheWeights(visualization: Visualization): Unit = { - visualization.module.getIr.getMetadata(CachePreferenceAnalysis).foreach { - metadata => - CacheInvalidation.runVisualizations( - Seq(visualization), - CacheInvalidation.Command.SetMetadata(metadata) - ) + private def setCacheWeights(visualization: Visualization): Unit = + visualization match { + case visualization: Visualization.AttachedVisualization => + visualization.module.getIr + .getMetadata(CachePreferenceAnalysis) + .foreach { metadata => + CacheInvalidation.runVisualizations( + Seq(visualization), + CacheInvalidation.Command.SetMetadata(metadata) + ) + } + case _: Visualization.OneshotExpression => } - } /** Invalidate the first cached dependent node of the provided expression. * 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 2d630f69a59a..f4b0c426e008 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,8 +1,5 @@ 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; @@ -10,6 +7,7 @@ import com.oracle.truffle.api.exception.AbstractTruffleException; import com.oracle.truffle.api.frame.FrameInstance; import com.oracle.truffle.api.frame.FrameInstanceVisitor; +import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.instrumentation.EventContext; @@ -20,16 +18,18 @@ 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.interop.TruffleObject; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; - import java.util.UUID; - import org.enso.interpreter.node.ClosureRootNode; +import org.enso.interpreter.node.EnsoRootNode; import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; +import org.enso.interpreter.node.expression.debug.EvalNode; import org.enso.interpreter.runtime.EnsoContext; import org.enso.interpreter.runtime.Module; +import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.control.TailCallException; import org.enso.interpreter.runtime.data.text.Text; @@ -39,8 +39,7 @@ import org.enso.interpreter.runtime.state.State; import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag; import org.enso.interpreter.runtime.tag.IdentifiedTag; - -import com.oracle.truffle.api.interop.TruffleObject; +import org.enso.polyglot.debugger.IdExecutionService; /** An instrument for getting values from AST-identified expressions. */ @TruffleInstrument.Registration( @@ -68,6 +67,8 @@ private static class IdEventNodeFactory implements ExecutionEventNodeFactory { private final Callbacks callbacks; private final Timer timer; + private final EvalNode evalNode = EvalNode.build(); + /** * Creates a new event node factory. * @@ -75,11 +76,7 @@ private static class IdEventNodeFactory implements ExecutionEventNodeFactory { * @param callbacks communication with users * @param timer the timer for timing execution */ - IdEventNodeFactory( - CallTarget entryCallTarget, - Callbacks callbacks, - Timer timer - ) { + IdEventNodeFactory(CallTarget entryCallTarget, Callbacks callbacks, Timer timer) { this.entryCallTarget = entryCallTarget; this.callbacks = callbacks; this.timer = timer; @@ -90,9 +87,97 @@ public ExecutionEventNode create(EventContext context) { return new IdExecutionEventNode(context); } - /** - * The execution event node class used by this instrument. - */ + /** Implementation of {@link Info} for the instrumented {@link Node}. */ + private final class NodeInfo extends Info { + + private final UUID nodeId; + private final Object result; + private final long elapsedTime; + private final MaterializedFrame materializedFrame; + private final EnsoRootNode ensoRootNode; + + /** + * Create a {@link NodeInfo} for the entered node. + * + * @param materializedFrame the execution frame + * @param node the entered node + */ + public NodeInfo( + MaterializedFrame materializedFrame, + Node node) { + super(); + + this.nodeId = getNodeId(node); + this.result = null; + this.elapsedTime = -1; + this.materializedFrame = materializedFrame; + this.ensoRootNode = (EnsoRootNode) node.getRootNode(); + } + + /** + * Create a {@link NodeInfo} for the executed node. + * + * @param nodeId the id of the executed node + * @param result the result of the node execution + * @param elapsedTime the execution time + * @param materializedFrame the execution frame + * @param node the executed node + */ + public NodeInfo( + UUID nodeId, + Object result, + long elapsedTime, + MaterializedFrame materializedFrame, + Node node) { + super(); + + this.nodeId = nodeId; + this.result = result; + this.elapsedTime = elapsedTime; + this.materializedFrame = materializedFrame; + this.ensoRootNode = (EnsoRootNode) node.getRootNode(); + } + + @Override + public UUID getId() { + return nodeId; + } + + @Override + public Object getResult() { + return result; + } + + @Override + public boolean isPanic() { + return result instanceof AbstractTruffleException && !(result instanceof DataflowError); + } + + @Override + public long getElapsedTime() { + return elapsedTime; + } + + @Override + public Object eval(String code) { + CallerInfo callerInfo = + new CallerInfo( + materializedFrame, ensoRootNode.getLocalScope(), ensoRootNode.getModuleScope()); + + return evalNode.execute(callerInfo, State.create(EnsoContext.get(null)), Text.create(code)); + } + + private static UUID getNodeId(Node node) { + return switch (node) { + case ExpressionNode n -> n.getId(); + case FunctionCallInstrumentationNode n -> n.getId(); + case null -> null; + default -> null; + }; + } + } + + /** The execution event node class used by this instrument. */ private class IdExecutionEventNode extends ExecutionEventNode { private final EventContext context; @@ -117,13 +202,10 @@ public void onEnter(VirtualFrame frame) { if (!isTopFrame(entryCallTarget)) { return; } - onEnterImpl(); - } - @CompilerDirectives.TruffleBoundary - private void onEnterImpl() { - UUID nodeId = getNodeId(context.getInstrumentedNode()); - var result = callbacks.findCachedResult(nodeId); + Info info = new NodeInfo(frame.materialize(), context.getInstrumentedNode()); + Object result = callbacks.findCachedResult(info); + if (result != null) { throw context.createUnwind(result); } @@ -131,12 +213,11 @@ private void onEnterImpl() { } /** - * Triggered when a node (either a function call sentry or an identified - * expression) finishes execution. + * 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. + * @param result the result of executing the node this method was triggered for. */ @Override public void onReturnValue(VirtualFrame frame, Object result) { @@ -146,15 +227,28 @@ public void onReturnValue(VirtualFrame frame, Object result) { } 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 (node instanceof FunctionCallInstrumentationNode functionCallInstrumentationNode + && result instanceof FunctionCallInstrumentationNode.FunctionCall) { + Info info = + new NodeInfo( + functionCallInstrumentationNode.getId(), + result, + nanoTimeElapsed, + frame.materialize(), + node); + Object cachedResult = callbacks.onFunctionReturn(info); if (cachedResult != null) { throw context.createUnwind(cachedResult); } - } else if (node instanceof ExpressionNode) { - onExpressionReturn(result, node, context, nanoTimeElapsed); + } else if (node instanceof ExpressionNode expressionNode) { + Info info = + new NodeInfo( + expressionNode.getId(), result, nanoTimeElapsed, frame.materialize(), node); + callbacks.updateCachedResult(info); + + if (info.isPanic()) { + throw context.createUnwind(result); + } } } @@ -169,25 +263,13 @@ public void onReturnExceptional(VirtualFrame frame, Throwable exception) { } } - private void onExpressionReturn(Object result, Node node, EventContext context, long howLong) { - boolean isPanic = result instanceof AbstractTruffleException && !(result instanceof DataflowError); - UUID nodeId = ((ExpressionNode) node).getId(); - - callbacks.updateCachedResult(nodeId, result, isPanic, howLong); - if (isPanic) { - throw context.createUnwind(result); - } - } - @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()); + FunctionCallInstrumentationNode.FunctionCall functionCall = + new FunctionCallInstrumentationNode.FunctionCall( + tailCallException.getFunction(), state, tailCallException.getArguments()); Object result = InteropLibrary.getFactory().getUncached().execute(functionCall); onReturnValue(null, result); } catch (InteropException e) { @@ -196,47 +278,39 @@ private void onTailCallReturn(Throwable exception, State state) { } /** - * Checks if we're not inside a recursive call, i.e. the - * {@link #entryCallTarget} only appears in the stack trace once. + * 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. + * @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; - } + Object result = + Truffle.getRuntime() + .iterateFrames( + new FrameInstanceVisitor() { + boolean seenFirst = false; - private static UUID getNodeId(Node node) { - return switch (node) { - case ExpressionNode n -> n.getId(); - case FunctionCallInstrumentationNode n -> n.getId(); - case null -> null; - default -> null; - }; + @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; } } } /** - * 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 mod module that contains the code * @param entryCallTarget the call target being observed. @@ -246,19 +320,19 @@ private static UUID getNodeId(Node node) { */ @Override public EventBinding bind( - TruffleObject mod, - CallTarget entryCallTarget, - Callbacks callbacks, - Object timer - ) { - var module = (Module)mod; - var builder = SourceSectionFilter.newBuilder() + TruffleObject mod, CallTarget entryCallTarget, Callbacks callbacks, Object timer) { + var module = (Module) mod; + var builder = + SourceSectionFilter.newBuilder() .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() instanceof SourceSection section && section != null) { + if (entryCallTarget instanceof RootCallTarget r + && r.getRootNode() instanceof ClosureRootNode c + && c.getSourceSection() instanceof SourceSection section + && section != null) { final int firstFunctionLine = section.getStartLine(); final int afterFunctionLine = section.getEndLine() + 1; builder.lineIn(SourceSectionFilter.IndexRange.between(firstFunctionLine, afterFunctionLine)); diff --git a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index 5f8c701d4679..fe484713ff8b 100644 --- a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -3513,4 +3513,365 @@ class RuntimeVisualizationsTest } new String(data1, StandardCharsets.UTF_8) shouldEqual "C" } + + it should "execute expression in the scope of local expression cached" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualizationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idOp1 = metadata.addItem(23, 2) + val idOp2 = metadata.addItem(42, 13) + + val code = + """main = + | operator1 = 42 + | operator2 = operator1 + 1 + | operator2 + | + |fun1 x = x.to_text + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 4 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idOp1, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idOp2, ConstantsGen.INTEGER_BUILTIN), + context.executionComplete(contextId) + ) + + // execute expression + context.send( + Api.Request( + requestId, + Api.ExecuteExpression( + contextId, + visualizationId, + idOp2, + "fun1 operator1" + ) + ) + ) + val executeExpressionResponses = + context.receiveNIgnoreExpressionUpdates(3) + executeExpressionResponses should contain allOf ( + Api.Response(requestId, Api.VisualizationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = executeExpressionResponses.collectFirst { + case Api.Response( + None, + Api.VisualizationUpdate( + Api.VisualizationContext( + `visualizationId`, + `contextId`, + `idOp2` + ), + data + ) + ) => + data + } + new String(data) shouldEqual "42" + } + + it should "execute expression in the scope of local expression not cached" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualizationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idOp1 = metadata.addItem(23, 2) + val idOp2 = metadata.addItem(42, 13) + val idRes = metadata.addItem(60, 9) + + val code = + """main = + | operator1 = 42 + | operator2 = operator1 + 1 + | operator2 + | + |fun1 x = x.to_text + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 5 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idOp1, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idOp2, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idRes, ConstantsGen.INTEGER_BUILTIN), + context.executionComplete(contextId) + ) + + // execute expression + context.send( + Api.Request( + requestId, + Api.ExecuteExpression( + contextId, + visualizationId, + idRes, + "fun1 operator1" + ) + ) + ) + val executeExpressionResponses = + context.receiveNIgnoreExpressionUpdates(3) + executeExpressionResponses should contain allOf ( + Api.Response(requestId, Api.VisualizationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = executeExpressionResponses.collectFirst { + case Api.Response( + None, + Api.VisualizationUpdate( + Api.VisualizationContext( + `visualizationId`, + `contextId`, + `idRes` + ), + data + ) + ) => + data + } + new String(data) shouldEqual "42" + } + + it should "execute expression in the scope of local binding" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualizationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idOp1 = metadata.addItem(23, 2) + val idOp2 = metadata.addItem(42, 13) + val idOp2Binding = metadata.addItem(30, 25) + val idRes = metadata.addItem(60, 9) + + val code = + """main = + | operator1 = 42 + | operator2 = operator1 + 1 + | operator2 + | + |fun1 x = x.to_text + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 6 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idOp1, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idOp2, ConstantsGen.INTEGER_BUILTIN), + TestMessages + .update(contextId, idOp2Binding, ConstantsGen.NOTHING_BUILTIN), + TestMessages.update(contextId, idRes, ConstantsGen.INTEGER_BUILTIN), + context.executionComplete(contextId) + ) + + // execute expression + context.send( + Api.Request( + requestId, + Api.ExecuteExpression( + contextId, + visualizationId, + idOp2Binding, + "fun1 operator1+operator2" + ) + ) + ) + val executeExpressionResponses = + context.receiveNIgnoreExpressionUpdates(3) + executeExpressionResponses should contain allOf ( + Api.Response(requestId, Api.VisualizationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = executeExpressionResponses.collectFirst { + case Api.Response( + None, + Api.VisualizationUpdate( + Api.VisualizationContext( + `visualizationId`, + `contextId`, + `idOp2Binding` + ), + data + ) + ) => + data + } + new String(data) shouldEqual "85" + } + + it should "execute expression in the scope of main method" in { + val contextId = UUID.randomUUID() + val requestId = UUID.randomUUID() + val visualizationId = UUID.randomUUID() + val moduleName = "Enso_Test.Test.Main" + val metadata = new Metadata + + val idOp1 = metadata.addItem(23, 2) + val idOp2 = metadata.addItem(42, 13) + val idMain = metadata.addItem(6, 63) + + val code = + """main = + | operator1 = 42 + | operator2 = operator1 + 1 + | operator2 + | + |fun1 x = x.to_text + |""".stripMargin.linesIterator.mkString("\n") + val contents = metadata.appendToCode(code) + val mainFile = context.writeMain(contents) + + // create context + context.send(Api.Request(requestId, Api.CreateContextRequest(contextId))) + context.receive shouldEqual Some( + Api.Response(requestId, Api.CreateContextResponse(contextId)) + ) + + // Open the new file + context.send( + Api.Request(requestId, Api.OpenFileRequest(mainFile, contents)) + ) + context.receive shouldEqual Some( + Api.Response(Some(requestId), Api.OpenFileResponse) + ) + + // push main + val item1 = Api.StackItem.ExplicitCall( + Api.MethodPointer(moduleName, moduleName, "main"), + None, + Vector() + ) + context.send( + Api.Request(requestId, Api.PushContextRequest(contextId, item1)) + ) + context.receiveNIgnorePendingExpressionUpdates( + 5 + ) should contain theSameElementsAs Seq( + Api.Response(requestId, Api.PushContextResponse(contextId)), + TestMessages.update(contextId, idOp1, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idOp2, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idMain, ConstantsGen.INTEGER_BUILTIN), + context.executionComplete(contextId) + ) + + // execute expression + context.send( + Api.Request( + requestId, + Api.ExecuteExpression( + contextId, + visualizationId, + idMain, + "fun1 operator1+operator2" + ) + ) + ) + val executeExpressionResponses = + context.receiveNIgnoreExpressionUpdates(3) + executeExpressionResponses should contain allOf ( + Api.Response(requestId, Api.VisualizationAttached()), + context.executionComplete(contextId) + ) + val Some(data) = executeExpressionResponses.collectFirst { + case Api.Response( + None, + Api.VisualizationUpdate( + Api.VisualizationContext( + `visualizationId`, + `contextId`, + `idMain` + ), + data + ) + ) => + data + } + new String(data) shouldEqual "85" + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java index 8ecb2dff409e..bad08d407b36 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java @@ -201,7 +201,7 @@ protected CallTarget parse(ParsingRequest request) { /** * Parses the given Enso source code snippet in {@code request}. - * + *

* Inline parsing does not handle the following expressions: *

    *
  • Assignments
  • 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 8776e371bcaf..007a9e1d75ec 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 @@ -1,7 +1,5 @@ package org.enso.interpreter.node.expression.builtin.meta; -import java.util.UUID; - import org.enso.interpreter.instrument.Timer; import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; import org.enso.interpreter.runtime.EnsoContext; @@ -15,7 +13,6 @@ import com.oracle.truffle.api.instrumentation.EventBinding; import com.oracle.truffle.api.interop.InteropException; import com.oracle.truffle.api.interop.InteropLibrary; -import com.oracle.truffle.api.interop.TruffleObject; final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks { @@ -61,33 +58,33 @@ final Instrumentor deactivate() { // Callbacks // @Override - public Object findCachedResult(UUID nodeId) { + public Object findCachedResult(IdExecutionService.Info info) { try { if (onEnter != null) { - var ret = InteropLibrary.getUncached().execute(onEnter, nodeId.toString()); + var ret = InteropLibrary.getUncached().execute(onEnter, info.getId().toString()); ret = InteropLibrary.getUncached().isNull(ret) ? null : ret; - return handle.isDisposed() ? null : ret; - } - } catch (InteropException ex) { + return handle.isDisposed() ? null : ret; } + } catch (InteropException ignored) { } return null; } @Override - public void updateCachedResult(UUID nodeId, Object result, boolean isPanic, long nanoElapsedTime) { + public void updateCachedResult(IdExecutionService.Info info) { try { if (onReturn != null) { - InteropLibrary.getUncached().execute(onReturn, nodeId.toString(), result); + InteropLibrary.getUncached().execute(onReturn, info.getId().toString(), info.getResult()); } - } catch (InteropException ex) { + } catch (InteropException ignored) { } } @Override - public Object onFunctionReturn(UUID nodeId, TruffleObject result) { + public Object onFunctionReturn(IdExecutionService.Info info) { try { - if (onCall != null && result instanceof FunctionCallInstrumentationNode.FunctionCall call) { - var args = (Object[])call.getArguments().clone(); + if (onCall != null + && info.getResult() instanceof FunctionCallInstrumentationNode.FunctionCall call) { + var args = (Object[]) call.getArguments().clone(); for (var i = 0; i < args.length; i++) { if (args[i] == null) { args[i] = EnsoContext.get(null).getBuiltins().nothing(); @@ -95,14 +92,14 @@ public Object onFunctionReturn(UUID nodeId, TruffleObject result) { } var ret = InteropLibrary.getUncached().execute( onCall, - nodeId.toString(), + info.getId().toString(), call.getFunction(), ArrayLikeHelpers.asVectorWithCheckAt(args) ); ret = InteropLibrary.getUncached().isNull(ret) ? null : ret; return handle.isDisposed() ? null : ret; } - } catch (InteropException ex) { + } catch (InteropException ignored) { } return null; }