Inline Execution (#8148)

close #8132

Update the `executionContext/executeExpression` request to execute expressions in the local scope.
This commit is contained in:
Dmitry Bushev 2023-11-13 16:05:05 +00:00 committed by GitHub
parent 876cbd7b5d
commit 565a858c5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1380 additions and 563 deletions

View File

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

View File

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

View File

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

View File

@ -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) +
")"
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,36 +9,60 @@ import java.util.UUID;
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);
}
/**

View File

@ -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`.
*

View File

@ -67,7 +67,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)
@ -111,7 +111,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
@ -141,7 +141,7 @@ class Compiler(
shouldCompileDependencies: Boolean,
useGlobalCacheLocations: Boolean
): Future[java.lang.Boolean] = {
getPackageRepository().getMainProjectPackage match {
getPackageRepository.getMainProjectPackage match {
case None =>
context.log(
Level.SEVERE,
@ -268,7 +268,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}]",
@ -278,7 +278,7 @@ class Compiler(
importedModulesLoadedFromSource.take(10).mkString("", ",", "...")
)
)
context.updateModule(module, _.invalidateCache)
context.updateModule(module, _.invalidateCache())
parseModule(module)
runImportsAndExportsResolution(module, generateCode)
} else {
@ -457,9 +457,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
}
@ -572,7 +572,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)) {
@ -603,7 +603,7 @@ class Compiler(
"Loading module [{0}] from source.",
context.getModuleName(module)
)
context.updateModule(module, _.resetScope)
context.updateModule(module, _.resetScope())
val moduleContext = ModuleContext(
module = module,
@ -694,10 +694,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)
}
}
@ -742,14 +742,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.
@ -858,7 +850,7 @@ class Compiler(
* for inline evaluation
* @return the output result of the
*/
def runCompilerPhasesInline(
private def runCompilerPhasesInline(
ir: Expression,
inlineContext: InlineContext
): Expression = {
@ -872,12 +864,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(
@ -895,7 +887,7 @@ class Compiler(
*
* @param modules the modules to check against errors
*/
def runErrorHandling(
private def runErrorHandling(
modules: List[Module]
): Unit = {
if (config.isStrictErrors) {
@ -921,7 +913,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),

View File

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

View File

@ -459,7 +459,7 @@ case object FullyQualifiedNames extends IRPass {
override def restoreFromSerialization(
compiler: CompilerContext
): Option[PartiallyResolvedFQN] = {
val packageRepository = compiler.getPackageRepository()
val packageRepository = compiler.getPackageRepository
moduleRef
.toConcrete(packageRepository.getModuleMap)
.map(ResolvedModule(_))

View File

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

View File

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

View File

@ -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.runtime.type.Constants;
import org.enso.interpreter.service.ExecutionService.ExpressionCall;
import org.enso.interpreter.service.ExecutionService.ExpressionValue;
import org.enso.interpreter.service.ExecutionService.FunctionCallInfo;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.interop.TruffleObject;
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<ExpressionValue> onCachedCallback;
private final Consumer<ExpressionValue> onComputedCallback;
private final Consumer<ExpressionCall> functionCallCallback;
private final Consumer<ExecutedVisualization> 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<ExpressionValue> onCachedCallback, Consumer<ExpressionValue> onComputedCallback,
Consumer<ExpressionCall> functionCallCallback
) {
VisualizationHolder visualizationHolder,
UUID nextExecutionItem,
RuntimeCache cache,
MethodCallsCache methodCallsCache,
UpdatesSynchronizationState syncState,
Consumer<ExpressionValue> onCachedCallback,
Consumer<ExpressionValue> onComputedCallback,
Consumer<ExpressionCall> functionCallCallback,
Consumer<ExecutedVisualization> 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 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
}
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 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
}
@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 @@ final class ExecutionCallbacks implements IdExecutionService.Callbacks {
}
@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<Visualization> 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<Visualization> findVisualizations(UUID nodeId) {
return visualizationHolder.find(nodeId).iterator();
}
@CompilerDirectives.TruffleBoundary
private FunctionCallInfo functionCallInfoById(UUID nodeId) {
return calls.get(nodeId);

View File

@ -1,62 +1,5 @@
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.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.ExecutionEventNodeFactory;
import com.oracle.truffle.api.nodes.RootNode;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.function.Consumer;
import 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.node.callable.FunctionCallInstrumentationNode;
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.function.Function;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.error.PanicException;
import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.runtime.state.State;
import org.enso.interpreter.service.error.FailedToApplyEditsException;
import org.enso.interpreter.service.error.MethodNotFoundException;
import org.enso.interpreter.service.error.ModuleNotFoundException;
import org.enso.interpreter.service.error.SourceNotFoundException;
import org.enso.interpreter.service.error.TypeNotFoundException;
import org.enso.lockmanager.client.ConnectedLockManager;
import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.MethodNames;
import org.enso.text.editing.JavaEditorAdapter;
import org.enso.text.editing.model;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLogger;
@ -72,6 +15,53 @@ 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.compiler.context.SimpleUpdate;
import org.enso.interpreter.instrument.Endpoint;
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;
import org.enso.interpreter.runtime.state.State;
import org.enso.interpreter.service.error.FailedToApplyEditsException;
import org.enso.interpreter.service.error.MethodNotFoundException;
import org.enso.interpreter.service.error.ModuleNotFoundException;
import org.enso.interpreter.service.error.SourceNotFoundException;
import org.enso.interpreter.service.error.TypeNotFoundException;
import org.enso.lockmanager.client.ConnectedLockManager;
import org.enso.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;
/**
* 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 final class ExecutionService {
* @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 final class ExecutionService {
UUID nextExecutionItem,
Consumer<ExecutionService.ExpressionCall> funCallCallback,
Consumer<ExecutionService.ExpressionValue> onComputedCallback,
Consumer<ExecutionService.ExpressionValue> onCachedCallback
) throws ArityException, SourceNotFoundException, UnsupportedMessageException, UnsupportedTypeException {
Consumer<ExecutionService.ExpressionValue> onCachedCallback,
Consumer<ExecutedVisualization> 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<EventBinding<ExecutionEventNodeFactory>> 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 final class ExecutionService {
String moduleName,
String typeName,
String methodName,
VisualizationHolder visualizationHolder,
RuntimeCache cache,
MethodCallsCache methodCallsCache,
UpdatesSynchronizationState syncState,
UUID nextExecutionItem,
Consumer<
ExecutionService.ExpressionCall> funCallCallback,
Consumer<ExecutionService.ExpressionCall> funCallCallback,
Consumer<ExecutionService.ExpressionValue> onComputedCallback,
Consumer<ExecutionService.ExpressionValue> onCachedCallback
)
throws ArityException, TypeNotFoundException, MethodNotFoundException,
ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException {
Consumer<ExecutionService.ExpressionValue> onCachedCallback,
Consumer<ExecutedVisualization> 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 final class ExecutionService {
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 final class ExecutionService {
* @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 final class ExecutionService {
* @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 final class ExecutionService {
(value) -> context.getLogger().finest("_ON_COMPUTED " + value.getExpressionId());
Consumer<ExpressionValue> onCachedCallback =
(value) -> context.getLogger().finest("_ON_CACHED_VALUE " + value.getExpressionId());
Consumer<ExecutedVisualization> onExecutedVisualizationCallback = (value) -> {};
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<EventBinding<ExecutionEventNodeFactory>> 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 final class ExecutionService {
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 @@ public final class ExecutionService {
@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 final class ExecutionService {
}
/** 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 final class ExecutionService {
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 final class ExecutionService {
*
* @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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.Truffle;
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.StandardTags;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.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.error.PanicSentinel;
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 @@ public class IdExecutionInstrument extends TruffleInstrument implements IdExecut
private final Callbacks callbacks;
private final Timer timer;
private final EvalNode evalNode = EvalNode.build();
/**
* Creates a new event node factory.
*
@ -75,11 +76,7 @@ public class IdExecutionInstrument extends TruffleInstrument implements IdExecut
* @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 class IdExecutionInstrument extends TruffleInstrument implements IdExecut
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 class IdExecutionInstrument extends TruffleInstrument implements IdExecut
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 @@ public class IdExecutionInstrument extends TruffleInstrument implements IdExecut
}
/**
* 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 class IdExecutionInstrument extends TruffleInstrument implements IdExecut
}
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 class IdExecutionInstrument extends TruffleInstrument implements IdExecut
}
}
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 @@ public class IdExecutionInstrument extends TruffleInstrument implements IdExecut
}
/**
* 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<Object>() {
boolean seenFirst = false;
Object result =
Truffle.getRuntime()
.iterateFrames(
new FrameInstanceVisitor<Object>() {
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;
}
}
});
@Override
public Object visitFrame(FrameInstance frameInstance) {
CallTarget ct = frameInstance.getCallTarget();
if (ct != entryCallTarget) {
return null;
}
if (seenFirst) {
return new Object();
} else {
seenFirst = true;
return null;
}
}
});
return result == null;
}
private static UUID getNodeId(Node node) {
return switch (node) {
case ExpressionNode n -> n.getId();
case FunctionCallInstrumentationNode n -> n.getId();
case null -> null;
default -> 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 @@ public class IdExecutionInstrument extends TruffleInstrument implements IdExecut
*/
@Override
public EventBinding<ExecutionEventNodeFactory> 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));

View File

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

View File

@ -201,7 +201,7 @@ public final class EnsoLanguage extends TruffleLanguage<EnsoContext> {
/**
* Parses the given Enso source code snippet in {@code request}.
*
* <p>
* Inline parsing does not handle the following expressions:
* <ul>
* <li>Assignments</li>

View File

@ -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.CompilerDirectives;
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 class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
// 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 @@ final class Instrumentor implements EnsoObject, IdExecutionService.Callbacks {
}
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;
}