mirror of
https://github.com/enso-org/enso.git
synced 2024-11-25 10:43:02 +03:00
Register instruments/language in their own compilation units to fix the sbt build issues (#3509)
New plan to [fix the `sbt` build](https://www.pivotaltracker.com/n/projects/2539304/stories/182209126) and its annoying: ``` 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)" ) ``` When it is hard to fix `sbt` incremental compilation, let's restructure our project sources so that each `@TruffleInstrument` and `@TruffleLanguage` registration is in individual compilation unit. Each such unit is either going to be compiled or not going to be compiled as a batch - that will eliminate the `sbt` incremental compilation issues without addressing them in `sbt` itself. fa2cf6a33ec4a5b2e3370e1b22c2b5f712286a75 is the first step - it introduces `IdExecutionService` and moves all the `IdExecutionInstrument` API up to that interface. The rest of the `runtime` project then depends only on `IdExecutionService`. Such refactoring allows us to move the `IdExecutionInstrument` out of `runtime` project into independent compilation unit.
This commit is contained in:
parent
fd46e84e8d
commit
dc30e44b60
112
build.sbt
112
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`)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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<ExecutionEventListener> bind(
|
||||
CallTarget entryCallTarget,
|
||||
LocationFilter locationFilter,
|
@ -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<String, Object> listBindings() {
|
||||
Map<String, FramePointer> 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<Exception, Object> 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<Exception, String> showObject(Object o) {
|
||||
@Override
|
||||
public Either<Exception, String> 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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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());
|
||||
}
|
@ -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;
|
||||
|
||||
/**
|
@ -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
|
@ -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.
|
||||
*
|
||||
* <p>{@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);
|
||||
}
|
||||
}
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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<Context> {
|
||||
private IdExecutionInstrument idExecutionInstrument;
|
||||
private Optional<IdExecutionService> idExecutionInstrument = Optional.empty();
|
||||
private static final LanguageReference<Language> REFERENCE =
|
||||
LanguageReference.create(Language.class);
|
||||
|
||||
@ -108,12 +110,15 @@ public final class Language extends TruffleLanguage<Context> {
|
||||
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<Context> {
|
||||
}
|
||||
|
||||
/** @return a reference to the execution instrument */
|
||||
public IdExecutionInstrument getIdExecutionInstrument() {
|
||||
public Optional<IdExecutionService> getIdExecutionService() {
|
||||
return idExecutionInstrument;
|
||||
}
|
||||
}
|
||||
|
@ -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<ExecutionEventListener> bind(
|
||||
CallTarget entryCallTarget,
|
||||
LocationFilter locationFilter,
|
||||
RuntimeCache cache,
|
||||
MethodCallsCache methodCallsCache,
|
||||
UpdatesSynchronizationState syncState,
|
||||
UUID nextExecutionItem,
|
||||
Consumer<ExpressionCall> functionCallCallback,
|
||||
Consumer<ExpressionValue> onComputedCallback,
|
||||
Consumer<ExpressionValue> onCachedCallback,
|
||||
Consumer<Exception> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String, Object> 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<Exception, Object> 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<Exception, String> showObject(Object object);
|
||||
|
||||
/**
|
||||
* Terminates this REPL session.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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();
|
||||
}
|
@ -11,7 +11,7 @@ public final class RuntimeCache {
|
||||
|
||||
private final Map<UUID, SoftReference<Object>> cache = new HashMap<>();
|
||||
private final Map<UUID, String> types = new HashMap<>();
|
||||
private final Map<UUID, IdExecutionInstrument.FunctionCallInfo> calls = new HashMap<>();
|
||||
private final Map<UUID, IdExecutionService.FunctionCallInfo> calls = new HashMap<>();
|
||||
private Map<UUID, Double> 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);
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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<IdExecutionService> 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<IdExecutionService> idExecutionInstrument,
|
||||
NotificationHandler.Forwarder notificationForwarder,
|
||||
ConnectedLockManager connectedLockManager) {
|
||||
this.idExecutionInstrument = idExecutionInstrument;
|
||||
@ -142,9 +142,9 @@ public class ExecutionService {
|
||||
MethodCallsCache methodCallsCache,
|
||||
UpdatesSynchronizationState syncState,
|
||||
UUID nextExecutionItem,
|
||||
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback,
|
||||
Consumer<IdExecutionService.ExpressionCall> funCallCallback,
|
||||
Consumer<IdExecutionService.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionService.ExpressionValue> onCachedCallback,
|
||||
Consumer<Exception> onExceptionalCallback)
|
||||
throws ArityException, SourceNotFoundException, UnsupportedMessageException,
|
||||
UnsupportedTypeException {
|
||||
@ -154,24 +154,26 @@ public class ExecutionService {
|
||||
}
|
||||
LocationFilter locationFilter = LocationFilter.create(module.getIr(), src);
|
||||
|
||||
EventBinding<ExecutionEventListener> listener =
|
||||
idExecutionInstrument.bind(
|
||||
call.getFunction().getCallTarget(),
|
||||
locationFilter,
|
||||
cache,
|
||||
methodCallsCache,
|
||||
syncState,
|
||||
nextExecutionItem,
|
||||
funCallCallback,
|
||||
onComputedCallback,
|
||||
onCachedCallback,
|
||||
onExceptionalCallback);
|
||||
Optional<EventBinding<ExecutionEventListener>> 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<IdExecutionInstrument.ExpressionCall> funCallCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback,
|
||||
Consumer<IdExecutionService.ExpressionCall> funCallCallback,
|
||||
Consumer<IdExecutionService.ExpressionValue> onComputedCallback,
|
||||
Consumer<IdExecutionService.ExpressionValue> onCachedCallback,
|
||||
Consumer<Exception> onExceptionalCallback)
|
||||
throws ArityException, ConstructorNotFoundException, MethodNotFoundException,
|
||||
ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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."
|
||||
)
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
E955B4896E24F176DC2DE1E947D0D320B7B688CC43E88BA0AD8BF3B75473356E
|
||||
F584041D9C65EE3A64897906C912A288C33868416BDA1C24C9892C3C06E597CD
|
||||
741A2D55D2B3911B0426A0472585A2966D5B9E381B6938EE1209A68E6F76C5AB
|
||||
0
|
||||
|
@ -1,3 +1,3 @@
|
||||
DDB2BFB46423B9AC369876C9DDAA6EA0F64FBB0B68C847C8D632F51F16E7D01F
|
||||
807B8CAF24F78001A73CDFC31DDFA2CB129CDEA07FFCF907ED994FEC7EC4003C
|
||||
FCC80E706112594F80CDC45199FDC1B0A90EF165DC5B5AB9171C5C185EC2B9E2
|
||||
0
|
||||
|
@ -1,3 +1,3 @@
|
||||
FE0E365572AF01D432BC54F8586C13CF6FB175F31E6700795D4FE4F944AE6482
|
||||
E8BCE903881AE1141DA4E2CAAC72D673680955BDA2AF64B822C62A5AF46DBFE8
|
||||
0758086E9F188E70B4D212BD957A2DC12AB1D51A56E68F042BC94919B3F2E163
|
||||
0
|
||||
|
Loading…
Reference in New Issue
Block a user