diff --git a/build.sbt b/build.sbt index eb22cd59bb..17caa17769 100644 --- a/build.sbt +++ b/build.sbt @@ -260,6 +260,11 @@ lazy val enso = (project in file(".")) searcher, launcher, downloader, + `runtime-language-epb`, + `runtime-instrument-id-execution`, + `runtime-instrument-repl-debugger`, + `runtime-instrument-runtime-server`, + `runtime-with-instruments`, `runtime-version-manager`, `runtime-version-manager-test`, editions, @@ -1156,6 +1161,30 @@ lazy val frgaalJavaCompilerSetting = Seq( Compile / javacOptions ++= Seq("-source", frgaalSourceLevel) ) + +lazy val instrumentationSettings = frgaalJavaCompilerSetting ++ Seq( + version := ensoVersion, + commands += WithDebugCommand.withDebug, + Compile/logManager := + sbt.internal.util.CustomLogManager.excludeMsg("Could not determine source for class ", Level.Warn), + Compile / javacOptions --= Seq("-source", frgaalSourceLevel), + libraryDependencies ++= Seq( + "org.graalvm.truffle" % "truffle-api" % graalVersion % "provided", + "org.graalvm.truffle" % "truffle-dsl-processor" % graalVersion % "provided", + ), + (Compile / javacOptions) ++= Seq( + "-s", + (Compile / sourceManaged).value.getAbsolutePath, + "-Xlint:unchecked", + ), +) + +lazy val `runtime-language-epb` = (project in file("engine/runtime-language-epb")) + .settings( + inConfig(Compile)(truffleRunOptionsSettings), + instrumentationSettings + ) + lazy val runtime = (project in file("engine/runtime")) .configs(Benchmark) .settings( @@ -1164,7 +1193,6 @@ lazy val runtime = (project in file("engine/runtime")) sbt.internal.util.CustomLogManager.excludeMsg("Could not determine source for class ", Level.Warn), version := ensoVersion, commands += WithDebugCommand.withDebug, - cleanInstruments := FixInstrumentsGeneration.cleanInstruments.value, inConfig(Compile)(truffleRunOptionsSettings), inConfig(Benchmark)(Defaults.testSettings), inConfig(Benchmark)( @@ -1197,14 +1225,9 @@ lazy val runtime = (project in file("engine/runtime")) "junit" % "junit" % "4.12" % Test, "com.novocode" % "junit-interface" % "0.11" % Test exclude ("junit", "junit-dep") ), - // Note [Unmanaged Classpath] - Test / unmanagedClasspath += (baseDirectory.value / ".." / ".." / "app" / "gui" / "view" / "graph-editor" / "src" / "builtin" / "visualization" / "native" / "inc"), Compile / compile / compileInputs := (Compile / compile / compileInputs) .dependsOn(CopyTruffleJAR.preCompileTask) .value, - Compile / compile := FixInstrumentsGeneration.patchedCompile - .dependsOn(FixInstrumentsGeneration.preCompileTask) - .value, // Note [Classpath Separation] Test / javaOptions ++= Seq( "-Dgraalvm.locatorDisabled=true", @@ -1258,20 +1281,7 @@ lazy val runtime = (project in file("engine/runtime")) }.evaluated, Benchmark / parallelExecution := false ) - .settings( - assembly / assemblyJarName := "runtime.jar", - assembly / test := {}, - assembly / assemblyOutputPath := file("runtime.jar"), - assembly / assemblyMergeStrategy := { - case PathList("META-INF", file, xs @ _*) if file.endsWith(".DSA") => - MergeStrategy.discard - case PathList("META-INF", file, xs @ _*) if file.endsWith(".SF") => - MergeStrategy.discard - case PathList("META-INF", "MANIFEST.MF", xs @ _*) => - MergeStrategy.discard - case _ => MergeStrategy.first - } - ) + .dependsOn(`runtime-language-epb`) .dependsOn(`edition-updater`) .dependsOn(`interpreter-dsl`) .dependsOn(`library-manager`) @@ -1288,6 +1298,66 @@ lazy val runtime = (project in file("engine/runtime")) .dependsOn(`docs-generator`) .dependsOn(testkit % Test) +lazy val `runtime-instrument-id-execution` = (project in file("engine/runtime-instrument-id-execution")) + .settings( + inConfig(Compile)(truffleRunOptionsSettings), + instrumentationSettings + ) + .dependsOn(runtime) + +lazy val `runtime-instrument-repl-debugger` = (project in file("engine/runtime-instrument-repl-debugger")) + .settings( + inConfig(Compile)(truffleRunOptionsSettings), + instrumentationSettings + ) + .dependsOn(runtime) + +lazy val `runtime-instrument-runtime-server` = (project in file("engine/runtime-instrument-runtime-server")) + .settings( + inConfig(Compile)(truffleRunOptionsSettings), + instrumentationSettings + ) + .dependsOn(runtime) + +lazy val `runtime-with-instruments` = (project in file("engine/runtime-with-instruments")) + .configs(Benchmark) + .settings( + inConfig(Compile)(truffleRunOptionsSettings), + inConfig(Benchmark)(Defaults.testSettings), + Benchmark / javacOptions --= Seq("-source", frgaalSourceLevel), + Test / javaOptions ++= Seq( + "-Dgraalvm.locatorDisabled=true", + s"--upgrade-module-path=${file("engine/runtime/build-cache/truffle-api.jar").absolutePath}" + ), + Test / fork := true, + Test / envVars ++= distributionEnvironmentOverrides ++ Map( + "ENSO_TEST_DISABLE_IR_CACHE" -> "false" + ), + libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % scalatestVersion % Test, + ), + // Note [Unmanaged Classpath] + Test / unmanagedClasspath += (baseDirectory.value / ".." / ".." / "app" / "gui" / "view" / "graph-editor" / "src" / "builtin" / "visualization" / "native" / "inc"), + assembly / assemblyJarName := "runtime.jar", + assembly / test := {}, + assembly / assemblyOutputPath := file("runtime.jar"), + assembly / assemblyMergeStrategy := { + case PathList("META-INF", file, xs @ _*) if file.endsWith(".DSA") => + MergeStrategy.discard + case PathList("META-INF", file, xs @ _*) if file.endsWith(".SF") => + MergeStrategy.discard + case PathList("META-INF", "MANIFEST.MF", xs @ _*) => + MergeStrategy.discard + case PathList("META-INF", "services", xs @ _*) => + MergeStrategy.concat + case _ => MergeStrategy.first + } + ) + .dependsOn(runtime % "compile->compile;test->test") + .dependsOn(`runtime-instrument-id-execution`) + .dependsOn(`runtime-instrument-repl-debugger`) + .dependsOn(`runtime-instrument-runtime-server`) + /* Note [Unmanaged Classpath] * ~~~~~~~~~~~~~~~~~~~~~~~~~~ * As the definition of the core primitives in `core_definition` is achieved @@ -1346,7 +1416,7 @@ lazy val `engine-runner` = project ) .settings( assembly := assembly - .dependsOn(runtime / assembly) + .dependsOn(`runtime-with-instruments` / assembly) .value ) .dependsOn(`version-output`) diff --git a/docs/infrastructure/sbt.md b/docs/infrastructure/sbt.md index 9e6b9bf479..b8ba11524a 100644 --- a/docs/infrastructure/sbt.md +++ b/docs/infrastructure/sbt.md @@ -85,9 +85,7 @@ dependency of `compileInputs` which runs _strictly before_ actual compilation. To check some invariants _after_ compilation, we can replace the original `Compile / compile` task with a custom one which does its post-compile checks -and returns the result of `(Compile / compile).value`. An example of such a -'patched' compile task is implemented in -[`FixInstrumentsGeneration`](../../project/FixInstrumentsGeneration.scala). +and returns the result of `(Compile / compile).value`. ## Helper Tasks @@ -122,26 +120,17 @@ information is used by `enso --version`. Truffle annotation processor generates a file that registers instruments provided by the runtime. Unfortunately, with incremental compilation, only the changed instruments are recompiled and the annotation processor does not detect -this, so un-changed instruments get un-registered. +this, so un-changed instruments get overwritten. -To fix this, the pre-compile task defined in -[`FixInstrumentsGeneration`](../../project/FixInstrumentsGeneration.scala) -detects changes to instruments and if only one of them should be recompiled, it -forces recompilation of all of them, to ensure consistency. - -For unclear reasons, if this task is attached to -`Compile / compile / compileInputs`, while it runs strictly _before_ -compilation, the deleted class files are not always all recompiled. So instead, -it is attached directly to `Compile / compile`. This technically could allow for -a data race between this task and the actual compilation that happens in -`compileIncremental`, but in practice it seems to be a stable solution. - -Sometimes it is unable to detect the need for recompilation before it takes -place. To help that, there is another task that replaces the default `compile` -task, which executes the default compilation task and after it, verifies the -consistency of instruments files. As it cannot restart compilation, to preserve -consistency it ensures the instruments will be recompiled the next time and -stops the current compilation task, asking the user to restart it. +In the past we had a pre-compile task (see +[FixInstrumentsGeneration](https://github.com/enso-org/enso/blob/8ec2a92b770dea35e47fa9287dbdd1363aabc3c0/project/FixInstrumentsGeneration.scala)) +that detected changes to instruments and if only one of them was to be +recompiled, it forced recompilation of all of them, to ensure consistency. This +workaround helped to avoid later runtime issues but sometimes triggered a +cascade of recompilations, which weren't clear to the end user. Instead, to +avoid overwriting entries in META-INF files, individual services were moved to +separate subprojects and during assembly of uber jar we concatenate meta files +with the same service name. ### Flatbuffers Generation diff --git a/docs/runtime/instruments.md b/docs/runtime/instruments.md index e370a0523e..26a165b4df 100644 --- a/docs/runtime/instruments.md +++ b/docs/runtime/instruments.md @@ -21,8 +21,7 @@ and other kinds of behavior analysis at runtime. ## Naming Conventions Every Instrument must be implemented in Java and have name that ends with -`Instrument`. This requirement is to ensure that the [fix](#fixing-compilation) -described below works. +`Instrument`. ## Fixing Compilation @@ -32,7 +31,6 @@ Unfortunately, when doing an incremental compilation, only the changed files are recompiled and the annotation processor 'forgets' about other instruments that haven't been recompiled, leading to runtime errors about missing instruments. -To fix that, we add the -[`FixInstrumentsGeneration.scala`](../../project/FixInstrumentsGeneration.scala) -task which detects changes to any of the instruments and forces recompilation of -all instruments in the project by removing their classfiles. +To fix that, individual services have to be placed in separate subprojects +depending on `runtime` and aggregated under `runtime-with-instruments`. Later +the META-INF registrations are concatenated in the final uber jar. diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java similarity index 65% rename from engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java rename to engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java index d38bef6f8c..d3efdfda64 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java +++ b/engine/runtime-instrument-id-execution/src/main/java/org/enso/interpreter/instrument/IdExecutionInstrument.java @@ -31,11 +31,9 @@ import java.util.function.Consumer; /** An instrument for getting values from AST-identified expressions. */ @TruffleInstrument.Registration( - id = IdExecutionInstrument.INSTRUMENT_ID, - services = IdExecutionInstrument.class) -public class IdExecutionInstrument extends TruffleInstrument { - public static final String INSTRUMENT_ID = "id-value-extractor"; - + id = IdExecutionService.INSTRUMENT_ID, + services = IdExecutionService.class) +public class IdExecutionInstrument extends TruffleInstrument implements IdExecutionService { private Timer timer; private Env env; @@ -56,225 +54,11 @@ public class IdExecutionInstrument extends TruffleInstrument { * * @param timer the timer to override with */ + @Override public void overrideTimer(Timer timer) { this.timer = timer; } - /** A class for notifications about functions being called in the course of execution. */ - public static class ExpressionCall { - private final UUID expressionId; - private final FunctionCallInstrumentationNode.FunctionCall call; - - /** - * Creates an instance of this class. - * - * @param expressionId the expression id where function call was performed. - * @param call the actual function call data. - */ - public ExpressionCall(UUID expressionId, FunctionCallInstrumentationNode.FunctionCall call) { - this.expressionId = expressionId; - this.call = call; - } - - /** @return the id of the node performing the function call. */ - public UUID getExpressionId() { - return expressionId; - } - - /** @return the function call metadata. */ - public FunctionCallInstrumentationNode.FunctionCall getCall() { - return call; - } - } - - /** A class for notifications about identified expressions' values being computed. */ - public static class ExpressionValue { - private final UUID expressionId; - private final Object value; - private final String type; - private final String cachedType; - private final FunctionCallInfo callInfo; - private final FunctionCallInfo cachedCallInfo; - private final ProfilingInfo[] profilingInfo; - private final boolean wasCached; - - /** - * Creates a new instance of this class. - * - * @param expressionId the id of the expression being computed. - * @param value the value returned by computing the expression. - * @param type the type of the returned value. - * @param cachedType the cached type of the value. - * @param callInfo the function call data. - * @param cachedCallInfo the cached call data. - * @param profilingInfo the profiling information associated with this node - * @param wasCached whether or not the value was obtained from the cache - */ - public ExpressionValue( - UUID expressionId, - Object value, - String type, - String cachedType, - FunctionCallInfo callInfo, - FunctionCallInfo cachedCallInfo, - ProfilingInfo[] profilingInfo, - boolean wasCached) { - this.expressionId = expressionId; - this.value = value; - this.type = type; - this.cachedType = cachedType; - this.callInfo = callInfo; - this.cachedCallInfo = cachedCallInfo; - this.profilingInfo = profilingInfo; - this.wasCached = wasCached; - } - - @Override - public String toString() { - String profilingInfo = Arrays.toString(this.profilingInfo); - return "ExpressionValue{" - + "expressionId=" - + expressionId - + ", value=" - + new MaskedString(value.toString()).applyMasking() - + ", type='" - + type - + '\'' - + ", cachedType='" - + cachedType - + '\'' - + ", callInfo=" - + callInfo - + ", cachedCallInfo=" - + cachedCallInfo - + ", profilingInfo=" - + profilingInfo - + ", wasCached=" - + wasCached - + '}'; - } - - /** @return the id of the expression computed. */ - public UUID getExpressionId() { - return expressionId; - } - - /** @return the type of the returned value. */ - public String getType() { - return type; - } - - /** @return the cached type of the value. */ - public String getCachedType() { - return cachedType; - } - - /** @return the computed value of the expression. */ - public Object getValue() { - return value; - } - - /** @return the function call data. */ - public FunctionCallInfo getCallInfo() { - return callInfo; - } - - /** @return the function call data previously associated with the expression. */ - public FunctionCallInfo getCachedCallInfo() { - return cachedCallInfo; - } - - /** @return the profiling information associated with this expression */ - public ProfilingInfo[] getProfilingInfo() { - return profilingInfo; - } - - /** @return whether or not the expression result was obtained from the cache */ - public boolean wasCached() { - return wasCached; - } - - /** @return {@code true} when the type differs from the cached value. */ - public boolean isTypeChanged() { - return !Objects.equals(type, cachedType); - } - - /** @return {@code true} when the function call differs from the cached value. */ - public boolean isFunctionCallChanged() { - return !Objects.equals(callInfo, cachedCallInfo); - } - } - - /** Information about the function call. */ - public static class FunctionCallInfo { - - private final QualifiedName moduleName; - private final QualifiedName typeName; - private final String functionName; - - /** - * Creates a new instance of this class. - * - * @param call the function call. - */ - public FunctionCallInfo(FunctionCallInstrumentationNode.FunctionCall call) { - RootNode rootNode = call.getFunction().getCallTarget().getRootNode(); - if (rootNode instanceof MethodRootNode) { - MethodRootNode methodNode = (MethodRootNode) rootNode; - moduleName = methodNode.getModuleScope().getModule().getName(); - typeName = methodNode.getAtomConstructor().getQualifiedName(); - functionName = methodNode.getMethodName(); - } else if (rootNode instanceof EnsoRootNode) { - moduleName = ((EnsoRootNode) rootNode).getModuleScope().getModule().getName(); - typeName = null; - functionName = rootNode.getName(); - } else { - moduleName = null; - typeName = null; - functionName = rootNode.getName(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FunctionCallInfo that = (FunctionCallInfo) o; - return Objects.equals(moduleName, that.moduleName) - && Objects.equals(typeName, that.typeName) - && Objects.equals(functionName, that.functionName); - } - - @Override - public int hashCode() { - return Objects.hash(moduleName, typeName, functionName); - } - - @Override - public String toString() { - return moduleName + "::" + typeName + "::" + functionName; - } - - /** @return the name of the module this function was defined in, or null if not available. */ - public QualifiedName getModuleName() { - return moduleName; - } - - /** @return the name of the type this method was defined for, or null if not a method. */ - public QualifiedName getTypeName() { - return typeName; - } - - /** @return the name of this function. */ - public String getFunctionName() { - return functionName; - } - } - /** The listener class used by this instrument. */ private static class IdExecutionEventListener implements ExecutionEventListener { private final CallTarget entryCallTarget; @@ -506,6 +290,7 @@ public class IdExecutionInstrument extends TruffleInstrument { * @param onExceptionalCallback the consumer of the exceptional events. * @return a reference to the attached event listener. */ + @Override public EventBinding bind( CallTarget entryCallTarget, LocationFilter locationFilter, diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java b/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java similarity index 87% rename from engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java rename to engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java index 7f3c50eb36..db975549ed 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java +++ b/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java @@ -20,7 +20,6 @@ import org.enso.interpreter.node.expression.builtin.text.util.ToJavaStringNode; import org.enso.interpreter.node.expression.debug.CaptureResultScopeNode; import org.enso.interpreter.node.expression.debug.EvalNode; import org.enso.interpreter.runtime.Context; -import org.enso.interpreter.runtime.builtin.Builtins; import org.enso.interpreter.runtime.callable.CallerInfo; import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.data.text.Text; @@ -61,8 +60,8 @@ public class ReplDebuggerInstrument extends TruffleInstrument { instrumenter.attachExecutionEventFactory( filter, ctx -> - new ReplExecutionEventNode( - ctx, handler, env.getLogger(ReplExecutionEventNode.class))); + new ReplExecutionEventNodeImpl( + ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class))); } else { env.getLogger(ReplDebuggerInstrument.class) .warning("ReplDebuggerInstrument was initialized, " + "but no client connected"); @@ -85,7 +84,8 @@ public class ReplDebuggerInstrument extends TruffleInstrument { } /** The actual node that's installed as a probe on any node the instrument was launched for. */ - public static class ReplExecutionEventNode extends ExecutionEventNode { + private static class ReplExecutionEventNodeImpl extends ExecutionEventNode + implements ReplExecutionEventNode { private @Child EvalNode evalNode = EvalNode.buildWithResultScopeCapture(); private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build(); @@ -95,7 +95,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument { private DebuggerMessageHandler handler; private TruffleLogger logger; - private ReplExecutionEventNode( + private ReplExecutionEventNodeImpl( EventContext eventContext, DebuggerMessageHandler handler, TruffleLogger logger) { this.eventContext = eventContext; this.handler = handler; @@ -114,11 +114,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument { return currentFrame; } - /** - * Lists all the bindings available in the current execution scope. - * - * @return a map, where keys are variable names and values are current values of variables. - */ + @Override public Map listBindings() { Map flatScope = nodeState.getLastScope().getLocalScope().flattenBindings(); @@ -129,12 +125,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument { return result; } - /** - * Evaluates an arbitrary expression in the current execution context. - * - * @param expression the expression to evaluate - * @return the result of evaluating the expression or an exception that caused failure - */ + @Override public Either evaluate(String expression) { ReplExecutionEventNodeState savedState = nodeState; try { @@ -155,14 +146,11 @@ public class ReplDebuggerInstrument extends TruffleInstrument { } } - /** - * Returns the String representation of the provided object as defined by Enso {@code to_text} - * operation. - */ - public Either showObject(Object o) { + @Override + public Either showObject(Object object) { try { InteropLibrary interop = InteropLibrary.getUncached(); - return new Right<>(interop.asString(interop.toDisplayString(o))); + return new Right<>(interop.asString(interop.toDisplayString(object))); } catch (Exception e) { return new Left<>(e); } @@ -176,15 +164,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument { } } - /** - * Terminates this REPL session. - * - *

The last result of {@link #evaluate(String)} (or {@link Builtins#nothing()} if {@link - * #evaluate(String)} was not called before) will be returned from the instrumented node. - * - *

This function must always be called at the end of REPL session, as otherwise the program - * will never resume. It's forbidden to use this object after exit has been called. - */ + @Override public void exit() { throw eventContext.createUnwind(nodeState.getLastReturn()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java b/engine/runtime-instrument-runtime-server/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java rename to engine/runtime-instrument-runtime-server/src/main/java/org/enso/interpreter/instrument/RuntimeServerInstrument.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java similarity index 98% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java index 47ea29c917..c7943dbf20 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbContext.java +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java @@ -3,7 +3,6 @@ package org.enso.interpreter.epb; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.nodes.Node; -import org.enso.interpreter.Language; import org.enso.interpreter.epb.runtime.GuardedTruffleContext; /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/EpbLanguage.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbParser.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/EpbParser.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbParser.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapExceptionNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ContextRewrapExceptionNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapExceptionNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ContextRewrapExceptionNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ContextRewrapNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ForeignEvalNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ForeignFunctionCallNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java similarity index 92% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java index 92ff666276..8e18ea37b7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/JsForeignNode.java @@ -4,7 +4,6 @@ import com.oracle.truffle.api.dsl.NodeField; import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.interop.*; import com.oracle.truffle.api.library.CachedLibrary; -import org.enso.interpreter.runtime.data.Array; /** A node responsible for performing foreign JS calls. */ @NodeField(name = "foreignFunction", type = Object.class) @@ -34,7 +33,7 @@ public abstract class JsForeignNode extends ForeignFunctionCallNode { if (getArity() - 1 >= 0) System.arraycopy(arguments, 1, positionalArgs, 0, getArity() - 1); try { return interopLibrary.invokeMember( - getForeignFunction(), "apply", arguments[0], new Array(positionalArgs)); + getForeignFunction(), "apply", arguments[0], new ReadOnlyArray(positionalArgs)); } catch (UnsupportedMessageException | UnknownIdentifierException | ArityException diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/PyForeignNode.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/node/RForeignNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/RForeignNode.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/node/RForeignNode.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/RForeignNode.java diff --git a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ReadOnlyArray.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ReadOnlyArray.java new file mode 100644 index 0000000000..08e834a1ae --- /dev/null +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/ReadOnlyArray.java @@ -0,0 +1,96 @@ +package org.enso.interpreter.epb.node; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.InvalidArrayIndexException; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; + +import java.util.Arrays; + +/** + * A primitive boxed array type to be used only in EPB. + * + *

{@link ReadOnlyArray} is essentially a stripped-down, read-only, version of {@link + * org.enso.interpreter.runtime.data.Array}, used for passing arguments. The latter cannot be used + * in EPB because EPB is a dependency of runtime. + */ +@ExportLibrary(InteropLibrary.class) +public class ReadOnlyArray implements TruffleObject { + + private final Object[] items; + + /** + * Creates a new array + * + * @param items the element values + */ + public ReadOnlyArray(Object... items) { + this.items = items; + } + /** + * Marks the object as array-like for Polyglot APIs. + * + * @return {@code true} + */ + @ExportMessage + public boolean hasArrayElements() { + return true; + } + + /** + * Handles reading an element by index through the polyglot API. + * + * @param index the index to read + * @return the element value at the provided index + * @throws InvalidArrayIndexException when the index is out of bounds. + */ + @ExportMessage + public Object readArrayElement(long index) throws InvalidArrayIndexException { + if (index >= items.length || index < 0) { + throw InvalidArrayIndexException.create(index); + } + return items[(int) index]; + } + + /** + * Exposes the size of this collection through the polyglot API. + * + * @return the size of this array + */ + @ExportMessage + long getArraySize() { + return items.length; + } + + /** + * Exposes an index validity check through the polyglot API. + * + * @param index the index to check + * @return {@code true} if the index is valid, {@code false} otherwise. + */ + @ExportMessage + boolean isArrayElementReadable(long index) { + return index < getArraySize() && index >= 0; + } + + @ExportMessage + void writeArrayElement(long index, Object value) { + throw new UnsupportedOperationException("writing unsupoorted in PrimArray"); + } + + @ExportMessage + boolean isArrayElementModifiable(long index) { + return false; + } + + @ExportMessage + boolean isArrayElementInsertable(long index) { + return false; + } + + @Override + public String toString() { + return Arrays.toString(items); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/GuardedTruffleContext.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotExceptionProxy.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotExceptionProxy.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotExceptionProxy.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotExceptionProxy.java diff --git a/engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java similarity index 100% rename from engine/runtime/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java rename to engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/PolyglotProxy.java diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala similarity index 100% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/ReplTest.scala diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeComponentsTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeComponentsTest.scala similarity index 100% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeComponentsTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeComponentsTest.scala diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala similarity index 99% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala index e4585d0c96..20c99d0a1f 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeErrorsTest.scala @@ -81,8 +81,9 @@ class RuntimeErrorsTest .getBindings(LanguageInfo.ID) .invokeMember(MethodNames.TopScope.LEAK_CONTEXT) .asHostObject[org.enso.interpreter.runtime.Context] - languageContext.getLanguage.getIdExecutionInstrument - .overrideTimer(new TestTimer) + languageContext.getLanguage.getIdExecutionService.ifPresent( + _.overrideTimer(new TestTimer) + ); def writeMain(contents: String): File = Files.write(pkg.mainFile.toPath, contents.getBytes).toFile diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeInstrumentTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeInstrumentTest.scala similarity index 99% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeInstrumentTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeInstrumentTest.scala index 42826be77e..5883a7fbc5 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeInstrumentTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeInstrumentTest.scala @@ -74,8 +74,9 @@ class RuntimeInstrumentTest .getBindings(LanguageInfo.ID) .invokeMember(MethodNames.TopScope.LEAK_CONTEXT) .asHostObject[org.enso.interpreter.runtime.Context] - languageContext.getLanguage.getIdExecutionInstrument - .overrideTimer(new TestTimer) + languageContext.getLanguage.getIdExecutionService.ifPresent( + _.overrideTimer(new TestTimer) + ); def writeMain(contents: String): File = Files.write(pkg.mainFile.toPath, contents.getBytes).toFile diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeProjectContextTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeProjectContextTest.scala similarity index 100% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeProjectContextTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeProjectContextTest.scala diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala similarity index 99% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala index d9c6715b76..8e6613d59a 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeServerTest.scala @@ -82,8 +82,9 @@ class RuntimeServerTest .asHostObject[EnsoContext] val info = languageContext.getEnvironment.getPublicLanguages.get(LanguageInfo.ID) - languageContext.getLanguage.getIdExecutionInstrument - .overrideTimer(new TestTimer) + languageContext.getLanguage.getIdExecutionService.ifPresent( + _.overrideTimer(new TestTimer) + ); def writeMain(contents: String): File = Files.write(pkg.mainFile.toPath, contents.getBytes).toFile diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala similarity index 100% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeStdlibTest.scala diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala similarity index 100% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeSuggestionUpdatesTest.scala diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualisationsTest.scala b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala similarity index 99% rename from engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualisationsTest.scala rename to engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index 5546a65ee0..8bdf9cc086 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualisationsTest.scala +++ b/engine/runtime-with-instruments/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -81,8 +81,9 @@ class RuntimeVisualisationsTest .getBindings(LanguageInfo.ID) .invokeMember(MethodNames.TopScope.LEAK_CONTEXT) .asHostObject[EnsoContext] - languageContext.getLanguage.getIdExecutionInstrument - .overrideTimer(new TestTimer) + languageContext.getLanguage.getIdExecutionService.ifPresent( + _.overrideTimer(new TestTimer) + ); def writeMain(contents: String): File = Files.write(pkg.mainFile.toPath, contents.getBytes).toFile diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Language.java b/engine/runtime/src/main/java/org/enso/interpreter/Language.java index ba08889b3a..836f2f44b2 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java @@ -15,7 +15,7 @@ import org.enso.distribution.Environment; import org.enso.distribution.locking.LockManager; import org.enso.distribution.locking.ThreadSafeFileLockManager; import org.enso.interpreter.epb.EpbLanguage; -import org.enso.interpreter.instrument.IdExecutionInstrument; +import org.enso.interpreter.instrument.IdExecutionService; import org.enso.interpreter.instrument.NotificationHandler.Forwarder; import org.enso.interpreter.instrument.NotificationHandler.TextMode$; import org.enso.interpreter.node.ProgramRootNode; @@ -29,6 +29,8 @@ import org.enso.polyglot.LanguageInfo; import org.enso.polyglot.RuntimeOptions; import org.graalvm.options.OptionDescriptors; +import java.util.Optional; + /** * The root of the Enso implementation. * @@ -59,7 +61,7 @@ import org.graalvm.options.OptionDescriptors; IdentifiedTag.class }) public final class Language extends TruffleLanguage { - private IdExecutionInstrument idExecutionInstrument; + private Optional idExecutionInstrument = Optional.empty(); private static final LanguageReference REFERENCE = LanguageReference.create(Language.class); @@ -108,12 +110,15 @@ public final class Language extends TruffleLanguage { Context context = new Context( this, getLanguageHome(), env, notificationHandler, lockManager, distributionManager); - InstrumentInfo idValueListenerInstrument = - env.getInstruments().get(IdExecutionInstrument.INSTRUMENT_ID); - idExecutionInstrument = env.lookup(idValueListenerInstrument, IdExecutionInstrument.class); + idExecutionInstrument = + Optional.ofNullable(env.getInstruments().get(IdExecutionService.INSTRUMENT_ID)) + .map( + idValueListenerInstrument -> + env.lookup(idValueListenerInstrument, IdExecutionService.class)); env.registerService( new ExecutionService( context, idExecutionInstrument, notificationHandler, connectedLockManager)); + return context; } @@ -179,7 +184,7 @@ public final class Language extends TruffleLanguage { } /** @return a reference to the execution instrument */ - public IdExecutionInstrument getIdExecutionInstrument() { + public Optional getIdExecutionService() { return idExecutionInstrument; } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java b/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java new file mode 100644 index 0000000000..a2d42ebc6c --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/instrument/IdExecutionService.java @@ -0,0 +1,271 @@ +package org.enso.interpreter.instrument; + +import com.oracle.truffle.api.CallTarget; +import com.oracle.truffle.api.instrumentation.EventBinding; +import com.oracle.truffle.api.instrumentation.ExecutionEventListener; +import com.oracle.truffle.api.nodes.RootNode; +import java.util.Arrays; +import java.util.Objects; +import java.util.UUID; +import java.util.function.Consumer; +import org.enso.interpreter.instrument.execution.LocationFilter; +import org.enso.interpreter.instrument.execution.Timer; +import org.enso.interpreter.instrument.profiling.ProfilingInfo; +import org.enso.interpreter.node.EnsoRootNode; +import org.enso.interpreter.node.MethodRootNode; +import org.enso.interpreter.node.callable.FunctionCallInstrumentationNode; +import org.enso.logger.masking.MaskedString; +import org.enso.pkg.QualifiedName; + +public interface IdExecutionService { + public static final String INSTRUMENT_ID = "id-value-extractor"; + + /** + * Attach a new listener to observe identified nodes within given function. + * + * @param entryCallTarget the call target being observed. + * @param locationFilter the location filter. + * @param cache the precomputed expression values. + * @param methodCallsCache the storage tracking the executed method calls. + * @param syncState the synchronization state of runtime updates. + * @param nextExecutionItem the next item scheduled for execution. + * @param functionCallCallback the consumer of function call events. + * @param onComputedCallback the consumer of the computed value events. + * @param onCachedCallback the consumer of the cached value events. + * @param onExceptionalCallback the consumer of the exceptional events. + * @return a reference to the attached event listener. + */ + public EventBinding bind( + CallTarget entryCallTarget, + LocationFilter locationFilter, + RuntimeCache cache, + MethodCallsCache methodCallsCache, + UpdatesSynchronizationState syncState, + UUID nextExecutionItem, + Consumer functionCallCallback, + Consumer onComputedCallback, + Consumer onCachedCallback, + Consumer onExceptionalCallback); + + /** + * Override the default nanosecond timer with the specified {@code timer}. + * + * @param timer the timer to override with + */ + void overrideTimer(Timer timer); + + /** A class for notifications about functions being called in the course of execution. */ + public static class ExpressionCall { + private final UUID expressionId; + private final FunctionCallInstrumentationNode.FunctionCall call; + + /** + * Creates an instance of this class. + * + * @param expressionId the expression id where function call was performed. + * @param call the actual function call data. + */ + public ExpressionCall(UUID expressionId, FunctionCallInstrumentationNode.FunctionCall call) { + this.expressionId = expressionId; + this.call = call; + } + + /** @return the id of the node performing the function call. */ + public UUID getExpressionId() { + return expressionId; + } + + /** @return the function call metadata. */ + public FunctionCallInstrumentationNode.FunctionCall getCall() { + return call; + } + } + + /** A class for notifications about identified expressions' values being computed. */ + public static class ExpressionValue { + private final UUID expressionId; + private final Object value; + private final String type; + private final String cachedType; + private final FunctionCallInfo callInfo; + private final FunctionCallInfo cachedCallInfo; + private final ProfilingInfo[] profilingInfo; + private final boolean wasCached; + + /** + * Creates a new instance of this class. + * + * @param expressionId the id of the expression being computed. + * @param value the value returned by computing the expression. + * @param type the type of the returned value. + * @param cachedType the cached type of the value. + * @param callInfo the function call data. + * @param cachedCallInfo the cached call data. + * @param profilingInfo the profiling information associated with this node + * @param wasCached whether or not the value was obtained from the cache + */ + public ExpressionValue( + UUID expressionId, + Object value, + String type, + String cachedType, + FunctionCallInfo callInfo, + FunctionCallInfo cachedCallInfo, + ProfilingInfo[] profilingInfo, + boolean wasCached) { + this.expressionId = expressionId; + this.value = value; + this.type = type; + this.cachedType = cachedType; + this.callInfo = callInfo; + this.cachedCallInfo = cachedCallInfo; + this.profilingInfo = profilingInfo; + this.wasCached = wasCached; + } + + @Override + public String toString() { + String profilingInfo = Arrays.toString(this.profilingInfo); + return "ExpressionValue{" + + "expressionId=" + + expressionId + + ", value=" + + new MaskedString(value.toString()).applyMasking() + + ", type='" + + type + + '\'' + + ", cachedType='" + + cachedType + + '\'' + + ", callInfo=" + + callInfo + + ", cachedCallInfo=" + + cachedCallInfo + + ", profilingInfo=" + + profilingInfo + + ", wasCached=" + + wasCached + + '}'; + } + + /** @return the id of the expression computed. */ + public UUID getExpressionId() { + return expressionId; + } + + /** @return the type of the returned value. */ + public String getType() { + return type; + } + + /** @return the cached type of the value. */ + public String getCachedType() { + return cachedType; + } + + /** @return the computed value of the expression. */ + public Object getValue() { + return value; + } + + /** @return the function call data. */ + public FunctionCallInfo getCallInfo() { + return callInfo; + } + + /** @return the function call data previously associated with the expression. */ + public FunctionCallInfo getCachedCallInfo() { + return cachedCallInfo; + } + + /** @return the profiling information associated with this expression */ + public ProfilingInfo[] getProfilingInfo() { + return profilingInfo; + } + + /** @return whether or not the expression result was obtained from the cache */ + public boolean wasCached() { + return wasCached; + } + + /** @return {@code true} when the type differs from the cached value. */ + public boolean isTypeChanged() { + return !Objects.equals(type, cachedType); + } + + /** @return {@code true} when the function call differs from the cached value. */ + public boolean isFunctionCallChanged() { + return !Objects.equals(callInfo, cachedCallInfo); + } + } + + /** Information about the function call. */ + public static class FunctionCallInfo { + + private final QualifiedName moduleName; + private final QualifiedName typeName; + private final String functionName; + + /** + * Creates a new instance of this class. + * + * @param call the function call. + */ + public FunctionCallInfo(FunctionCallInstrumentationNode.FunctionCall call) { + RootNode rootNode = call.getFunction().getCallTarget().getRootNode(); + if (rootNode instanceof MethodRootNode) { + MethodRootNode methodNode = (MethodRootNode) rootNode; + moduleName = methodNode.getModuleScope().getModule().getName(); + typeName = methodNode.getAtomConstructor().getQualifiedName(); + functionName = methodNode.getMethodName(); + } else if (rootNode instanceof EnsoRootNode) { + moduleName = ((EnsoRootNode) rootNode).getModuleScope().getModule().getName(); + typeName = null; + functionName = rootNode.getName(); + } else { + moduleName = null; + typeName = null; + functionName = rootNode.getName(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FunctionCallInfo that = (FunctionCallInfo) o; + return Objects.equals(moduleName, that.moduleName) + && Objects.equals(typeName, that.typeName) + && Objects.equals(functionName, that.functionName); + } + + @Override + public int hashCode() { + return Objects.hash(moduleName, typeName, functionName); + } + + @Override + public String toString() { + return moduleName + "::" + typeName + "::" + functionName; + } + + /** @return the name of the module this function was defined in, or null if not available. */ + public QualifiedName getModuleName() { + return moduleName; + } + + /** @return the name of the type this method was defined for, or null if not a method. */ + public QualifiedName getTypeName() { + return typeName; + } + + /** @return the name of this function. */ + public String getFunctionName() { + return functionName; + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplExecutionEventNode.java b/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplExecutionEventNode.java new file mode 100644 index 0000000000..0cd72abe33 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/instrument/ReplExecutionEventNode.java @@ -0,0 +1,44 @@ +package org.enso.interpreter.instrument; + +import scala.util.Either; + +import java.util.Map; + +public interface ReplExecutionEventNode { + + /** + * Lists all the bindings available in the current execution scope. + * + * @return a map, where keys are variable names and values are current values of variables. + */ + Map listBindings(); + + /** + * Evaluates an arbitrary expression in the current execution context. + * + * @param expression the expression to evaluate + * @return the result of evaluating the expression or an exception that caused failure + */ + Either evaluate(String expression); + + /** + * Returns the String representation of the provided object as defined by Enso {@code to_text} + * operation. + * + * @param object the object to show + * @return String representation of the provided object or a failure if it cannot be inferred + */ + Either showObject(Object object); + + /** + * Terminates this REPL session. + * + *

The last result of {@link #evaluate(String)} (or {@link + * org.enso.interpreter.runtime.builtin.Builtins#nothing()} if {@link #evaluate(String)} was not + * called before) will be returned from the instrumented node. + * + *

This function must always be called at the end of REPL session, as otherwise the program + * will never resume. It's forbidden to use this object after exit has been called. + */ + void exit(); +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java b/engine/runtime/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java index 389b19af7e..49198ceb4d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/instrument/RuntimeCache.java @@ -11,7 +11,7 @@ public final class RuntimeCache { private final Map> cache = new HashMap<>(); private final Map types = new HashMap<>(); - private final Map calls = new HashMap<>(); + private final Map calls = new HashMap<>(); private Map weights = new HashMap<>(); /** @@ -73,8 +73,8 @@ public final class RuntimeCache { * @param call the function call. * @return the function call that was previously associated with this expression. */ - public IdExecutionInstrument.FunctionCallInfo putCall( - UUID key, IdExecutionInstrument.FunctionCallInfo call) { + public IdExecutionService.FunctionCallInfo putCall( + UUID key, IdExecutionService.FunctionCallInfo call) { if (call == null) { return calls.remove(key); } @@ -82,7 +82,7 @@ public final class RuntimeCache { } /** @return the cached function call associated with the expression. */ - public IdExecutionInstrument.FunctionCallInfo getCall(UUID key) { + public IdExecutionService.FunctionCallInfo getCall(UUID key) { return calls.get(key); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java index 8955156476..87e39fa6d0 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Array.java @@ -20,7 +20,7 @@ import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary; import java.util.Arrays; -/** A primitve boxed array type for use in the runtime. */ +/** A primitive boxed array type for use in the runtime. */ @ExportLibrary(InteropLibrary.class) @ExportLibrary(MethodDispatchLibrary.class) @Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array") diff --git a/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java b/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java index decf3a56d6..d255616223 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/service/ExecutionService.java @@ -18,7 +18,7 @@ import java.util.UUID; import java.util.function.Consumer; import org.enso.compiler.context.ChangesetBuilder; import org.enso.interpreter.instrument.Endpoint; -import org.enso.interpreter.instrument.IdExecutionInstrument; +import org.enso.interpreter.instrument.IdExecutionService; import org.enso.interpreter.instrument.MethodCallsCache; import org.enso.interpreter.instrument.NotificationHandler; import org.enso.interpreter.instrument.RuntimeCache; @@ -54,7 +54,7 @@ import org.enso.text.editing.model; */ public class ExecutionService { private final Context context; - private final IdExecutionInstrument idExecutionInstrument; + private final Optional idExecutionInstrument; private final NotificationHandler.Forwarder notificationForwarder; private final InteropLibrary interopLibrary = InteropLibrary.getFactory().getUncached(); private final TruffleLogger logger = TruffleLogger.getLogger(LanguageInfo.ID); @@ -64,15 +64,15 @@ public class ExecutionService { * Creates a new instance of this service. * * @param context the language context to use. - * @param idExecutionInstrument an instance of the {@link IdExecutionInstrument} to use in the - * course of executions. + * @param idExecutionInstrument optional instance of the {@link IdExecutionService} to use in the + * course of executions * @param notificationForwarder a forwarder of notifications, used to communicate with the user * @param connectedLockManager a connected lock manager (if it is in use) that should be connected * to the language server, or null */ public ExecutionService( Context context, - IdExecutionInstrument idExecutionInstrument, + Optional idExecutionInstrument, NotificationHandler.Forwarder notificationForwarder, ConnectedLockManager connectedLockManager) { this.idExecutionInstrument = idExecutionInstrument; @@ -142,9 +142,9 @@ public class ExecutionService { MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Consumer funCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, + Consumer funCallCallback, + Consumer onComputedCallback, + Consumer onCachedCallback, Consumer onExceptionalCallback) throws ArityException, SourceNotFoundException, UnsupportedMessageException, UnsupportedTypeException { @@ -154,24 +154,26 @@ public class ExecutionService { } LocationFilter locationFilter = LocationFilter.create(module.getIr(), src); - EventBinding listener = - idExecutionInstrument.bind( - call.getFunction().getCallTarget(), - locationFilter, - cache, - methodCallsCache, - syncState, - nextExecutionItem, - funCallCallback, - onComputedCallback, - onCachedCallback, - onExceptionalCallback); + Optional> listener = + idExecutionInstrument.map( + service -> + service.bind( + call.getFunction().getCallTarget(), + locationFilter, + cache, + methodCallsCache, + syncState, + nextExecutionItem, + funCallCallback, + onComputedCallback, + onCachedCallback, + onExceptionalCallback)); Object p = context.getThreadManager().enter(); try { interopLibrary.execute(call); } finally { context.getThreadManager().leave(p); - listener.dispose(); + listener.ifPresent(binding -> binding.dispose()); } } @@ -199,9 +201,9 @@ public class ExecutionService { MethodCallsCache methodCallsCache, UpdatesSynchronizationState syncState, UUID nextExecutionItem, - Consumer funCallCallback, - Consumer onComputedCallback, - Consumer onCachedCallback, + Consumer funCallCallback, + Consumer onComputedCallback, + Consumer onCachedCallback, Consumer onExceptionalCallback) throws ArityException, ConstructorNotFoundException, MethodNotFoundException, ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException { diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/DebuggerMessageHandler.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/DebuggerMessageHandler.scala index 2aaeaf8f63..3d86911484 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/DebuggerMessageHandler.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/DebuggerMessageHandler.scala @@ -3,7 +3,6 @@ package org.enso.interpreter.instrument import java.nio.ByteBuffer import com.oracle.truffle.api.TruffleStackTrace import com.typesafe.scalalogging.Logger -import org.enso.interpreter.instrument.ReplDebuggerInstrument.ReplExecutionEventNode import org.enso.polyglot.debugger._ import org.graalvm.polyglot.io.MessageEndpoint diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala index 37283aa1c1..e2d2df78df 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/instrument/job/ProgramExecutionSupport.scala @@ -7,7 +7,7 @@ import java.util.UUID import cats.implicits._ import com.oracle.truffle.api.exception.AbstractTruffleException -import org.enso.interpreter.instrument.IdExecutionInstrument.{ +import org.enso.interpreter.instrument.IdExecutionService.{ ExpressionCall, ExpressionValue } diff --git a/project/FixInstrumentsGeneration.scala b/project/FixInstrumentsGeneration.scala deleted file mode 100644 index 5f397df217..0000000000 --- a/project/FixInstrumentsGeneration.scala +++ /dev/null @@ -1,140 +0,0 @@ -import sbt.Keys._ -import sbt._ - -object FixInstrumentsGeneration { - - /** This task detects any changes in source files of Instruments and forces - * recompilation of all instruments on any change. This is to ensure that the - * Annotation Processor registers all of the instruments. - * - * Without that fix, incremental compilation would not register unchanged - * instruments, leading to runtime errors. - * - * It should be added as a dependency of Compile / compile / compileInputs. - */ - lazy val preCompileTask = Def.task { - val log = streams.value.log - val root = baseDirectory.value - val classFilesDirectory = (Compile / classDirectory).value - val FragileFiles(fragileSources, fragileClassFiles) = - getFragileFiles(root, classFilesDirectory) - - val fragileSourcesStore = - streams.value.cacheStoreFactory.make("instruments_fixer") - - Tracked.diffInputs(fragileSourcesStore, FileInfo.hash)( - fragileSources.toSet - ) { sourcesDiff: ChangeReport[File] => - if (sourcesDiff.modified.nonEmpty && sourcesDiff.unmodified.nonEmpty) { - val others = - if (sourcesDiff.modified.size >= 2) - s" and ${sourcesDiff.modified.size - 1} others" - else "" - val firstInstrument = sourcesDiff.modified.head - val sourcesMessage = firstInstrument.toString + others - log.warn( - s"Instruments sources ($sourcesMessage) have been changed.\n" + - s"Forcing recompilation of all instruments to maintain " + - s"consistency of generated services files." - ) - - fragileClassFiles.foreach(_.delete()) - } - } - } - - /** This task detects if just a subset of the Instruments has been recompiled - * (right now we did not find a way of detecting this before compilation). If - * the Instrumentation state is detected to be inconsistent, current - * compilation is aborted and classfiles are deleted to ensure that when - * re-run Instrumentation will be brought back to a consistent state. - * - * Without that fix, incremental compilation would not register unchanged - * instruments, leading to runtime errors. - * - * It should replace the default `Compile / compile` task in a project. - */ - lazy val patchedCompile = Def.task { - val compilationResult = (Compile / compile).value - - val log = streams.value.log - val root = baseDirectory.value - val classFilesDirectory = (Compile / classDirectory).value - val FragileFiles(_, fragileClassFiles) = - getFragileFiles(root, classFilesDirectory) - - val fragileClassFilesStore = - streams.value.cacheStoreFactory.make("instruments_classfiles") - - Tracked.diffInputs(fragileClassFilesStore, FileInfo.lastModified)( - fragileClassFiles.toSet - ) { sourcesDiff: ChangeReport[File] => - if (sourcesDiff.modified.nonEmpty && sourcesDiff.unmodified.nonEmpty) { - fragileClassFiles.foreach(_.delete()) - - val projectName = name.value - log.error( - "Truffle Instrumentation is not up to date, " + - "which will lead to runtime errors\n" + - "Fixes have been applied to ensure consistent Instrumentation state, " + - "but compilation has to be triggered again.\n" + - "Please re-run the previous command.\n" + - "(If this for some reason fails, " + - s"please do a clean build of the $projectName project)" - ) - - throw new RuntimeException("Please re-run last command") - } - } - - compilationResult - } - - /** Deletes the compiled instrumentation class files, forcing all of them to - * be recompiled. - * - * Since all instruments are recompiled at once, the service state should be - * consistent as all of them will be re-registered. - */ - def cleanInstruments = Def.task { - val log = streams.value.log - val root = baseDirectory.value - val classFilesDirectory = (Compile / classDirectory).value - val FragileFiles(_, fragileClassFiles) = - getFragileFiles(root, classFilesDirectory) - fragileClassFiles.foreach { file => - if (file.exists()) { - log.info(s"[clean-instruments] Removing $file") - file.delete() - } else { - log.info(s"[clean-instruments] $file was already missing") - } - } - log.info( - "All fragile class files have been deleted. The next compilation " + - "should be forced to recompile all of them, preserving instrumentation " + - "configuration consistency." - ) - } - - private case class FragileFiles(sources: Seq[File], classFiles: Seq[File]) - - private def getFragileFiles( - root: File, - classFilesDirectory: File - ): FragileFiles = { - val fragileSources = - (file(s"$root/src/main/java/") ** "*Instrument.java").get ++ - Seq( - file(s"$root/src/main/java/org/enso/interpreter/Language.java"), - file(s"$root/src/main/java/org/enso/interpreter/epb/EpbLanguage.java") - ) - val fragileClassFiles = - (classFilesDirectory ** "*Instrument.class").get ++ - Seq( - file(s"$classFilesDirectory/org/enso/interpreter/Language.class"), - file(s"$classFilesDirectory/org/enso/interpreter/epb/EpbLanguage.class") - ) - FragileFiles(fragileSources, fragileClassFiles) - } -} diff --git a/project/GatherLicenses.scala b/project/GatherLicenses.scala index 27c6ea39ba..9573dc8443 100644 --- a/project/GatherLicenses.scala +++ b/project/GatherLicenses.scala @@ -146,6 +146,8 @@ object GatherLicenses { val currentInputHash = ReportState.computeInputHash(distributionDescription) if (currentInputHash != reviewState.inputHash) { + log.info("Input hash computed from build.sbt: " + currentInputHash) + log.info("Input hash stored in metadata: " + reviewState.inputHash) warnAndThrow( s"Report for the $name is not up to date - " + s"it seems that some dependencies were added or removed." @@ -180,6 +182,8 @@ object GatherLicenses { val currentOutputHash = ReportState.computeOutputHash(packageDestination) if (currentOutputHash != reportState.outputHash) { + log.info("Output hash computed from build.sbt: " + currentOutputHash) + log.info("Output hash stored in metadata: " + reportState.outputHash) log.error( s"Generated package at $packageDestination seems to be not up-to-date." ) diff --git a/project/src/main/scala/licenses/frontend/DependencyFilter.scala b/project/src/main/scala/licenses/frontend/DependencyFilter.scala index f74aa3ec22..14e960db86 100644 --- a/project/src/main/scala/licenses/frontend/DependencyFilter.scala +++ b/project/src/main/scala/licenses/frontend/DependencyFilter.scala @@ -1,5 +1,6 @@ package src.main.scala.licenses.frontend +import com.typesafe.sbt.license.DepModuleInfo import src.main.scala.licenses.DependencyInformation /** Filters out irrelevant dependencies. @@ -13,5 +14,10 @@ object DependencyFilter { /** Decides if the dependency should be kept for further processing. */ def shouldKeep(dependencyInformation: DependencyInformation): Boolean = - dependencyInformation.moduleInfo.organization != "org.enso" + shouldKeep(dependencyInformation.moduleInfo) + + /** Decides if the module should be kept for further processing. + */ + def shouldKeep(moduleInfo: DepModuleInfo): Boolean = + moduleInfo.organization != "org.enso" } diff --git a/project/src/main/scala/licenses/report/ReportState.scala b/project/src/main/scala/licenses/report/ReportState.scala index bada29b1e7..55b1ac57a8 100644 --- a/project/src/main/scala/licenses/report/ReportState.scala +++ b/project/src/main/scala/licenses/report/ReportState.scala @@ -3,11 +3,8 @@ package src.main.scala.licenses.report import java.security.MessageDigest import sbt.{File, IO, Logger} -import src.main.scala.licenses.{ - DistributionDescription, - FilesHelper, - PortablePath -} +import src.main.scala.licenses.frontend.DependencyFilter +import src.main.scala.licenses.{DistributionDescription, FilesHelper, PortablePath} import scala.util.control.NonFatal @@ -87,7 +84,7 @@ object ReportState { digest.update(sbtComponent.name.getBytes) val dependencies = sbtComponent.licenseReport.licenses.sortBy(_.module.toString) - for (dep <- dependencies) { + for (dep <- dependencies.filter(d => DependencyFilter.shouldKeep(d.module))) { digest.update(dep.module.toString.getBytes) digest.update(dep.license.name.getBytes) } diff --git a/tools/legal-review/engine/report-state b/tools/legal-review/engine/report-state index 2f8e2ed470..46183d097c 100644 --- a/tools/legal-review/engine/report-state +++ b/tools/legal-review/engine/report-state @@ -1,3 +1,3 @@ -E955B4896E24F176DC2DE1E947D0D320B7B688CC43E88BA0AD8BF3B75473356E +F584041D9C65EE3A64897906C912A288C33868416BDA1C24C9892C3C06E597CD 741A2D55D2B3911B0426A0472585A2966D5B9E381B6938EE1209A68E6F76C5AB 0 diff --git a/tools/legal-review/launcher/report-state b/tools/legal-review/launcher/report-state index af6e1b48e3..694046df87 100644 --- a/tools/legal-review/launcher/report-state +++ b/tools/legal-review/launcher/report-state @@ -1,3 +1,3 @@ -DDB2BFB46423B9AC369876C9DDAA6EA0F64FBB0B68C847C8D632F51F16E7D01F +807B8CAF24F78001A73CDFC31DDFA2CB129CDEA07FFCF907ED994FEC7EC4003C FCC80E706112594F80CDC45199FDC1B0A90EF165DC5B5AB9171C5C185EC2B9E2 0 diff --git a/tools/legal-review/project-manager/report-state b/tools/legal-review/project-manager/report-state index 7ee24accc3..04470c1c90 100644 --- a/tools/legal-review/project-manager/report-state +++ b/tools/legal-review/project-manager/report-state @@ -1,3 +1,3 @@ -FE0E365572AF01D432BC54F8586C13CF6FB175F31E6700795D4FE4F944AE6482 +E8BCE903881AE1141DA4E2CAAC72D673680955BDA2AF64B822C62A5AF46DBFE8 0758086E9F188E70B4D212BD957A2DC12AB1D51A56E68F042BC94919B3F2E163 0