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:
Jaroslav Tulach 2022-06-13 16:09:08 +02:00 committed by GitHub
parent fd46e84e8d
commit dc30e44b60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 606 additions and 498 deletions

112
build.sbt
View File

@ -260,6 +260,11 @@ lazy val enso = (project in file("."))
searcher, searcher,
launcher, launcher,
downloader, 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`,
`runtime-version-manager-test`, `runtime-version-manager-test`,
editions, editions,
@ -1156,6 +1161,30 @@ lazy val frgaalJavaCompilerSetting = Seq(
Compile / javacOptions ++= Seq("-source", frgaalSourceLevel) 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")) lazy val runtime = (project in file("engine/runtime"))
.configs(Benchmark) .configs(Benchmark)
.settings( .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), sbt.internal.util.CustomLogManager.excludeMsg("Could not determine source for class ", Level.Warn),
version := ensoVersion, version := ensoVersion,
commands += WithDebugCommand.withDebug, commands += WithDebugCommand.withDebug,
cleanInstruments := FixInstrumentsGeneration.cleanInstruments.value,
inConfig(Compile)(truffleRunOptionsSettings), inConfig(Compile)(truffleRunOptionsSettings),
inConfig(Benchmark)(Defaults.testSettings), inConfig(Benchmark)(Defaults.testSettings),
inConfig(Benchmark)( inConfig(Benchmark)(
@ -1197,14 +1225,9 @@ lazy val runtime = (project in file("engine/runtime"))
"junit" % "junit" % "4.12" % Test, "junit" % "junit" % "4.12" % Test,
"com.novocode" % "junit-interface" % "0.11" % Test exclude ("junit", "junit-dep") "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) Compile / compile / compileInputs := (Compile / compile / compileInputs)
.dependsOn(CopyTruffleJAR.preCompileTask) .dependsOn(CopyTruffleJAR.preCompileTask)
.value, .value,
Compile / compile := FixInstrumentsGeneration.patchedCompile
.dependsOn(FixInstrumentsGeneration.preCompileTask)
.value,
// Note [Classpath Separation] // Note [Classpath Separation]
Test / javaOptions ++= Seq( Test / javaOptions ++= Seq(
"-Dgraalvm.locatorDisabled=true", "-Dgraalvm.locatorDisabled=true",
@ -1258,20 +1281,7 @@ lazy val runtime = (project in file("engine/runtime"))
}.evaluated, }.evaluated,
Benchmark / parallelExecution := false Benchmark / parallelExecution := false
) )
.settings( .dependsOn(`runtime-language-epb`)
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(`edition-updater`) .dependsOn(`edition-updater`)
.dependsOn(`interpreter-dsl`) .dependsOn(`interpreter-dsl`)
.dependsOn(`library-manager`) .dependsOn(`library-manager`)
@ -1288,6 +1298,66 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`docs-generator`) .dependsOn(`docs-generator`)
.dependsOn(testkit % Test) .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] /* Note [Unmanaged Classpath]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~ * ~~~~~~~~~~~~~~~~~~~~~~~~~~
* As the definition of the core primitives in `core_definition` is achieved * As the definition of the core primitives in `core_definition` is achieved
@ -1346,7 +1416,7 @@ lazy val `engine-runner` = project
) )
.settings( .settings(
assembly := assembly assembly := assembly
.dependsOn(runtime / assembly) .dependsOn(`runtime-with-instruments` / assembly)
.value .value
) )
.dependsOn(`version-output`) .dependsOn(`version-output`)

View File

@ -85,9 +85,7 @@ dependency of `compileInputs` which runs _strictly before_ actual compilation.
To check some invariants _after_ compilation, we can replace the original To check some invariants _after_ compilation, we can replace the original
`Compile / compile` task with a custom one which does its post-compile checks `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 and returns the result of `(Compile / compile).value`.
'patched' compile task is implemented in
[`FixInstrumentsGeneration`](../../project/FixInstrumentsGeneration.scala).
## Helper Tasks ## Helper Tasks
@ -122,26 +120,17 @@ information is used by `enso --version`.
Truffle annotation processor generates a file that registers instruments Truffle annotation processor generates a file that registers instruments
provided by the runtime. Unfortunately, with incremental compilation, only the provided by the runtime. Unfortunately, with incremental compilation, only the
changed instruments are recompiled and the annotation processor does not detect 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 In the past we had a pre-compile task (see
[`FixInstrumentsGeneration`](../../project/FixInstrumentsGeneration.scala) [FixInstrumentsGeneration](https://github.com/enso-org/enso/blob/8ec2a92b770dea35e47fa9287dbdd1363aabc3c0/project/FixInstrumentsGeneration.scala))
detects changes to instruments and if only one of them should be recompiled, it that detected changes to instruments and if only one of them was to be
forces recompilation of all of them, to ensure consistency. recompiled, it forced recompilation of all of them, to ensure consistency. This
workaround helped to avoid later runtime issues but sometimes triggered a
For unclear reasons, if this task is attached to cascade of recompilations, which weren't clear to the end user. Instead, to
`Compile / compile / compileInputs`, while it runs strictly _before_ avoid overwriting entries in META-INF files, individual services were moved to
compilation, the deleted class files are not always all recompiled. So instead, separate subprojects and during assembly of uber jar we concatenate meta files
it is attached directly to `Compile / compile`. This technically could allow for with the same service name.
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.
### Flatbuffers Generation ### Flatbuffers Generation

View File

@ -21,8 +21,7 @@ and other kinds of behavior analysis at runtime.
## Naming Conventions ## Naming Conventions
Every Instrument must be implemented in Java and have name that ends with Every Instrument must be implemented in Java and have name that ends with
`Instrument`. This requirement is to ensure that the [fix](#fixing-compilation) `Instrument`.
described below works.
## Fixing Compilation ## 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 recompiled and the annotation processor 'forgets' about other instruments that
haven't been recompiled, leading to runtime errors about missing instruments. haven't been recompiled, leading to runtime errors about missing instruments.
To fix that, we add the To fix that, individual services have to be placed in separate subprojects
[`FixInstrumentsGeneration.scala`](../../project/FixInstrumentsGeneration.scala) depending on `runtime` and aggregated under `runtime-with-instruments`. Later
task which detects changes to any of the instruments and forces recompilation of the META-INF registrations are concatenated in the final uber jar.
all instruments in the project by removing their classfiles.

View File

@ -31,11 +31,9 @@ import java.util.function.Consumer;
/** An instrument for getting values from AST-identified expressions. */ /** An instrument for getting values from AST-identified expressions. */
@TruffleInstrument.Registration( @TruffleInstrument.Registration(
id = IdExecutionInstrument.INSTRUMENT_ID, id = IdExecutionService.INSTRUMENT_ID,
services = IdExecutionInstrument.class) services = IdExecutionService.class)
public class IdExecutionInstrument extends TruffleInstrument { public class IdExecutionInstrument extends TruffleInstrument implements IdExecutionService {
public static final String INSTRUMENT_ID = "id-value-extractor";
private Timer timer; private Timer timer;
private Env env; private Env env;
@ -56,225 +54,11 @@ public class IdExecutionInstrument extends TruffleInstrument {
* *
* @param timer the timer to override with * @param timer the timer to override with
*/ */
@Override
public void overrideTimer(Timer timer) { public void overrideTimer(Timer timer) {
this.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. */ /** The listener class used by this instrument. */
private static class IdExecutionEventListener implements ExecutionEventListener { private static class IdExecutionEventListener implements ExecutionEventListener {
private final CallTarget entryCallTarget; private final CallTarget entryCallTarget;
@ -506,6 +290,7 @@ public class IdExecutionInstrument extends TruffleInstrument {
* @param onExceptionalCallback the consumer of the exceptional events. * @param onExceptionalCallback the consumer of the exceptional events.
* @return a reference to the attached event listener. * @return a reference to the attached event listener.
*/ */
@Override
public EventBinding<ExecutionEventListener> bind( public EventBinding<ExecutionEventListener> bind(
CallTarget entryCallTarget, CallTarget entryCallTarget,
LocationFilter locationFilter, LocationFilter locationFilter,

View File

@ -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.CaptureResultScopeNode;
import org.enso.interpreter.node.expression.debug.EvalNode; import org.enso.interpreter.node.expression.debug.EvalNode;
import org.enso.interpreter.runtime.Context; 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.CallerInfo;
import org.enso.interpreter.runtime.callable.function.Function; import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.data.text.Text; import org.enso.interpreter.runtime.data.text.Text;
@ -61,8 +60,8 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
instrumenter.attachExecutionEventFactory( instrumenter.attachExecutionEventFactory(
filter, filter,
ctx -> ctx ->
new ReplExecutionEventNode( new ReplExecutionEventNodeImpl(
ctx, handler, env.getLogger(ReplExecutionEventNode.class))); ctx, handler, env.getLogger(ReplExecutionEventNodeImpl.class)));
} else { } else {
env.getLogger(ReplDebuggerInstrument.class) env.getLogger(ReplDebuggerInstrument.class)
.warning("ReplDebuggerInstrument was initialized, " + "but no client connected"); .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. */ /** 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 EvalNode evalNode = EvalNode.buildWithResultScopeCapture();
private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build(); private @Child ToJavaStringNode toJavaStringNode = ToJavaStringNode.build();
@ -95,7 +95,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
private DebuggerMessageHandler handler; private DebuggerMessageHandler handler;
private TruffleLogger logger; private TruffleLogger logger;
private ReplExecutionEventNode( private ReplExecutionEventNodeImpl(
EventContext eventContext, DebuggerMessageHandler handler, TruffleLogger logger) { EventContext eventContext, DebuggerMessageHandler handler, TruffleLogger logger) {
this.eventContext = eventContext; this.eventContext = eventContext;
this.handler = handler; this.handler = handler;
@ -114,11 +114,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
return currentFrame; return currentFrame;
} }
/** @Override
* 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.
*/
public Map<String, Object> listBindings() { public Map<String, Object> listBindings() {
Map<String, FramePointer> flatScope = Map<String, FramePointer> flatScope =
nodeState.getLastScope().getLocalScope().flattenBindings(); nodeState.getLastScope().getLocalScope().flattenBindings();
@ -129,12 +125,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
return result; return result;
} }
/** @Override
* 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
*/
public Either<Exception, Object> evaluate(String expression) { public Either<Exception, Object> evaluate(String expression) {
ReplExecutionEventNodeState savedState = nodeState; ReplExecutionEventNodeState savedState = nodeState;
try { try {
@ -155,14 +146,11 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
} }
} }
/** @Override
* Returns the String representation of the provided object as defined by Enso {@code to_text} public Either<Exception, String> showObject(Object object) {
* operation.
*/
public Either<Exception, String> showObject(Object o) {
try { try {
InteropLibrary interop = InteropLibrary.getUncached(); InteropLibrary interop = InteropLibrary.getUncached();
return new Right<>(interop.asString(interop.toDisplayString(o))); return new Right<>(interop.asString(interop.toDisplayString(object)));
} catch (Exception e) { } catch (Exception e) {
return new Left<>(e); return new Left<>(e);
} }
@ -176,15 +164,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
} }
} }
/** @Override
* 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.
*/
public void exit() { public void exit() {
throw eventContext.createUnwind(nodeState.getLastReturn()); throw eventContext.createUnwind(nodeState.getLastReturn());
} }

View File

@ -3,7 +3,6 @@ package org.enso.interpreter.epb;
import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.Language;
import org.enso.interpreter.epb.runtime.GuardedTruffleContext; import org.enso.interpreter.epb.runtime.GuardedTruffleContext;
/** /**

View File

@ -4,7 +4,6 @@ import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization; import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.*; import com.oracle.truffle.api.interop.*;
import com.oracle.truffle.api.library.CachedLibrary; import com.oracle.truffle.api.library.CachedLibrary;
import org.enso.interpreter.runtime.data.Array;
/** A node responsible for performing foreign JS calls. */ /** A node responsible for performing foreign JS calls. */
@NodeField(name = "foreignFunction", type = Object.class) @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); if (getArity() - 1 >= 0) System.arraycopy(arguments, 1, positionalArgs, 0, getArity() - 1);
try { try {
return interopLibrary.invokeMember( return interopLibrary.invokeMember(
getForeignFunction(), "apply", arguments[0], new Array(positionalArgs)); getForeignFunction(), "apply", arguments[0], new ReadOnlyArray(positionalArgs));
} catch (UnsupportedMessageException } catch (UnsupportedMessageException
| UnknownIdentifierException | UnknownIdentifierException
| ArityException | ArityException

View File

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

View File

@ -81,8 +81,9 @@ class RuntimeErrorsTest
.getBindings(LanguageInfo.ID) .getBindings(LanguageInfo.ID)
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT) .invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[org.enso.interpreter.runtime.Context] .asHostObject[org.enso.interpreter.runtime.Context]
languageContext.getLanguage.getIdExecutionInstrument languageContext.getLanguage.getIdExecutionService.ifPresent(
.overrideTimer(new TestTimer) _.overrideTimer(new TestTimer)
);
def writeMain(contents: String): File = def writeMain(contents: String): File =
Files.write(pkg.mainFile.toPath, contents.getBytes).toFile Files.write(pkg.mainFile.toPath, contents.getBytes).toFile

View File

@ -74,8 +74,9 @@ class RuntimeInstrumentTest
.getBindings(LanguageInfo.ID) .getBindings(LanguageInfo.ID)
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT) .invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[org.enso.interpreter.runtime.Context] .asHostObject[org.enso.interpreter.runtime.Context]
languageContext.getLanguage.getIdExecutionInstrument languageContext.getLanguage.getIdExecutionService.ifPresent(
.overrideTimer(new TestTimer) _.overrideTimer(new TestTimer)
);
def writeMain(contents: String): File = def writeMain(contents: String): File =
Files.write(pkg.mainFile.toPath, contents.getBytes).toFile Files.write(pkg.mainFile.toPath, contents.getBytes).toFile

View File

@ -82,8 +82,9 @@ class RuntimeServerTest
.asHostObject[EnsoContext] .asHostObject[EnsoContext]
val info = val info =
languageContext.getEnvironment.getPublicLanguages.get(LanguageInfo.ID) languageContext.getEnvironment.getPublicLanguages.get(LanguageInfo.ID)
languageContext.getLanguage.getIdExecutionInstrument languageContext.getLanguage.getIdExecutionService.ifPresent(
.overrideTimer(new TestTimer) _.overrideTimer(new TestTimer)
);
def writeMain(contents: String): File = def writeMain(contents: String): File =
Files.write(pkg.mainFile.toPath, contents.getBytes).toFile Files.write(pkg.mainFile.toPath, contents.getBytes).toFile

View File

@ -81,8 +81,9 @@ class RuntimeVisualisationsTest
.getBindings(LanguageInfo.ID) .getBindings(LanguageInfo.ID)
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT) .invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[EnsoContext] .asHostObject[EnsoContext]
languageContext.getLanguage.getIdExecutionInstrument languageContext.getLanguage.getIdExecutionService.ifPresent(
.overrideTimer(new TestTimer) _.overrideTimer(new TestTimer)
);
def writeMain(contents: String): File = def writeMain(contents: String): File =
Files.write(pkg.mainFile.toPath, contents.getBytes).toFile Files.write(pkg.mainFile.toPath, contents.getBytes).toFile

View File

@ -15,7 +15,7 @@ import org.enso.distribution.Environment;
import org.enso.distribution.locking.LockManager; import org.enso.distribution.locking.LockManager;
import org.enso.distribution.locking.ThreadSafeFileLockManager; import org.enso.distribution.locking.ThreadSafeFileLockManager;
import org.enso.interpreter.epb.EpbLanguage; 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.Forwarder;
import org.enso.interpreter.instrument.NotificationHandler.TextMode$; import org.enso.interpreter.instrument.NotificationHandler.TextMode$;
import org.enso.interpreter.node.ProgramRootNode; import org.enso.interpreter.node.ProgramRootNode;
@ -29,6 +29,8 @@ import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.RuntimeOptions; import org.enso.polyglot.RuntimeOptions;
import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionDescriptors;
import java.util.Optional;
/** /**
* The root of the Enso implementation. * The root of the Enso implementation.
* *
@ -59,7 +61,7 @@ import org.graalvm.options.OptionDescriptors;
IdentifiedTag.class IdentifiedTag.class
}) })
public final class Language extends TruffleLanguage<Context> { public final class Language extends TruffleLanguage<Context> {
private IdExecutionInstrument idExecutionInstrument; private Optional<IdExecutionService> idExecutionInstrument = Optional.empty();
private static final LanguageReference<Language> REFERENCE = private static final LanguageReference<Language> REFERENCE =
LanguageReference.create(Language.class); LanguageReference.create(Language.class);
@ -108,12 +110,15 @@ public final class Language extends TruffleLanguage<Context> {
Context context = Context context =
new Context( new Context(
this, getLanguageHome(), env, notificationHandler, lockManager, distributionManager); this, getLanguageHome(), env, notificationHandler, lockManager, distributionManager);
InstrumentInfo idValueListenerInstrument = idExecutionInstrument =
env.getInstruments().get(IdExecutionInstrument.INSTRUMENT_ID); Optional.ofNullable(env.getInstruments().get(IdExecutionService.INSTRUMENT_ID))
idExecutionInstrument = env.lookup(idValueListenerInstrument, IdExecutionInstrument.class); .map(
idValueListenerInstrument ->
env.lookup(idValueListenerInstrument, IdExecutionService.class));
env.registerService( env.registerService(
new ExecutionService( new ExecutionService(
context, idExecutionInstrument, notificationHandler, connectedLockManager)); context, idExecutionInstrument, notificationHandler, connectedLockManager));
return context; return context;
} }
@ -179,7 +184,7 @@ public final class Language extends TruffleLanguage<Context> {
} }
/** @return a reference to the execution instrument */ /** @return a reference to the execution instrument */
public IdExecutionInstrument getIdExecutionInstrument() { public Optional<IdExecutionService> getIdExecutionService() {
return idExecutionInstrument; return idExecutionInstrument;
} }
} }

View File

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

View File

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

View File

@ -11,7 +11,7 @@ public final class RuntimeCache {
private final Map<UUID, SoftReference<Object>> cache = new HashMap<>(); private final Map<UUID, SoftReference<Object>> cache = new HashMap<>();
private final Map<UUID, String> types = 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<>(); private Map<UUID, Double> weights = new HashMap<>();
/** /**
@ -73,8 +73,8 @@ public final class RuntimeCache {
* @param call the function call. * @param call the function call.
* @return the function call that was previously associated with this expression. * @return the function call that was previously associated with this expression.
*/ */
public IdExecutionInstrument.FunctionCallInfo putCall( public IdExecutionService.FunctionCallInfo putCall(
UUID key, IdExecutionInstrument.FunctionCallInfo call) { UUID key, IdExecutionService.FunctionCallInfo call) {
if (call == null) { if (call == null) {
return calls.remove(key); return calls.remove(key);
} }
@ -82,7 +82,7 @@ public final class RuntimeCache {
} }
/** @return the cached function call associated with the expression. */ /** @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); return calls.get(key);
} }

View File

@ -20,7 +20,7 @@ import org.enso.interpreter.runtime.library.dispatch.MethodDispatchLibrary;
import java.util.Arrays; 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(InteropLibrary.class)
@ExportLibrary(MethodDispatchLibrary.class) @ExportLibrary(MethodDispatchLibrary.class)
@Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array") @Builtin(pkg = "mutable", stdlibName = "Standard.Base.Data.Array.Array")

View File

@ -18,7 +18,7 @@ import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.enso.compiler.context.ChangesetBuilder; import org.enso.compiler.context.ChangesetBuilder;
import org.enso.interpreter.instrument.Endpoint; 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.MethodCallsCache;
import org.enso.interpreter.instrument.NotificationHandler; import org.enso.interpreter.instrument.NotificationHandler;
import org.enso.interpreter.instrument.RuntimeCache; import org.enso.interpreter.instrument.RuntimeCache;
@ -54,7 +54,7 @@ import org.enso.text.editing.model;
*/ */
public class ExecutionService { public class ExecutionService {
private final Context context; private final Context context;
private final IdExecutionInstrument idExecutionInstrument; private final Optional<IdExecutionService> idExecutionInstrument;
private final NotificationHandler.Forwarder notificationForwarder; private final NotificationHandler.Forwarder notificationForwarder;
private final InteropLibrary interopLibrary = InteropLibrary.getFactory().getUncached(); private final InteropLibrary interopLibrary = InteropLibrary.getFactory().getUncached();
private final TruffleLogger logger = TruffleLogger.getLogger(LanguageInfo.ID); private final TruffleLogger logger = TruffleLogger.getLogger(LanguageInfo.ID);
@ -64,15 +64,15 @@ public class ExecutionService {
* Creates a new instance of this service. * Creates a new instance of this service.
* *
* @param context the language context to use. * @param context the language context to use.
* @param idExecutionInstrument an instance of the {@link IdExecutionInstrument} to use in the * @param idExecutionInstrument optional instance of the {@link IdExecutionService} to use in the
* course of executions. * course of executions
* @param notificationForwarder a forwarder of notifications, used to communicate with the user * @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 * @param connectedLockManager a connected lock manager (if it is in use) that should be connected
* to the language server, or null * to the language server, or null
*/ */
public ExecutionService( public ExecutionService(
Context context, Context context,
IdExecutionInstrument idExecutionInstrument, Optional<IdExecutionService> idExecutionInstrument,
NotificationHandler.Forwarder notificationForwarder, NotificationHandler.Forwarder notificationForwarder,
ConnectedLockManager connectedLockManager) { ConnectedLockManager connectedLockManager) {
this.idExecutionInstrument = idExecutionInstrument; this.idExecutionInstrument = idExecutionInstrument;
@ -142,9 +142,9 @@ public class ExecutionService {
MethodCallsCache methodCallsCache, MethodCallsCache methodCallsCache,
UpdatesSynchronizationState syncState, UpdatesSynchronizationState syncState,
UUID nextExecutionItem, UUID nextExecutionItem,
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback, Consumer<IdExecutionService.ExpressionCall> funCallCallback,
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback, Consumer<IdExecutionService.ExpressionValue> onComputedCallback,
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback, Consumer<IdExecutionService.ExpressionValue> onCachedCallback,
Consumer<Exception> onExceptionalCallback) Consumer<Exception> onExceptionalCallback)
throws ArityException, SourceNotFoundException, UnsupportedMessageException, throws ArityException, SourceNotFoundException, UnsupportedMessageException,
UnsupportedTypeException { UnsupportedTypeException {
@ -154,24 +154,26 @@ public class ExecutionService {
} }
LocationFilter locationFilter = LocationFilter.create(module.getIr(), src); LocationFilter locationFilter = LocationFilter.create(module.getIr(), src);
EventBinding<ExecutionEventListener> listener = Optional<EventBinding<ExecutionEventListener>> listener =
idExecutionInstrument.bind( idExecutionInstrument.map(
call.getFunction().getCallTarget(), service ->
locationFilter, service.bind(
cache, call.getFunction().getCallTarget(),
methodCallsCache, locationFilter,
syncState, cache,
nextExecutionItem, methodCallsCache,
funCallCallback, syncState,
onComputedCallback, nextExecutionItem,
onCachedCallback, funCallCallback,
onExceptionalCallback); onComputedCallback,
onCachedCallback,
onExceptionalCallback));
Object p = context.getThreadManager().enter(); Object p = context.getThreadManager().enter();
try { try {
interopLibrary.execute(call); interopLibrary.execute(call);
} finally { } finally {
context.getThreadManager().leave(p); context.getThreadManager().leave(p);
listener.dispose(); listener.ifPresent(binding -> binding.dispose());
} }
} }
@ -199,9 +201,9 @@ public class ExecutionService {
MethodCallsCache methodCallsCache, MethodCallsCache methodCallsCache,
UpdatesSynchronizationState syncState, UpdatesSynchronizationState syncState,
UUID nextExecutionItem, UUID nextExecutionItem,
Consumer<IdExecutionInstrument.ExpressionCall> funCallCallback, Consumer<IdExecutionService.ExpressionCall> funCallCallback,
Consumer<IdExecutionInstrument.ExpressionValue> onComputedCallback, Consumer<IdExecutionService.ExpressionValue> onComputedCallback,
Consumer<IdExecutionInstrument.ExpressionValue> onCachedCallback, Consumer<IdExecutionService.ExpressionValue> onCachedCallback,
Consumer<Exception> onExceptionalCallback) Consumer<Exception> onExceptionalCallback)
throws ArityException, ConstructorNotFoundException, MethodNotFoundException, throws ArityException, ConstructorNotFoundException, MethodNotFoundException,
ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException { ModuleNotFoundException, UnsupportedMessageException, UnsupportedTypeException {

View File

@ -3,7 +3,6 @@ package org.enso.interpreter.instrument
import java.nio.ByteBuffer import java.nio.ByteBuffer
import com.oracle.truffle.api.TruffleStackTrace import com.oracle.truffle.api.TruffleStackTrace
import com.typesafe.scalalogging.Logger import com.typesafe.scalalogging.Logger
import org.enso.interpreter.instrument.ReplDebuggerInstrument.ReplExecutionEventNode
import org.enso.polyglot.debugger._ import org.enso.polyglot.debugger._
import org.graalvm.polyglot.io.MessageEndpoint import org.graalvm.polyglot.io.MessageEndpoint

View File

@ -7,7 +7,7 @@ import java.util.UUID
import cats.implicits._ import cats.implicits._
import com.oracle.truffle.api.exception.AbstractTruffleException import com.oracle.truffle.api.exception.AbstractTruffleException
import org.enso.interpreter.instrument.IdExecutionInstrument.{ import org.enso.interpreter.instrument.IdExecutionService.{
ExpressionCall, ExpressionCall,
ExpressionValue ExpressionValue
} }

View File

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

View File

@ -146,6 +146,8 @@ object GatherLicenses {
val currentInputHash = val currentInputHash =
ReportState.computeInputHash(distributionDescription) ReportState.computeInputHash(distributionDescription)
if (currentInputHash != reviewState.inputHash) { if (currentInputHash != reviewState.inputHash) {
log.info("Input hash computed from build.sbt: " + currentInputHash)
log.info("Input hash stored in metadata: " + reviewState.inputHash)
warnAndThrow( warnAndThrow(
s"Report for the $name is not up to date - " + s"Report for the $name is not up to date - " +
s"it seems that some dependencies were added or removed." s"it seems that some dependencies were added or removed."
@ -180,6 +182,8 @@ object GatherLicenses {
val currentOutputHash = ReportState.computeOutputHash(packageDestination) val currentOutputHash = ReportState.computeOutputHash(packageDestination)
if (currentOutputHash != reportState.outputHash) { if (currentOutputHash != reportState.outputHash) {
log.info("Output hash computed from build.sbt: " + currentOutputHash)
log.info("Output hash stored in metadata: " + reportState.outputHash)
log.error( log.error(
s"Generated package at $packageDestination seems to be not up-to-date." s"Generated package at $packageDestination seems to be not up-to-date."
) )

View File

@ -1,5 +1,6 @@
package src.main.scala.licenses.frontend package src.main.scala.licenses.frontend
import com.typesafe.sbt.license.DepModuleInfo
import src.main.scala.licenses.DependencyInformation import src.main.scala.licenses.DependencyInformation
/** Filters out irrelevant dependencies. /** Filters out irrelevant dependencies.
@ -13,5 +14,10 @@ object DependencyFilter {
/** Decides if the dependency should be kept for further processing. /** Decides if the dependency should be kept for further processing.
*/ */
def shouldKeep(dependencyInformation: DependencyInformation): Boolean = 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"
} }

View File

@ -3,11 +3,8 @@ package src.main.scala.licenses.report
import java.security.MessageDigest import java.security.MessageDigest
import sbt.{File, IO, Logger} import sbt.{File, IO, Logger}
import src.main.scala.licenses.{ import src.main.scala.licenses.frontend.DependencyFilter
DistributionDescription, import src.main.scala.licenses.{DistributionDescription, FilesHelper, PortablePath}
FilesHelper,
PortablePath
}
import scala.util.control.NonFatal import scala.util.control.NonFatal
@ -87,7 +84,7 @@ object ReportState {
digest.update(sbtComponent.name.getBytes) digest.update(sbtComponent.name.getBytes)
val dependencies = val dependencies =
sbtComponent.licenseReport.licenses.sortBy(_.module.toString) 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.module.toString.getBytes)
digest.update(dep.license.name.getBytes) digest.update(dep.license.name.getBytes)
} }

View File

@ -1,3 +1,3 @@
E955B4896E24F176DC2DE1E947D0D320B7B688CC43E88BA0AD8BF3B75473356E F584041D9C65EE3A64897906C912A288C33868416BDA1C24C9892C3C06E597CD
741A2D55D2B3911B0426A0472585A2966D5B9E381B6938EE1209A68E6F76C5AB 741A2D55D2B3911B0426A0472585A2966D5B9E381B6938EE1209A68E6F76C5AB
0 0

View File

@ -1,3 +1,3 @@
DDB2BFB46423B9AC369876C9DDAA6EA0F64FBB0B68C847C8D632F51F16E7D01F 807B8CAF24F78001A73CDFC31DDFA2CB129CDEA07FFCF907ED994FEC7EC4003C
FCC80E706112594F80CDC45199FDC1B0A90EF165DC5B5AB9171C5C185EC2B9E2 FCC80E706112594F80CDC45199FDC1B0A90EF165DC5B5AB9171C5C185EC2B9E2
0 0

View File

@ -1,3 +1,3 @@
FE0E365572AF01D432BC54F8586C13CF6FB175F31E6700795D4FE4F944AE6482 E8BCE903881AE1141DA4E2CAAC72D673680955BDA2AF64B822C62A5AF46DBFE8
0758086E9F188E70B4D212BD957A2DC12AB1D51A56E68F042BC94919B3F2E163 0758086E9F188E70B4D212BD957A2DC12AB1D51A56E68F042BC94919B3F2E163
0 0