Upgrade to GraalVM 22.3.0 (#3663)

Upgrading to GraalVM 22.3.0.

# Important Notes
- Removed all deprecated `FrameSlot`, and replaced them with frame indexes - integers.
- Add more information to `AliasAnalysis` so that it also gathers these indexes.
- Add quick build mode option to `native-image` as default for non-release builds
- `graaljs` and `native-image` should now be downloaded via `gu` automatically, as dependencies.
- Remove `engine-runner-native` project - native image is now build straight from `engine-runner`.
- We used to have `engine-runner-native` without `sqldf` in classpath as a workaround for an internal native image bug.
- Fixed chrome inspector integration, such that it shows values of local variables both for current stack frame and caller stack frames.
- There are still many issues with the debugging in general, for example, when there is a polyglot value among local variables, a `NullPointerException` is thrown and no values are displayed.
- Removed some deprecated `native-image` options
- Remove some deprecated Truffle API method calls.
This commit is contained in:
Jaroslav Tulach 2022-11-23 15:30:48 +01:00 committed by GitHub
parent deb670785c
commit 402ebb2f8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 1003 additions and 299 deletions

View File

@ -8,7 +8,7 @@ on:
env:
# Please ensure that this is in sync with graalVersion in build.sbt
graalVersion: 21.3.0
graalVersion: 22.3.0
# Please ensure that this is in sync with javaVersion in build.sbt
javaVersion: 11
# Please ensure that this is in sync with project/build.properties

View File

@ -6,7 +6,7 @@ on:
env:
# Please ensure that this is in sync with graalVersion in build.sbt
graalVersion: 21.3.0
graalVersion: 22.3.0
# Please ensure that this is in sync with javaVersion in build.sbt
javaVersion: 11
# Please ensure that this is in sync with project/build.properties

View File

@ -444,6 +444,8 @@
- [Accept Array-like object seamlessly in builtins][3817]
- [Initialize Builtins at Native Image build time][3821]
- [Split Atom suggestion entry to Type and Constructor][3835]
- [Update to GraalVM 22.3.0][3663]
- [Connecting IGV 4 Enso with Engine sources][3810]
- [Add the `Self` keyword referring to current type][3844]
- [Support VCS for projects in Language Server][3851]
- [Support multiple exports of the same module][3897]
@ -509,6 +511,8 @@
[3821]: https://github.com/enso-org/enso/pull/3821
[3828]: https://github.com/enso-org/enso/pull/3828
[3835]: https://github.com/enso-org/enso/pull/3835
[3663]: https://github.com/enso-org/enso/pull/3663
[3810]: https://github.com/enso-org/enso/pull/3810
[3844]: https://github.com/enso-org/enso/pull/3844
[3851]: https://github.com/enso-org/enso/pull/3851
[3897]: https://github.com/enso-org/enso/pull/3897

119
build.sbt
View File

@ -7,6 +7,7 @@ import sbt.addCompilerPlugin
import sbt.complete.DefaultParsers._
import sbt.complete.Parser
import sbt.nio.file.FileTreeView
import sbt.internal.util.ManagedLogger
import sbtcrossproject.CrossPlugin.autoImport.{crossProject, CrossType}
import src.main.scala.licenses.{
DistributionDescription,
@ -20,7 +21,7 @@ import java.io.File
// ============================================================================
val scalacVersion = "2.13.8"
val graalVersion = "21.3.0"
val graalVersion = "22.3.0"
val javaVersion = "11"
val defaultDevEnsoVersion = "0.0.0-dev"
val ensoVersion = sys.env.getOrElse(
@ -983,6 +984,7 @@ lazy val `project-manager` = (project in file("lib/scala/project-manager"))
initializeAtRuntime = Seq("scala.util.Random")
)
.dependsOn(VerifyReflectionSetup.run)
.dependsOn(installNativeImage)
.dependsOn(assembly)
.value,
buildNativeImage := NativeImage
@ -1303,6 +1305,79 @@ lazy val `runtime-language-epb` =
instrumentationSettings
)
/**
* Runs gu (GraalVM updater) command with given `args`.
* For example `runGu(Seq("install", "js"))`.
* @param logger Logger for the `gu` command.
* @param args Arguments for the `gu` command.
*/
def runGu(logger: ManagedLogger, args: Seq[String]): String = {
val javaHome = new File(
System.getProperty("java.home")
)
val os = {
if (Platform.isLinux) DistributionPackage.OS.Linux
else if (Platform.isMacOS) DistributionPackage.OS.MacOS
else if (Platform.isWindows) DistributionPackage.OS.Windows
else throw new RuntimeException("Unknown platform")
}
packageBuilder.gu(
logger,
os,
javaHome,
args:_*
)
}
def installedGuComponents(logger: ManagedLogger): Seq[String] = {
val componentList = runGu(
logger,
Seq("list")
)
val components = componentList
.linesIterator
.drop(2)
.map { line =>
line
.split(" ")
.head
}
.toList
logger.debug(s"Installed GU components = $components")
if (!components.contains("graalvm")) {
throw new RuntimeException(s"graalvm components is not in $components")
}
components
}
lazy val installGraalJs = taskKey[Unit]("Install graaljs GraalVM component")
ThisBuild / installGraalJs := {
val logger = streams.value.log
if (!installedGuComponents(logger).contains("js")) {
logger.info("Installing js GraalVM component")
runGu(
logger,
Seq("install", "js")
)
}
}
lazy val installNativeImage = taskKey[Unit]("Install native-image GraalVM component")
ThisBuild / installNativeImage := {
val logger = streams.value.log
if (!installedGuComponents(logger).contains("native-image")) {
logger.info("Installing native-image GraalVM component")
runGu(
logger,
Seq("install", "native-image")
)
}
}
ThisBuild / installNativeImage := {
(ThisBuild / installNativeImage).result.value
}
lazy val runtime = (project in file("engine/runtime"))
.configs(Benchmark)
.settings(
@ -1381,6 +1456,7 @@ lazy val runtime = (project in file("engine/runtime"))
.settings(
(Compile / compile) := (Compile / compile)
.dependsOn(Def.task { (Compile / sourceManaged).value.mkdirs })
.dependsOn(installGraalJs)
.value
)
.settings(
@ -1555,34 +1631,8 @@ lazy val `engine-runner` = project
.settings(
assembly := assembly
.dependsOn(`runtime-with-instruments` / assembly)
.value
)
.dependsOn(`version-output`)
.dependsOn(pkg)
.dependsOn(cli)
.dependsOn(`library-manager`)
.dependsOn(`language-server`)
.dependsOn(`polyglot-api`)
.dependsOn(`logging-service`)
// A workaround for https://github.com/oracle/graal/issues/4200 until we upgrade to GraalVM 22.x.
// sqlite-jdbc jar is problematic and had to be exluded for the purposes of building a native
// image of the runner.
lazy val `engine-runner-native` = project
.in(file("engine/runner-native"))
.settings(
assembly / assemblyExcludedJars := {
val cp = (assembly / fullClasspath).value
(assembly / assemblyExcludedJars).value ++
cp.filter(_.data.getName.startsWith("sqlite-jdbc"))
},
assembly / mainClass := (`engine-runner` / assembly / mainClass).value,
assembly / assemblyMergeStrategy := (`engine-runner` / assembly / assemblyMergeStrategy).value,
assembly / assemblyJarName := "runner-native.jar",
assembly / assemblyOutputPath := file("runner-native.jar"),
assembly := assembly
.dependsOn(`engine-runner` / assembly)
.value,
rebuildNativeImage := NativeImage
.buildNativeImage(
"runner",
@ -1590,7 +1640,6 @@ lazy val `engine-runner-native` = project
additionalOptions = Seq(
"-Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.NoOpLog",
"-H:IncludeResources=.*Main.enso$",
"--allow-incomplete-classpath",
"--macro:truffle",
"--language:js",
// "-g",
@ -1606,9 +1655,10 @@ lazy val `engine-runner-native` = project
"io.methvin.watchservice.jna.CarbonAPI"
)
)
.dependsOn(installNativeImage)
.dependsOn(assembly)
.dependsOn(VerifyReflectionSetup.run)
.value,
buildNativeImage := NativeImage
.incrementalNativeImageBuild(
rebuildNativeImage,
@ -1616,7 +1666,13 @@ lazy val `engine-runner-native` = project
)
.value
)
.dependsOn(`engine-runner`)
.dependsOn(`version-output`)
.dependsOn(pkg)
.dependsOn(cli)
.dependsOn(`library-manager`)
.dependsOn(`language-server`)
.dependsOn(`polyglot-api`)
.dependsOn(`logging-service`)
lazy val launcher = project
.in(file("engine/launcher"))
@ -1646,6 +1702,7 @@ lazy val launcher = project
"org.enso.loggingservice.WSLoggerManager$"
)
)
.dependsOn(installNativeImage)
.dependsOn(assembly)
.dependsOn(VerifyReflectionSetup.run)
.value,

View File

@ -269,7 +269,7 @@ impl RunContext {
ret.packages.engine = Some(self.paths.engine.clone());
}
if build_native_runner {
tasks.push("engine-runner-native/buildNativeImage");
tasks.push("engine-runner/buildNativeImage");
}
if TARGET_OS != OS::Windows {

View File

@ -134,7 +134,7 @@ mod tests {
#[ignore]
async fn test_is_enabled() -> Result {
setup_logging()?;
let graal_version = Version::parse("21.3.0").unwrap();
let graal_version = Version::parse("22.3.0").unwrap();
let java_version = java::LanguageVersion(11);
let os = TARGET_OS;
let arch = Arch::X86_64;
@ -152,14 +152,14 @@ mod tests {
/// Check that we correctly recognize both the GraalVM version and the Java version.
#[test]
fn version_recognize() {
let version_string = r"openjdk 11.0.13 2021-10-19
OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 11.0.13+7-jvmci-21.3-b05, mixed mode, sharing)";
let version_string = r"openjdk 11.0.17 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 11.0.17+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 11.0.17+8-jvmci-22.3-b08, mixed mode, sharing)";
let found_graal = graal_version_from_version_string(version_string).unwrap();
assert_eq!(found_graal, Version::new(21, 3, 0));
assert_eq!(found_graal, Version::new(22, 3, 0));
let found_java = Java.parse_version(version_string).unwrap();
assert_eq!(found_java, Version::new(11, 0, 13));
assert_eq!(found_java, Version::new(11, 0, 17));
}
}

View File

@ -529,6 +529,12 @@ enso --run test/Geo_Tests
enso --run test/Table_Tests
```
Or to run just a single test (e.g., `Duration_Spec.enso`):
```bash
enso --in-project test/Tests --run test/Tests/src/Data/Time/Duration_Spec.enso
```
The Database tests will by default only test the SQLite backend, to test other
backends see
[`test/Table_Tests/src/Database/README.md`](../test/Table_Tests/src/Database/README.md)

View File

@ -206,7 +206,7 @@ state. Limitations are currently mostly due to
of stdlib components. To generate the Native Image for runner simply execute
```
sbt> engine-runner-native/buildNativeImage
sbt> engine-runner/buildNativeImage
```
and execute the binary on a sample factorial test program

View File

@ -35,8 +35,9 @@ to perform the following tasks:
configuration. This is both a version number and (if it is changed), the
associated version of Java.
- Change the expected GraalVM version in the
[`scala.yml`](../../.github/workflows/scala.yml) and
[`release.yml`](../../.github/workflows/release.yml) workflows.
[`release-publish-edition`](../../.github/workflows/release-publish-edition.yml)
workflow.
- Change the base image in the [`Dockerfile`](../../tools/ci/docker/Dockerfile)
to contain the correct GraalVM version.
- Just to be sure, search for the version regex in all the files in the repo.
- Ensure that all deprecations have been handled.

View File

@ -1,7 +1,8 @@
package org.enso.languageserver.io
import java.io.OutputStream
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary
import java.io.OutputStream
import org.enso.languageserver.io.ObservableOutputStream.OutputObserver
/** An observable output stream of bytes. It accepts output bytes
@ -15,11 +16,13 @@ class ObservableOutputStream extends OutputStream {
private var observers = Set.empty[OutputObserver]
/** @inheritdoc */
@TruffleBoundary
override def write(byte: Int): Unit = lock.synchronized {
notify(Array[Byte](byte.toByte))
}
/** @inheritdoc */
@TruffleBoundary
override def write(bytes: Array[Byte]): Unit = lock.synchronized {
if (bytes.length > 0) {
notify(bytes)
@ -27,6 +30,7 @@ class ObservableOutputStream extends OutputStream {
}
/** @inheritdoc */
@TruffleBoundary
override def write(bytes: Array[Byte], off: Int, len: Int): Unit =
lock.synchronized {
if (len > 0) {
@ -52,6 +56,7 @@ class ObservableOutputStream extends OutputStream {
observers -= observer
}
@TruffleBoundary
protected def notify(output: Array[Byte]): Unit = {
observers foreach { _.update(output) }
}

View File

@ -105,7 +105,7 @@ public class ReplDebuggerInstrument extends TruffleInstrument {
}
private Object getValue(MaterializedFrame frame, FramePointer ptr) {
return getProperFrame(frame, ptr).getValue(ptr.getFrameSlot());
return getProperFrame(frame, ptr).getValue(ptr.getFrameSlotIdx());
}
private MaterializedFrame getProperFrame(MaterializedFrame frame, FramePointer ptr) {

View File

@ -39,7 +39,12 @@ public class EpbContext {
if (!isInner) {
innerContext =
new GuardedTruffleContext(
env.newContextBuilder().config(INNER_OPTION, "yes").build(), true);
env.newInnerContextBuilder()
.initializeCreatorContext(true)
.inheritAllAccess(true)
.config(INNER_OPTION, "yes")
.build(),
true);
}
}

View File

@ -52,8 +52,8 @@ public class EpbLanguage extends TruffleLanguage<EpbContext> {
@Override
protected CallTarget parse(ParsingRequest request) {
EpbParser.Result code = EpbParser.parse(request.getSource());
return Truffle.getRuntime()
.createCallTarget(ForeignEvalNode.build(this, code, request.getArgumentNames()));
ForeignEvalNode foreignEvalNode = ForeignEvalNode.build(this, code, request.getArgumentNames());
return foreignEvalNode.getCallTarget();
}
@Override

View File

@ -1,10 +1,12 @@
package org.enso.interpreter.epb.node;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.strings.TruffleString;
@GenerateUncached
@ReportPolymorphism
@ -74,6 +76,12 @@ public abstract class CoercePrimitiveNode extends Node {
return s;
}
@Specialization
String doTruffleString(
TruffleString truffleString, @Cached TruffleString.ToJavaStringNode toJavaStringNode) {
return toJavaStringNode.execute(truffleString);
}
@Specialization
Object doNonPrimitive(TruffleObject value) {
return value;

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.epb.runtime;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
@ -49,6 +50,7 @@ public class ForeignParsingException extends AbstractTruffleException {
}
@ExportMessage
@TruffleBoundary
String toDisplayString(boolean hasSideEffects) {
return "ForeignParsingException: '" + message + "'";
}

View File

@ -1,6 +1,9 @@
package org.enso.interpreter;
import com.oracle.truffle.api.*;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.debug.DebuggerTags;
import com.oracle.truffle.api.instrumentation.ProvidedTags;
import com.oracle.truffle.api.instrumentation.StandardTags;
@ -59,6 +62,7 @@ import org.graalvm.options.OptionType;
StandardTags.ExpressionTag.class,
StandardTags.StatementTag.class,
StandardTags.RootTag.class,
StandardTags.RootBodyTag.class,
StandardTags.TryBlockTag.class,
IdentifiedTag.class,
AvoidIdInstrumentationTag.class,
@ -167,7 +171,7 @@ public final class Language extends TruffleLanguage<Context> {
@Override
protected CallTarget parse(ParsingRequest request) {
RootNode root = ProgramRootNode.build(this, request.getSource());
return Truffle.getRuntime().createCallTarget(root);
return root.getCallTarget();
}
@Option(
@ -189,7 +193,7 @@ public final class Language extends TruffleLanguage<Context> {
}
/**
* Returns the top scope of the requested contenxt.
* Returns the top scope of the requested context.
*
* @param context the context holding the top scope
* @return the language's top scope

View File

@ -3,7 +3,6 @@ package org.enso.interpreter.node;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeInfo;

View File

@ -2,12 +2,10 @@ package org.enso.interpreter.node;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.ReportPolymorphism;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.source.SourceSection;
import org.enso.interpreter.Language;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.scope.LocalScope;
import org.enso.interpreter.runtime.scope.ModuleScope;

View File

@ -1,7 +1,5 @@
package org.enso.interpreter.node;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;

View File

@ -1,25 +1,31 @@
package org.enso.interpreter.node;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.NodeLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.NodeInfo;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.source.SourceSection;
import java.util.UUID;
import org.enso.interpreter.runtime.builtin.Builtins;
import org.enso.interpreter.runtime.callable.atom.Atom;
import org.enso.interpreter.runtime.callable.atom.AtomConstructor;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.runtime.type.TypesGen;
import java.util.UUID;
import org.enso.interpreter.runtime.tag.Patchable;
import org.enso.interpreter.runtime.scope.DebugLocalScope;
import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
import org.enso.interpreter.runtime.tag.IdentifiedTag;
import org.enso.interpreter.runtime.tag.Patchable;
import org.enso.interpreter.runtime.type.TypesGen;
/**
* A base class for all Enso expressions.
@ -32,12 +38,22 @@ import org.enso.interpreter.runtime.tag.AvoidIdInstrumentationTag;
* executeGeneric} method for various scenarios in order to improve performance.
*/
@NodeInfo(shortName = "EnsoExpression", description = "The base node for all enso expressions.")
@ExportLibrary(NodeLibrary.class)
@GenerateWrapper
public abstract class ExpressionNode extends BaseNode implements InstrumentableNode {
private @CompilerDirectives.CompilationFinal int sourceStartIndex;
private @CompilerDirectives.CompilationFinal int sourceLength;
private @CompilerDirectives.CompilationFinal UUID id = null;
public static boolean isWrapper(ExpressionNode node) {
return node instanceof ExpressionNodeWrapper;
}
public static ExpressionNode unwrapDelegate(ExpressionNode wrapperNode) {
assert isWrapper(wrapperNode);
return ((ExpressionNodeWrapper) wrapperNode).getDelegateNode();
}
/** Creates a new instance of this node. */
public ExpressionNode() {
sourceLength = EnsoRootNode.NO_SOURCE;
@ -63,7 +79,11 @@ public abstract class ExpressionNode extends BaseNode implements InstrumentableN
*/
@Override
public SourceSection getSourceSection() {
return EnsoRootNode.findSourceSection(getRootNode(), sourceStartIndex, sourceLength);
if (this instanceof ExpressionNodeWrapper wrapper) {
return wrapper.getDelegateNode().getSourceSection();
} else {
return EnsoRootNode.findSourceSection(getRootNode(), sourceStartIndex, sourceLength);
}
}
/**
@ -188,4 +208,19 @@ public abstract class ExpressionNode extends BaseNode implements InstrumentableN
public WrapperNode createWrapper(ProbeNode probe) {
return new ExpressionNodeWrapper(this, probe);
}
@ExportMessage
boolean hasScope(Frame frame) {
return isInstrumentable();
}
@ExportMessage
Object getScope(Frame frame, boolean onEnter) {
RootNode rootNode = getRootNode();
if (!isInstrumentable() || rootNode == null) {
return null;
} else {
return DebugLocalScope.createFromFrame((EnsoRootNode) rootNode, frame.materialize());
}
}
}

View File

@ -1,6 +1,5 @@
package org.enso.interpreter.node.callable;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.NodeInfo;

View File

@ -66,7 +66,9 @@ public abstract class CaptureCallerInfoNode extends Node {
@CompilerDirectives.TruffleBoundary
ScopeInfo buildUncachedScopeInfo() {
RootCallTarget ct = (RootCallTarget) Truffle.getRuntime().getCurrentFrame().getCallTarget();
RootCallTarget ct =
Truffle.getRuntime()
.iterateFrames(frameInstance -> (RootCallTarget) frameInstance.getCallTarget());
EnsoRootNode rootNode = (EnsoRootNode) ct.getRootNode();
return new ScopeInfo(rootNode.getLocalScope(), rootNode.getModuleScope());
}

View File

@ -6,9 +6,7 @@ import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.GenerateUncached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
@ -105,22 +103,23 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
* {@link RepeatingNode}.
*/
public static final class RepeatedCallNode extends Node implements RepeatingNode {
private final FrameSlot resultSlot;
private final FrameSlot functionSlot;
private final FrameSlot argsSlot;
private final FrameSlot stateSlot;
private final FrameSlot callerInfoSlot;
private final int resultSlotIdx;
private final int functionSlotIdx;
private final int argsSlotIdx;
private final int stateSlotIdx;
private final int callerInfoSlotIdx;
private final FrameDescriptor descriptor;
@Child private ExecuteCallNode dispatchNode;
/** Creates a new node used for repeating a call. */
public RepeatedCallNode() {
descriptor = new FrameDescriptor();
functionSlot = descriptor.findOrAddFrameSlot("<TCO Function>", FrameSlotKind.Object);
resultSlot = descriptor.findOrAddFrameSlot("<TCO Result>", FrameSlotKind.Object);
argsSlot = descriptor.findOrAddFrameSlot("<TCO Arguments>", FrameSlotKind.Object);
stateSlot = descriptor.findOrAddFrameSlot("<TCO State>", FrameSlotKind.Object);
callerInfoSlot = descriptor.findOrAddFrameSlot("<TCO Caller Info>", FrameSlotKind.Object);
var descrBuilder = FrameDescriptor.newBuilder();
functionSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "<TCO Function>", null);
resultSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "<TCO Result>", null);
argsSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "<TCO Arguments>", null);
stateSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "<TCO State>", null);
callerInfoSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "<TCO Caller Info>", null);
descriptor = descrBuilder.build();
dispatchNode = ExecuteCallNodeGen.create();
}
@ -138,13 +137,13 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
*/
private void setNextCall(
VirtualFrame frame, Function function, CallerInfo callerInfo, Object[] arguments) {
frame.setObject(functionSlot, function);
frame.setObject(callerInfoSlot, callerInfo);
frame.setObject(argsSlot, arguments);
frame.setObject(functionSlotIdx, function);
frame.setObject(callerInfoSlotIdx, callerInfo);
frame.setObject(argsSlotIdx, arguments);
}
private void setState(VirtualFrame frame, State state) {
frame.setObject(stateSlot, state);
frame.setObject(stateSlotIdx, state);
}
/**
@ -154,12 +153,12 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
* @return the result of execution in {@code frame}
*/
public Object getResult(VirtualFrame frame) {
return FrameUtil.getObjectSafe(frame, resultSlot);
return frame.getObject(resultSlotIdx);
}
private CallerInfo getCallerInfo(VirtualFrame frame) {
CallerInfo result = (CallerInfo) FrameUtil.getObjectSafe(frame, callerInfoSlot);
frame.setObject(callerInfoSlot, null);
CallerInfo result = (CallerInfo) frame.getObject(callerInfoSlotIdx);
frame.setObject(callerInfoSlotIdx, null);
return result;
}
@ -170,8 +169,8 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
* @return the function to be executed next in the loop
*/
public Function getNextFunction(VirtualFrame frame) {
Object result = FrameUtil.getObjectSafe(frame, functionSlot);
frame.setObject(functionSlot, null);
Object result = frame.getObject(functionSlotIdx);
frame.setObject(functionSlotIdx, null);
return (Function) result;
}
@ -182,7 +181,7 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
* @return the state to pass to the next function
*/
public Object getNextState(VirtualFrame frame) {
return FrameUtil.getObjectSafe(frame, stateSlot);
return frame.getObject(stateSlotIdx);
}
/**
@ -192,8 +191,8 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
* @return the arguments to be applied to the next function
*/
public Object[] getNextArgs(VirtualFrame frame) {
Object[] result = (Object[]) FrameUtil.getObjectSafe(frame, argsSlot);
frame.setObject(argsSlot, null);
Object[] result = (Object[]) frame.getObject(argsSlotIdx);
frame.setObject(argsSlotIdx, null);
return result;
}
@ -212,7 +211,7 @@ public abstract class LoopingCallOptimiserNode extends CallOptimiserNode {
Object[] arguments = getNextArgs(frame);
CallerInfo callerInfo = getCallerInfo(frame);
frame.setObject(
resultSlot, dispatchNode.executeCall(function, callerInfo, state, arguments));
resultSlotIdx, dispatchNode.executeCall(function, callerInfo, state, arguments));
return false;
} catch (TailCallException e) {
setNextCall(frame, e.getFunction(), e.getCallerInfo(), e.getArguments());

View File

@ -53,18 +53,32 @@ public class BlockNode extends ExpressionNode {
return returnExpr.executeGeneric(frame);
}
/**
* Wrap all the statements inside this block node in {@link StatementNode}. Care is taken not for
* wrapping expression twice.
*
* @return This BlockNode with all the statements wrapped.
*/
@Override
public InstrumentableNode materializeInstrumentableNodes(
Set<Class<? extends Tag>> materializedTags) {
if (materializedTags.contains(StandardTags.StatementTag.class)) {
for (int i = 0; i < statements.length; i++) {
statements[i] = insert(StatementNode.wrap(statements[i]));
if (!isNodeWrapped(statements[i])) {
statements[i] = insert(StatementNode.wrap(statements[i]));
}
}
if (!isNodeWrapped(returnExpr)) {
returnExpr = insert(StatementNode.wrap(returnExpr));
}
this.returnExpr = insert(StatementNode.wrap(returnExpr));
}
return this;
}
private static boolean isNodeWrapped(ExpressionNode node) {
return node instanceof StatementNode || ExpressionNode.isWrapper(node);
}
@Override
public boolean hasTag(Class<? extends Tag> tag) {
return super.hasTag(tag)

View File

@ -3,7 +3,6 @@ package org.enso.interpreter.node.callable.thunk;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
import org.enso.interpreter.node.ExpressionNode;

View File

@ -3,7 +3,6 @@ package org.enso.interpreter.node.controlflow.caseexpr;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.NodeInfo;

View File

@ -1,6 +1,5 @@
package org.enso.interpreter.node.controlflow.permission;
import com.oracle.truffle.api.frame.FrameUtil;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.profiles.BranchProfile;
import org.enso.interpreter.node.ExpressionNode;

View File

@ -1,7 +1,9 @@
package org.enso.interpreter.node.expression.builtin.meta;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.AcceptsError;
@ -20,12 +22,30 @@ public abstract class IsSameObjectNode extends Node {
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right);
@Specialization(limit = "3")
boolean doExecute(
Object left,
Object right,
@CachedLibrary("left") InteropLibrary leftInterop,
@CachedLibrary("right") InteropLibrary rightInteropp) {
return (left == right) || leftInterop.isIdentical(left, right, rightInteropp);
/**
* Shortcut specialization for meta objects.
*
* <p>Note: Do not remove, as this is a workaround for an unexpected behavior of HostObject
* interop in GraalVM 22.3.0, where isIdentical(ArrayList.class, arrayListObject.getClass()) would
* return false.
*
* @return True if the qualified names of the meta objects are same.
*/
@Specialization(guards = {"interop.isMetaObject(metaLeft)", "interop.isMetaObject(metaRight)"})
boolean isSameMetaObjects(
Object metaLeft, Object metaRight, @CachedLibrary(limit = "2") InteropLibrary interop) {
try {
Object metaLeftName = interop.getMetaQualifiedName(metaLeft);
Object metaRightName = interop.getMetaQualifiedName(metaRight);
return isIdenticalObjects(metaLeftName, metaRightName, interop);
} catch (UnsupportedMessageException e) {
throw new IllegalStateException(e);
}
}
@Fallback
boolean isIdenticalObjects(
Object left, Object right, @CachedLibrary(limit = "2") InteropLibrary interop) {
return (left == right) || interop.isIdentical(left, right, interop);
}
}

View File

@ -1,10 +1,12 @@
package org.enso.interpreter.node.expression.builtin.special;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import org.enso.interpreter.dsl.BuiltinMethod;
@BuiltinMethod(type = "Special", name = "<join_thread>")
public class JoinThreadNode extends Node {
@TruffleBoundary
public Object execute(Object self) {
try {
((Thread) self).join();

View File

@ -79,7 +79,7 @@ public abstract class EvalNode extends BaseNode {
ClosureRootNode framedNode =
ClosureRootNode.build(
context.getLanguage(), localScope, moduleScope, expr, null, "<eval>", false, false);
return Truffle.getRuntime().createCallTarget(framedNode);
return framedNode.getCallTarget();
}
@Specialization(

View File

@ -1,9 +1,8 @@
package org.enso.interpreter.node.scope;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeField;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.NodeInfo;
@ -13,21 +12,25 @@ import org.enso.interpreter.runtime.Context;
/** This node represents an assignment to a variable in a given scope. */
@NodeInfo(shortName = "=", description = "Assigns expression result to a variable.")
@NodeChild(value = "rhsNode", type = ExpressionNode.class)
@NodeField(name = "frameSlot", type = FrameSlot.class)
public abstract class AssignmentNode extends ExpressionNode {
AssignmentNode() {}
private final int frameSlotIdx;
AssignmentNode(int frameSlotIdx) {
this.frameSlotIdx = frameSlotIdx;
}
/**
* Creates an instance of this node.
*
* @param expression the expression being assigned
* @param slot the slot to which {@code expression} is being assigned
* @param frameSlotIdx the slot index to which {@code expression} is being assigned
* @return a node representing an assignment
*/
public static AssignmentNode build(ExpressionNode expression, FrameSlot slot) {
return AssignmentNodeGen.create(expression, slot);
public static AssignmentNode build(ExpressionNode expression, int frameSlotIdx) {
return AssignmentNodeGen.create(frameSlotIdx, expression);
}
/**
* Writes a long value into the provided frame.
*
@ -37,8 +40,8 @@ public abstract class AssignmentNode extends ExpressionNode {
*/
@Specialization(guards = "isLongOrIllegal(frame)")
protected Object writeLong(VirtualFrame frame, long value) {
frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Long);
frame.setLong(getFrameSlot(), value);
frame.getFrameDescriptor().setSlotKind(frameSlotIdx, FrameSlotKind.Long);
frame.setLong(frameSlotIdx, value);
return Context.get(this).getNothing();
}
@ -50,23 +53,16 @@ public abstract class AssignmentNode extends ExpressionNode {
* @param value the value to write
* @return the unit type
*/
@Specialization
@Fallback
protected Object writeObject(VirtualFrame frame, Object value) {
frame.getFrameDescriptor().setFrameSlotKind(getFrameSlot(), FrameSlotKind.Object);
frame.setObject(getFrameSlot(), value);
frame.getFrameDescriptor().setSlotKind(frameSlotIdx, FrameSlotKind.Object);
frame.setObject(frameSlotIdx, value);
return Context.get(this).getNothing();
}
boolean isLongOrIllegal(VirtualFrame frame) {
FrameSlotKind kind = frame.getFrameDescriptor().getFrameSlotKind(getFrameSlot());
FrameSlotKind kind = frame.getFrameDescriptor().getSlotKind(frameSlotIdx);
return kind == FrameSlotKind.Long || kind == FrameSlotKind.Illegal;
}
/**
* Gets the current frame slot
*
* @return the frame slot being written to
*/
public abstract FrameSlot getFrameSlot();
}

View File

@ -12,7 +12,12 @@ import org.enso.interpreter.node.ExpressionNode;
import org.enso.interpreter.runtime.callable.function.Function;
import org.enso.interpreter.runtime.scope.FramePointer;
/** Reads from a local target (variable or call target). */
/**
* Reads from a local target (variable or call target).
*
* <p>Note that local in this context does not necessarily mean that the variable is in the given
* {@link Frame}. The {@code framePointer} field may point to the parent frame.
*/
@NodeInfo(shortName = "readVar", description = "Access local variable value.")
@NodeField(name = "framePointer", type = FramePointer.class)
public abstract class ReadLocalVariableNode extends ExpressionNode {
@ -41,9 +46,9 @@ public abstract class ReadLocalVariableNode extends ExpressionNode {
@Specialization(rewriteOn = FrameSlotTypeException.class)
protected long readLong(VirtualFrame frame) throws FrameSlotTypeException {
if (getFramePointer().getParentLevel() == 0)
return frame.getLong(getFramePointer().getFrameSlot());
return frame.getLong(getFramePointer().getFrameSlotIdx());
MaterializedFrame currentFrame = getProperFrame(frame);
return currentFrame.getLong(getFramePointer().getFrameSlot());
return currentFrame.getLong(getFramePointer().getFrameSlotIdx());
}
/**
@ -57,17 +62,17 @@ public abstract class ReadLocalVariableNode extends ExpressionNode {
@Specialization(rewriteOn = FrameSlotTypeException.class)
protected Object readGeneric(VirtualFrame frame) throws FrameSlotTypeException {
if (getFramePointer().getParentLevel() == 0)
return frame.getObject(getFramePointer().getFrameSlot());
return frame.getObject(getFramePointer().getFrameSlotIdx());
MaterializedFrame currentFrame = getProperFrame(frame);
return currentFrame.getObject(getFramePointer().getFrameSlot());
return currentFrame.getObject(getFramePointer().getFrameSlotIdx());
}
@Specialization
protected Object readGenericValue(VirtualFrame frame) {
if (getFramePointer().getParentLevel() == 0)
return frame.getValue(getFramePointer().getFrameSlot());
return frame.getValue(getFramePointer().getFrameSlotIdx());
MaterializedFrame currentFrame = getProperFrame(frame);
return currentFrame.getValue(getFramePointer().getFrameSlot());
return currentFrame.getValue(getFramePointer().getFrameSlotIdx());
}
/**

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.runtime.callable;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
@ -52,7 +53,7 @@ public final class UnresolvedSymbol implements TruffleObject {
* is returned. This is useful for certain subtyping relations, such as "any constructor is a
* subtype of Any" or "Nat is a subtype of Int, is a subtype of Number".
*
* @param constructors the constructors hierarchy for which this symbol should be resolved
* @param type the type for which this symbol should be resolved
* @return the resolved function definition, or null if not found
*/
public Function resolveFor(Type type) {
@ -73,6 +74,7 @@ public final class UnresolvedSymbol implements TruffleObject {
}
@ExportMessage
@TruffleBoundary
String toDisplayString(boolean allowSideEffects) {
return this.toString();
}

View File

@ -1,6 +1,7 @@
package org.enso.interpreter.runtime.callable.atom;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.interop.ArityException;
@ -145,13 +146,13 @@ public final class AtomConstructor implements TruffleObject {
type.getName() + "." + name,
null,
false);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(rootNode);
RootCallTarget callTarget = rootNode.getCallTarget();
return new Function(callTarget, null, new FunctionSchema(args));
}
private void generateQualifiedAccessor() {
QualifiedAccessorNode node = new QualifiedAccessorNode(null, this);
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
RootCallTarget callTarget = node.getCallTarget();
Function function =
new Function(
callTarget,
@ -249,12 +250,13 @@ public final class AtomConstructor implements TruffleObject {
}
@ExportMessage
@TruffleBoundary
String toDisplayString(boolean allowSideEffects) {
return "Constructor<" + name + ">";
}
/** @return the fully qualified name of this constructor. */
@CompilerDirectives.TruffleBoundary
@TruffleBoundary
public QualifiedName getQualifiedName() {
return type.getQualifiedName().createChild(getName());
}

View File

@ -85,7 +85,7 @@ public final class Function implements TruffleObject {
* @return a Function object with specified behavior and arguments
*/
public static Function fromBuiltinRootNode(BuiltinRootNode node, ArgumentDefinition... args) {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
RootCallTarget callTarget = node.getCallTarget();
FunctionSchema schema = new FunctionSchema(args);
return new Function(callTarget, null, schema);
}
@ -102,7 +102,7 @@ public final class Function implements TruffleObject {
*/
public static Function fromBuiltinRootNodeWithCallerFrameAccess(
BuiltinRootNode node, ArgumentDefinition... args) {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
RootCallTarget callTarget = node.getCallTarget();
FunctionSchema schema = new FunctionSchema(FunctionSchema.CallerFrameAccess.FULL, args);
return new Function(callTarget, null, schema);
}

View File

@ -66,10 +66,9 @@ public final class Type implements TruffleObject {
private void generateQualifiedAccessor() {
var node = new ConstantNode(null, this);
var callTarget = Truffle.getRuntime().createCallTarget(node);
var function =
new Function(
callTarget,
node.getCallTarget(),
null,
new FunctionSchema(
new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE)));
@ -161,10 +160,9 @@ public final class Type implements TruffleObject {
}
roots.forEach(
(name, node) -> {
RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node);
var f =
new Function(
callTarget,
node.getCallTarget(),
null,
new FunctionSchema(
new ArgumentDefinition(

View File

@ -1,5 +1,6 @@
package org.enso.interpreter.runtime.error;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.InteropLibrary;
@ -82,6 +83,7 @@ public class DataflowError extends AbstractTruffleException {
}
@ExportMessage
@TruffleBoundary
public String toDisplayString(
boolean allowSideEffects,
@CachedLibrary(limit = "3") InteropLibrary displays,

View File

@ -0,0 +1,279 @@
package org.enso.interpreter.runtime.scope;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.List;
import java.util.stream.Collectors;
import org.enso.interpreter.Language;
import org.enso.interpreter.node.EnsoRootNode;
import org.enso.interpreter.runtime.callable.function.Function;
/**
* This class serves as a basic support for debugging with Chrome inspector. Currently, only
* function scopes are supported.
*
* <p>Some of the features that remain to be implemented are:
*
* <ul>
* <li>Module scopes. How to display imports in chrome devtools? Get inspiration from Python?
* <li>Evaluation of an arbitrary expression
* </ul>
*/
@ExportLibrary(InteropLibrary.class)
public class DebugLocalScope implements TruffleObject {
private final EnsoRootNode rootNode;
/** All the bindings, including the parent scopes. */
private final Map<String, FramePointer> allBindings;
/**
* The inner lists represent particular scope in a scope hierarchy. For example, for the following
* snippet:
*
* <pre>
* func =
* x = 1
* inner_func =
* y = 2
* y
* inner_func
* </pre>
*
* the value of this field (for `inner_func` scope) would be {@code [['x'], ['y']]}
*/
private final List<List<String>> bindingsByLevels;
/** Index of the current scope into {@link #bindingsByLevels} list. */
private final int bindingsByLevelsIdx;
private final MaterializedFrame frame;
private DebugLocalScope(
EnsoRootNode rootNode,
MaterializedFrame frame,
List<List<String>> bindingsByLevels,
int bindingsByLevelsIdx) {
assert bindingsByLevels != null;
this.rootNode = rootNode;
this.frame = frame;
this.allBindings = rootNode.getLocalScope().flattenBindings();
this.bindingsByLevels = bindingsByLevels;
this.bindingsByLevelsIdx = bindingsByLevelsIdx;
assert !this.bindingsByLevels.isEmpty();
assert 0 <= this.bindingsByLevelsIdx && this.bindingsByLevelsIdx < this.bindingsByLevels.size();
}
public static DebugLocalScope createFromFrame(EnsoRootNode rootNode, MaterializedFrame frame) {
return new DebugLocalScope(
rootNode, frame, gatherBindingsByLevels(rootNode.getLocalScope().flattenBindings()), 0);
}
private static DebugLocalScope createParent(DebugLocalScope childScope) {
return new DebugLocalScope(
childScope.rootNode,
childScope.frame,
childScope.bindingsByLevels,
childScope.bindingsByLevelsIdx + 1);
}
private static List<List<String>> gatherBindingsByLevels(Map<String, FramePointer> bindings) {
int maxParentLevel =
bindings.values().stream()
.max(Comparator.comparingInt(FramePointer::getParentLevel))
.orElseThrow()
.getParentLevel();
// Get all binding names for a particular parent level
List<List<String>> bindingsByLevels = new ArrayList<>(maxParentLevel + 1);
for (int level = 0; level < maxParentLevel + 1; level++) {
final int finalLevel = level;
List<String> levelBindings =
bindings.entrySet().stream()
.filter(entry -> entry.getValue().getParentLevel() == finalLevel)
.map(Entry::getKey)
.collect(Collectors.toList());
bindingsByLevels.add(levelBindings);
}
return bindingsByLevels;
}
@ExportMessage
boolean hasLanguage() {
return true;
}
@ExportMessage
Class<? extends TruffleLanguage<?>> getLanguage() {
return Language.class;
}
@ExportMessage
boolean isScope() {
return true;
}
@ExportMessage
boolean hasMembers() {
return true;
}
/** Returns the members from the current local scope and all the parent scopes. */
@ExportMessage
ScopeMembers getMembers(boolean includeInternal) {
List<String> members = new ArrayList<>();
bindingsByLevels.stream().skip(bindingsByLevelsIdx).forEach(members::addAll);
return new ScopeMembers(members);
}
@ExportMessage
boolean isMemberModifiable(String memberName) {
return false;
}
@ExportMessage
boolean isMemberInsertable(String memberName) {
return false;
}
@ExportMessage
boolean isMemberInvocable(String memberName) {
// TODO
return false;
}
@ExportMessage
boolean hasMemberReadSideEffects(String member) {
return false;
}
@ExportMessage
boolean hasMemberWriteSideEffects(String member) {
return false;
}
@ExportMessage
boolean isMemberReadable(String memberName) {
// When a value in a frame is null, it means that the corresponding
// AssignmentNode was not run yet, and the slot kind of the
// FrameDescriptor would be Illegal.
return allBindings.containsKey(memberName)
&& getValue(frame, allBindings.get(memberName)) != null;
}
@ExportMessage
Object readMember(String member) {
FramePointer framePtr = allBindings.get(member);
return getValue(frame, framePtr);
}
@ExportMessage
void writeMember(String member, Object value) throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}
@ExportMessage
Object invokeMember(String member, Object[] args) throws UnsupportedMessageException {
throw UnsupportedMessageException.create();
}
@ExportMessage
boolean hasScopeParent() {
return bindingsByLevelsIdx < bindingsByLevels.size() - 1;
}
/**
* Returns the parent scope. ModuleScopes are not supported yet.
*
* @return Parent scope (outer method).
* @throws UnsupportedMessageException if there is no parent scope.
*/
@ExportMessage
Object getScopeParent() throws UnsupportedMessageException {
if (!hasScopeParent()) {
throw UnsupportedMessageException.create();
} else {
return createParent(this);
}
}
@ExportMessage
boolean hasSourceLocation() {
return true;
}
@ExportMessage
SourceSection getSourceLocation() {
return rootNode.getSourceSection();
}
@ExportMessage
@TruffleBoundary
String toDisplayString(boolean allowSideEffects) {
return rootNode.toString();
}
@Override
public String toString() {
return String.format(
"DebugLocalScope{rootNode = '%s', bindingsByLevels = %s, idx = %d}",
rootNode.toString(), bindingsByLevels.toString(), bindingsByLevelsIdx);
}
private Object getValue(MaterializedFrame frame, FramePointer ptr) {
return getProperFrame(frame, ptr).getValue(ptr.getFrameSlotIdx());
}
private MaterializedFrame getProperFrame(MaterializedFrame frame, FramePointer ptr) {
MaterializedFrame currentFrame = frame;
for (int i = 0; i < ptr.getParentLevel(); i++) {
currentFrame = Function.ArgumentsHelper.getLocalScope(currentFrame.getArguments());
}
return currentFrame;
}
/** Simple interop wrapper for a list of strings. */
@ExportLibrary(InteropLibrary.class)
static final class ScopeMembers implements TruffleObject {
private final List<String> memberNames;
ScopeMembers(List<String> memberNames) {
this.memberNames = memberNames;
}
@ExportMessage
boolean hasArrayElements() {
return true;
}
@ExportMessage
long getArraySize() {
return memberNames.size();
}
@ExportMessage
boolean isArrayElementReadable(long index) {
return 0 <= index && index < memberNames.size();
}
@ExportMessage
String readArrayElement(long index) {
return memberNames.get((int) index);
}
@Override
public String toString() {
return memberNames.toString();
}
}
}

View File

@ -1,23 +1,21 @@
package org.enso.interpreter.runtime.scope;
import com.oracle.truffle.api.frame.FrameSlot;
/**
* A representation of a pointer into a stack frame at a given number of levels above the current.
*/
public class FramePointer {
private final int parentLevel;
private final FrameSlot frameSlot;
private final int frameSlotIdx;
/**
* A representation of a frame slot at a given level above the current frame.
*
* @param parentLevel the number of parents to move from the current frame to get here
* @param frameSlot the slot in the n-th parent frame
* @param frameSlotIdx the index of the slot in the n-th parent frame
*/
public FramePointer(int parentLevel, FrameSlot frameSlot) {
public FramePointer(int parentLevel, int frameSlotIdx) {
this.parentLevel = parentLevel;
this.frameSlot = frameSlot;
this.frameSlotIdx = frameSlotIdx;
}
/**
@ -30,11 +28,11 @@ public class FramePointer {
}
/**
* Gets the frame slot.
* Gets the index of the frame slot.
*
* @return the frame slot represented by this {@code FramePointer}
* @return the frame slot index represented by this {@code FramePointer}
*/
public FrameSlot getFrameSlot() {
return frameSlot;
public int getFrameSlotIdx() {
return frameSlotIdx;
}
}

View File

@ -51,7 +51,7 @@ class SerializationManager(compiler: Compiler) {
TimeUnit.SECONDS,
new LinkedBlockingDeque[Runnable](),
(runnable: Runnable) => {
env.createThread(runnable)
env.createSystemThread(runnable)
}
)

View File

@ -1,7 +1,5 @@
package org.enso.compiler.codegen
import com.oracle.truffle.api.Truffle
import com.oracle.truffle.api.frame.FrameSlot
import com.oracle.truffle.api.source.{Source, SourceSection}
import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Import
@ -244,12 +242,13 @@ class IrToTruffle(
"No occurrence on an argument definition."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
val slot = localScope.createVarSlot(occInfo.id)
val slotIdx = localScope.getVarSlotIdx(occInfo.id)
argDefs(idx) = arg
val readArg =
ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null))
val assignmentArg = AssignmentNode.build(readArg, slot)
val argRead = ReadLocalVariableNode.build(new FramePointer(0, slot))
val assignmentArg = AssignmentNode.build(readArg, slotIdx)
val argRead =
ReadLocalVariableNode.build(new FramePointer(0, slotIdx))
argumentExpressions.append((assignmentArg, argRead))
}
@ -398,7 +397,7 @@ class IrToTruffle(
cons,
methodDef.methodName.name
)
val callTarget = Truffle.getRuntime.createCallTarget(rootNode)
val callTarget = rootNode.getCallTarget
val arguments = bodyBuilder.args()
Right(
Some(
@ -479,7 +478,7 @@ class IrToTruffle(
toType,
methodDef.methodName.name
)
val callTarget = Truffle.getRuntime.createCallTarget(rootNode)
val callTarget = rootNode.getCallTarget
val arguments = bodyBuilder.args()
new RuntimeFunction(
callTarget,
@ -602,11 +601,10 @@ class IrToTruffle(
}
private def generateEnsoProjectMethod(): Unit = {
val name = BindingsMap.Generated.ensoProjectMethodName
val pkg = context.getPackageOf(moduleScope.getModule.getSourceFile)
val body = Truffle.getRuntime.createCallTarget(
new EnsoProjectNode(language, context, pkg)
)
val name = BindingsMap.Generated.ensoProjectMethodName
val pkg = context.getPackageOf(moduleScope.getModule.getSourceFile)
val ensoProjectNode = new EnsoProjectNode(language, context, pkg)
val body = ensoProjectNode.getCallTarget
val schema = new FunctionSchema(
new ArgumentDefinition(
0,
@ -621,9 +619,7 @@ class IrToTruffle(
private def generateReExportBindings(module: IR.Module): Unit = {
def mkConsGetter(constructor: AtomConstructor): RuntimeFunction = {
new RuntimeFunction(
Truffle.getRuntime.createCallTarget(
new QualifiedAccessorNode(language, constructor)
),
new QualifiedAccessorNode(language, constructor).getCallTarget,
null,
new FunctionSchema(
new ArgumentDefinition(
@ -637,9 +633,7 @@ class IrToTruffle(
def mkTypeGetter(tp: Type): RuntimeFunction = {
new RuntimeFunction(
Truffle.getRuntime.createCallTarget(
new ConstantNode(language, tp)
),
new ConstantNode(language, tp).getCallTarget,
null,
new FunctionSchema(
new ArgumentDefinition(
@ -838,7 +832,7 @@ class IrToTruffle(
false
)
val callTarget = Truffle.getRuntime.createCallTarget(defaultRootNode)
val callTarget = defaultRootNode.getCallTarget
setLocation(CreateThunkNode.build(callTarget), block.location)
} else {
val statementExprs = block.expressions.map(this.run).toArray
@ -1213,10 +1207,10 @@ class IrToTruffle(
currentVarName = binding.name.name
val slot = scope.createVarSlot(occInfo.id)
val slotIdx = scope.getVarSlotIdx(occInfo.id)
setLocation(
AssignmentNode.build(this.run(binding.expression, true), slot),
AssignmentNode.build(this.run(binding.expression, true), slotIdx),
binding.location
)
}
@ -1275,10 +1269,10 @@ class IrToTruffle(
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
val slot = scope.getFramePointer(useInfo.id)
val global = name.getMetadata(GlobalNames)
if (slot.isDefined) {
ReadLocalVariableNode.build(slot.get)
val framePointer = scope.getFramePointer(useInfo.id)
val global = name.getMetadata(GlobalNames)
if (framePointer.isDefined) {
ReadLocalVariableNode.build(framePointer.get)
} else if (global.isDefined) {
val resolution = global.get.target
nodeForResolution(resolution)
@ -1475,7 +1469,7 @@ class IrToTruffle(
def bodyNode(): RuntimeExpression = bodyN
private def computeBodyNode(): RuntimeExpression = {
val (argSlots, _, argExpressions) = slots
val (argSlotIdxs, _, argExpressions) = slots
val bodyExpr = body match {
case IR.Foreign.Definition(lang, code, _, _, _) =>
@ -1483,7 +1477,7 @@ class IrToTruffle(
lang,
code,
arguments.map(_.name.name),
argSlots
argSlotIdxs
)
case _ => ExpressionProcessor.this.run(body)
}
@ -1496,7 +1490,7 @@ class IrToTruffle(
}
private def computeSlots(): (
List[FrameSlot],
List[Int],
Array[ArgumentDefinition],
ArrayBuffer[RuntimeExpression]
) = {
@ -1516,10 +1510,10 @@ class IrToTruffle(
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
val slot = scope.createVarSlot(occInfo.id)
val slotIdx = scope.getVarSlotIdx(occInfo.id)
val readArg =
ReadArgumentNode.build(idx, arg.getDefaultValue.orElse(null))
val assignArg = AssignmentNode.build(readArg, slot)
val assignArg = AssignmentNode.build(readArg, slotIdx)
argExpressions.append(assignArg)
@ -1534,7 +1528,7 @@ class IrToTruffle(
s"A duplicate argument name, $argName, was found during codegen."
)
} else seenArgNames.add(argName)
slot
slotIdx
}
(argSlots, argDefinitions, argExpressions)
}
@ -1544,13 +1538,15 @@ class IrToTruffle(
language: EpbParser.ForeignLanguage,
code: String,
argumentNames: List[String],
argumentSlots: List[FrameSlot]
argumentSlotIdxs: List[Int]
): RuntimeExpression = {
val src = EpbParser.buildSource(language, code, scopeName)
val foreignCt = context.getEnvironment
.parseInternal(src, argumentNames: _*)
val argumentReaders = argumentSlots
.map(slot => ReadLocalVariableNode.build(new FramePointer(0, slot)))
val argumentReaders = argumentSlotIdxs
.map(slotIdx =>
ReadLocalVariableNode.build(new FramePointer(0, slotIdx))
)
.toArray[RuntimeExpression]
ForeignMethodCallNode.build(argumentReaders, foreignCt)
}
@ -1580,7 +1576,7 @@ class IrToTruffle(
false,
binding
)
val callTarget = Truffle.getRuntime.createCallTarget(fnRootNode)
val callTarget = fnRootNode.getCallTarget
val expr = CreateFunctionNode.build(callTarget, bodyBuilder.args())
@ -1599,14 +1595,14 @@ class IrToTruffle(
* 1. Argument Conversion: Arguments are converted into their definitions so
* as to provide a compact representation of all known information about
* that argument.
* 2. Frame Conversion: A variable slot is created in the function's local
* 2. Frame Conversion: A variable framePointer is created in the function's local
* frame to contain the value of the function argument.
* 3. Read Provision: A `ReadArgumentNode` is generated to allow that
* function argument to be treated purely as a local variable access. See
* Note [Handling Argument Defaults] for more information on how this
* works.
* 4. Value Assignment: A `AssignmentNode` is created to connect the
* argument value to the frame slot created in Step 2.
* argument value to the framePointer created in Step 2.
* 5. Body Rewriting: The expression representing the argument is written
* into the function body, thus allowing it to be read simply.
*/
@ -1729,14 +1725,14 @@ class IrToTruffle(
)
.unsafeAs[AliasAnalysis.Info.Scope.Child]
val shouldSuspend = value match {
val shouldCreateClosureRootNode = value match {
case _: IR.Name => false
case _: IR.Literal.Text => false
case _: IR.Literal.Number => false
case _ => true
}
val childScope = if (shouldSuspend) {
val childScope = if (shouldCreateClosureRootNode) {
scope.createChild(scopeInfo.scope)
} else {
// Note [Scope Flattening]
@ -1745,7 +1741,7 @@ class IrToTruffle(
val argumentExpression =
new ExpressionProcessor(childScope, scopeName).run(value)
val result = if (!shouldSuspend) {
val result = if (!shouldCreateClosureRootNode) {
argumentExpression
} else {
argumentExpression.setTailStatus(getTailStatus(value))
@ -1757,18 +1753,17 @@ class IrToTruffle(
.map(loc => source.createSection(loc.start, loc.length))
.orNull
val callTarget = Truffle.getRuntime.createCallTarget(
ClosureRootNode.build(
language,
childScope,
moduleScope,
argumentExpression,
section,
displayName,
true,
false
)
val closureRootNode = ClosureRootNode.build(
language,
childScope,
moduleScope,
argumentExpression,
section,
displayName,
true,
false
)
val callTarget = closureRootNode.getCallTarget
CreateThunkNode.build(callTarget)
}
@ -1848,7 +1843,7 @@ class IrToTruffle(
)
CreateThunkNode.build(
Truffle.getRuntime.createCallTarget(defaultRootNode)
defaultRootNode.getCallTarget
)
} else {
defaultExpression

View File

@ -366,6 +366,7 @@ case object AliasAnalysis extends IRPass {
)
parentScope.add(occurrence)
parentScope.addDefinition(occurrence)
binding
.copy(
@ -414,9 +415,10 @@ case object AliasAnalysis extends IRPass {
}
val labelId = graph.nextId()
parentScope.add(
val definition =
Occurrence.Def(labelId, label.name, label.getId, label.getExternalId)
)
parentScope.add(definition)
parentScope.addDefinition(definition)
member
.copy(
@ -452,7 +454,7 @@ case object AliasAnalysis extends IRPass {
): List[IR.DefinitionArgument] = {
args.map {
case arg @ IR.DefinitionArgument.Specified(
IR.Name.Self(_, true, _, _),
selfName @ IR.Name.Self(_, true, _, _),
_,
_,
_,
@ -460,9 +462,19 @@ case object AliasAnalysis extends IRPass {
_,
_
) =>
// Synthetic `self` must not be added to the scope
// Synthetic `self` must not be added to the scope, but it has to be added as a
// definition for frame index metadata
val occurrenceId = graph.nextId()
arg.updateMetadata(this -->> Info.Occurrence(graph, occurrenceId))
val definition = Graph.Occurrence.Def(
occurrenceId,
selfName.name,
arg.getId,
arg.getExternalId
)
scope.addDefinition(definition)
arg.updateMetadata(
this -->> Info.Occurrence(graph, occurrenceId)
)
case arg @ IR.DefinitionArgument.Specified(
name,
_,
@ -480,10 +492,15 @@ case object AliasAnalysis extends IRPass {
)
val occurrenceId = graph.nextId()
scope.add(
Graph.Occurrence
.Def(occurrenceId, name.name, arg.getId, arg.getExternalId, susp)
val definition = Graph.Occurrence.Def(
occurrenceId,
name.name,
arg.getId,
arg.getExternalId,
susp
)
scope.add(definition)
scope.addDefinition(definition)
arg
.copy(defaultValue = newDefault)
@ -605,7 +622,7 @@ case object AliasAnalysis extends IRPass {
* @param isConstructorNameInPatternContext whether or not the name is
* constructor name occurring in a pattern context
* @param graph the graph in which the analysis is taking place
* @param parentScope the scope in which `name` is delcared
* @param parentScope the scope in which `name` is declared
* @return `name`, with alias analysis information attached
*/
def analyseName(
@ -618,9 +635,10 @@ case object AliasAnalysis extends IRPass {
val occurrenceId = graph.nextId()
if (isInPatternContext && !isConstructorNameInPatternContext) {
val occurrence =
val definition =
Occurrence.Def(occurrenceId, name.name, name.getId, name.getExternalId)
parentScope.add(occurrence)
parentScope.add(definition)
parentScope.addDefinition(definition)
} else {
val occurrence =
Occurrence.Use(occurrenceId, name.name, name.getId, name.getExternalId)
@ -1145,11 +1163,15 @@ case object AliasAnalysis extends IRPass {
*
* @param childScopes all scopes that are _direct_ children of `this`
* @param occurrences all symbol occurrences in `this` scope
* @param allDefinitions all definitions in this scope, including synthetic ones.
* Note that there may not be a link for all these definitions.
*/
sealed class Scope(
var childScopes: List[Scope] = List(),
var occurrences: Set[Occurrence] = Set()
var childScopes: List[Scope] = List(),
var occurrences: Set[Occurrence] = Set(),
var allDefinitions: List[Occurrence.Def] = List()
) extends Serializable {
var parent: Option[Scope] = None
/** Counts the number of scopes from this scope to the root.
@ -1189,7 +1211,8 @@ case object AliasAnalysis extends IRPass {
this.childScopes.foreach(scope =>
childScopeCopies += scope.deepCopy(mapping)
)
val newScope = new Scope(childScopeCopies.toList, occurrences)
val newScope =
new Scope(childScopeCopies.toList, occurrences, allDefinitions)
mapping.put(this, newScope)
newScope
}
@ -1235,6 +1258,15 @@ case object AliasAnalysis extends IRPass {
occurrences += occurrence
}
/** Adds a definition, including a definition with synthetic name, without
* any links.
*
* @param definition The definition to add.
*/
def addDefinition(definition: Occurrence.Def): Unit = {
allDefinitions = allDefinitions ++ List(definition)
}
/** Finds an occurrence for the provided ID in the current scope, if it
* exists.
*

View File

@ -1,14 +1,18 @@
package org.enso.interpreter.runtime.scope
import com.oracle.truffle.api.frame.{FrameDescriptor, FrameSlot}
import com.oracle.truffle.api.frame.{FrameDescriptor, FrameSlotKind}
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{
Id,
Occurrence,
Scope => AliasScope
}
import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis}
import org.enso.interpreter.runtime.scope.LocalScope.{
internalSlots,
monadicStateSlotName
}
import scala.collection.mutable
import scala.jdk.CollectionConverters._
/** A representation of an Enso local scope.
@ -28,20 +32,27 @@ import scala.jdk.CollectionConverters._
* @param dataflowInfo information on the dataflow analysis for this scope
* @param flattenToParent whether or not the frame should be flattened into its
* parent
* @param frameSlots a mapping from symbol definition identifiers to slots in
* the Enso frame
* @param parentFrameSlotIdxs Mapping of occurence identifiers to frame slot indexes
* from the whole parent hierarchy, i.e., this parameter should contain all the
* indexes for all the parents.
*/
class LocalScope(
final val parentScope: Option[LocalScope],
final val aliasingGraph: AliasAnalysis.Graph,
final val scope: AliasAnalysis.Graph.Scope,
final val dataflowInfo: DataflowAnalysis.Metadata,
final val flattenToParent: Boolean = false,
final val frameSlots: mutable.Map[Graph.Id, FrameSlot] = mutable.Map()
final val flattenToParent: Boolean = false,
private val parentFrameSlotIdxs: Map[Graph.Id, Int] = Map()
) {
lazy val frameDescriptor: FrameDescriptor = buildFrameDescriptor()
private lazy val localFrameSlotIdxs: Map[Graph.Id, Int] =
gatherLocalFrameSlotIdxs()
/** A descriptor for this frame. */
val frameDescriptor: FrameDescriptor = new FrameDescriptor()
/** All frame slot indexes, including local and all the parents.
* Useful for quick searching for [[FramePointer]] of parent scopes.
*/
private lazy val allFrameSlotIdxs: Map[Graph.Id, Int] =
parentFrameSlotIdxs ++ localFrameSlotIdxs
/** Creates a new child with a new aliasing scope.
*
@ -66,38 +77,56 @@ class LocalScope(
childScope,
dataflowInfo,
flattenToParent,
frameSlots
allFrameSlotIdxs
)
}
/** Creates a frame slot for a given identifier.
*
* @param id the identifier of a variable definition occurrence from alias
* analysis
* @return a new frame slot for `id`
/** Returns frame slot index to a monadic state, which is considered an internal slot.
* @return
*/
def createVarSlot(id: Graph.Id): FrameSlot = {
val slot = frameDescriptor.addFrameSlot(aliasingGraph.idToSymbol(id))
frameSlots(id) = slot
slot
def monadicStateSlotIdx: Int = {
internalSlots.zipWithIndex
.find { case ((_, name), _) => name == monadicStateSlotName }
.map(_._2)
.getOrElse(
throw new IllegalStateException(
s"$monadicStateSlotName slot should be present in every frame descriptor"
)
)
}
/** Obtains the frame pointer for a given identifier.
/** Get a frame slot index for a given identifier.
*
* The identifier must be present in the local scope.
*
* @param id the identifier of a variable definition occurrence from alias
* analysis.
* @return the frame slot index for `id`.
*/
def getVarSlotIdx(id: Graph.Id): Int = {
assert(localFrameSlotIdxs.contains(id))
localFrameSlotIdxs(id)
}
/** Obtains the frame pointer for a given identifier from the current scope, or from
* any parent scopes.
*
* @param id the identifier of a variable usage occurrence from alias
* analysis
* @return the frame pointer for `id`, if it exists
*/
def getFramePointer(id: Graph.Id): Option[FramePointer] = {
aliasingGraph.defLinkFor(id).flatMap { link =>
val slot = frameSlots.get(link.target)
slot.map(
new FramePointer(
if (flattenToParent) link.scopeCount - 1 else link.scopeCount,
_
aliasingGraph
.defLinkFor(id)
.flatMap { link =>
val slotIdx = allFrameSlotIdxs.get(link.target)
slotIdx.map(
new FramePointer(
if (flattenToParent) link.scopeCount - 1 else link.scopeCount,
_
)
)
)
}
}
}
/** Collects all the bindings in the current stack of scopes, accounting for
@ -108,6 +137,50 @@ class LocalScope(
def flattenBindings: java.util.Map[String, FramePointer] =
flattenBindingsWithLevel(0).asJava
private def addInternalSlots(
descriptorBuilder: FrameDescriptor.Builder
): Unit = {
for ((slotKind, name) <- internalSlots) {
descriptorBuilder.addSlot(slotKind, name, null)
}
}
/** Builds a [[FrameDescriptor]] from the alias analysis scope metadata for the local scope.
* See [[AliasAnalysis.Graph.Scope.allDefinitions]].
*
* @return [[FrameDescriptor]] built from the variable definitions in the local scope.
*/
private def buildFrameDescriptor(): FrameDescriptor = {
val descriptorBuilder = FrameDescriptor.newBuilder()
addInternalSlots(descriptorBuilder)
for (definition <- scope.allDefinitions) {
val returnedFrameIdx =
descriptorBuilder.addSlot(
FrameSlotKind.Illegal,
definition.symbol,
null
)
assert(localFrameSlotIdxs(definition.id) == returnedFrameIdx)
}
val frameDescriptor = descriptorBuilder.build()
assert(
internalSlots.length + localFrameSlotIdxs.size == frameDescriptor.getNumberOfSlots
)
frameDescriptor
}
/** Gather local variables from the alias scope information.
* Does not include any variables from the parent scopes.
* @return Mapping of local variable identifiers to their
* indexes in the frame. Takes into account all the
* internal slots, that are prepended to every frame.
*/
private def gatherLocalFrameSlotIdxs(): Map[Id, Int] = {
scope.allDefinitions.zipWithIndex.map { case (definition, i) =>
definition.id -> (i + internalSlots.size)
}.toMap
}
/** Flatten bindings from a given set of levels, accounting for shadowing.
*
* @param level the level to which to flatten
@ -124,12 +197,16 @@ class LocalScope(
case x: Occurrence.Def =>
parentResult += x.symbol -> new FramePointer(
level,
frameSlots(x.id)
allFrameSlotIdxs(x.id)
)
case _ =>
}
parentResult
}
override def toString: String = {
s"LocalScope(${frameDescriptor.toString})"
}
}
object LocalScope {
@ -147,4 +224,14 @@ object LocalScope {
DataflowAnalysis.DependencyInfo()
)
}
private val monadicStateSlotName = "<<monadic_state>>"
/** Internal slots are prepended at the beginning of every [[FrameDescriptor]].
* Every tuple of the list denotes frame slot kind and its name.
* Note that `info` for a frame slot is not used by Enso.
*/
def internalSlots: List[(FrameSlotKind, String)] = List(
(FrameSlotKind.Object, monadicStateSlotName)
)
}

View File

@ -1,12 +1,17 @@
package org.enso.interpreter.test;
import com.oracle.truffle.api.debug.DebugException;
import com.oracle.truffle.api.debug.DebugStackFrame;
import com.oracle.truffle.api.debug.DebugValue;
import com.oracle.truffle.api.debug.Debugger;
import com.oracle.truffle.api.debug.DebuggerSession;
import com.oracle.truffle.api.debug.SuspendedEvent;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@ -15,27 +20,74 @@ import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import org.junit.After;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
public class DebuggingEnsoTest {
@Test
public void evaluation() throws Exception {
Engine eng = Engine.newBuilder()
.allowExperimentalOptions(true)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../test/micro-distribution/component").toFile().getAbsolutePath()
).build();
Context ctx = Context.newBuilder()
.engine(eng)
.allowIO(true)
.build();
final Map<String, Language> langs = ctx.getEngine().getLanguages();
org.junit.Assert.assertNotNull("Enso found: " + langs, langs.get("enso"));
private Context context;
private Engine engine;
private Debugger debugger;
@Before
public void initContext() {
engine = Engine.newBuilder()
.allowExperimentalOptions(true)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths.get("../../test/micro-distribution/component").toFile().getAbsolutePath()
).build();
context = Context.newBuilder()
.engine(engine)
.allowExperimentalOptions(true)
.allowIO(true)
.allowAllAccess(true)
.build();
debugger = Debugger.find(engine);
Map<String, Language> langs = engine.getLanguages();
Assert.assertNotNull("Enso found: " + langs, langs.get("enso"));
}
@After
public void disposeContext() {
context.close();
engine.close();
}
private static void expectStackFrame(DebugStackFrame actualFrame, Map<String, String> expectedValues) {
Map<String, String> actualValues = new HashMap<>();
for (DebugValue declaredValue : actualFrame.getScope().getDeclaredValues()) {
actualValues.put(
declaredValue.getName(),
declaredValue.toDisplayString()
);
}
String errMessage = String.format("Expected values in stack: %s, instead got: %s",
expectedValues, actualValues);
Assert.assertEquals(errMessage, expectedValues, actualValues);
}
private static List<DebugStackFrame> getStackFramesFromEvent(SuspendedEvent event) {
List<DebugStackFrame> stackFrames = new ArrayList<>();
event.getStackFrames().forEach(stackFrames::add);
return stackFrames;
}
/**
* Steps through recursive evaluation of factorial with an accumulator, and for each step,
* checks the value of the `accumulator` variable.
*/
@Test
public void recursiveFactorialCall() throws Exception {
final URI facUri = new URI("memory://fac.enso");
final Source facSrc = Source.newBuilder("enso", """
fac : Number -> Number
@ -50,11 +102,10 @@ public class DebuggingEnsoTest {
.uri(facUri)
.buildLiteral();
var module = ctx.eval(facSrc);
var module = context.eval(facSrc);
var facFn = module.invokeMember("eval_expression", "fac");
final var dbg = Debugger.find(eng);
final var values = new TreeSet<Integer>();
try (var session = dbg.startSession((event) -> {
try (var session = debugger.startSession((event) -> {
final DebugValue accumulatorValue = findDebugValue(event, "accumulator");
if (accumulatorValue != null) {
final int accumulator = accumulatorValue.asInt();
@ -69,7 +120,60 @@ public class DebuggingEnsoTest {
assertEquals("Accumulator gets following values one by one", Set.of(1, 5, 20, 60, 120), values);
}
/**
* Checks whether the debugger correctly displays the values of variables in
* stack frames, including the stack frame of the caller method.
*/
@Test
public void callerVariablesAreVisibleOnPreviousStackFrame() {
URI fooUri = URI.create("memory://tmp.enso");
Source fooSource = Source.newBuilder("enso", """
bar arg_bar =
loc_bar = arg_bar + 1
loc_bar
foo x =
loc_foo = 1
bar loc_foo
""", "tmp.enso")
.uri(fooUri)
.buildLiteral();
Value module = context.eval(fooSource);
Value fooFunc = module.invokeMember("eval_expression", "foo");
try (DebuggerSession session = debugger.startSession((SuspendedEvent event) -> {
// TODO[PM]: This is a workaround for proper breakpoints, which do not work atm.
switch (event.getSourceSection().getCharacters().toString().strip()) {
// In method "foo"
case "bar loc_foo" -> {
List<DebugStackFrame> stackFrames = getStackFramesFromEvent(event);
Assert.assertEquals(1, stackFrames.size());
expectStackFrame(stackFrames.get(0), Map.of("x", "42", "loc_foo", "1"));
}
// In method "bar" called from "foo"
case "loc_bar" -> {
List<DebugStackFrame> stackFrames = getStackFramesFromEvent(event);
Assert.assertEquals(2, stackFrames.size());
Assert.assertTrue(stackFrames.get(1).getName().contains("foo"));
Assert.assertTrue(stackFrames.get(0).getName().contains("bar"));
expectStackFrame(stackFrames.get(1), Map.of("x", "42", "loc_foo", "1"));
expectStackFrame(stackFrames.get(0), Map.of("arg_bar", "1", "loc_bar", "2"));
}
}
event.getSession().suspendNextExecution();
})) {
session.suspendNextExecution();
fooFunc.execute(42);
}
}
// TODO[PM]: Re-enable (https://www.pivotaltracker.com/story/show/183854585)
@Test
@Ignore
public void unsafeRecursiveAtom() throws Exception {
Engine eng = Engine.newBuilder()
.allowExperimentalOptions(true)

View File

@ -894,6 +894,21 @@ class AliasAnalysisTest extends CompilerTest {
// No link between self.x and self
graphLinks shouldEqual Set(Link(valueUseId, 1, valueDefId))
}
"add self as a definition" in {
lambda.arguments.length shouldEqual 2
lambda.arguments(0).name shouldBe a[IR.Name.Self]
val lambdaScope = lambda
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Scope.Child]
.scope
lambdaScope.allDefinitions.length shouldBe 2
val defSymbols = lambdaScope.allDefinitions
.map(definition => definition.symbol)
defSymbols should equal(List("self", "x"))
}
}
"Alias analysis on conversion methods" should {
@ -1109,6 +1124,19 @@ class AliasAnalysisTest extends CompilerTest {
rootScope.childScopes should contain(fallbackBranchScope)
}
"cons branch scope should have argument definitions" in {
val consBranchScope = caseExpr.branches.head
.getMetadata(AliasAnalysis)
.get
.unsafeAs[Info.Scope.Child]
.scope
consBranchScope.allDefinitions.length shouldBe 2
val defSymbols = consBranchScope.allDefinitions
.map(definition => definition.symbol)
defSymbols should equal(List("a", "b"))
}
"correctly link to pattern variables" in {
val consBranch = caseExpr.branches.head
val pattern = consBranch.pattern.asInstanceOf[Pattern.Constructor]

View File

@ -99,6 +99,15 @@ class SuspendedArgumentsTest extends InterpreterTest {
eval(code).call(1) shouldEqual 1
}
"work properly with multiple defaulted arguments" in {
val code =
"""from Standard.Base import all
|
|main = a -> (~b = Panic.throw 1) -> (~c = Panic.throw 2) -> a
|""".stripMargin
eval(code).call(1) shouldEqual 1
}
"allow passing suspended functions" in {
val code =
"""main =

View File

@ -506,34 +506,47 @@ object DistributionPackage {
* @param os the system type
* @param graalDir the directory with a GraalVM distribution
* @param arguments the command arguments
* @return Stdout from the `gu` command.
*/
def gu(
log: ManagedLogger,
os: OS,
graalDir: File,
arguments: String*
): Unit = {
): String = {
val shallowFile = graalDir / "bin" / "gu"
val deepFile = graalDir / "Contents" / "Home" / "bin" / "gu"
val executableFile = os match {
case OS.Linux =>
graalDir / "bin" / "gu"
shallowFile
case OS.MacOS =>
graalDir / "Contents" / "Home" / "bin" / "gu"
if (deepFile.exists) {
deepFile
} else {
shallowFile
}
case OS.Windows =>
graalDir / "bin" / "gu.cmd"
}
val javaHomeFile = executableFile.getParentFile.getParentFile
val javaHome = javaHomeFile.toPath.toAbsolutePath
val command =
executableFile.toPath.toAbsolutePath.toString +: arguments
val exitCode = Process(
command,
Some(graalDir),
("JAVA_HOME", javaHomeFile.toPath.toAbsolutePath.toString),
("GRAALVM_HOME", javaHomeFile.toPath.toAbsolutePath.toString)
).!
if (exitCode != 0) {
throw new RuntimeException(
s"Failed to run '${command.mkString(" ")}'"
)
log.debug(s"Running $command in $graalDir with JAVA_HOME=${javaHome.toString}")
try {
Process(
command,
Some(graalDir),
("JAVA_HOME", javaHome.toString),
("GRAALVM_HOME", javaHome.toString)
).!!
} catch {
case _: RuntimeException =>
throw new RuntimeException(
s"Failed to run '${command.mkString(" ")}'"
)
}
}

View File

@ -30,8 +30,6 @@ object NativeImage {
* @param artifactName name of the artifact to create
* @param staticOnLinux specifies whether to link statically (applies only
* on Linux)
* @param initializeAtBuildtime specifies if classes should be initialized at
* build time by default
* @param additionalOptions additional options for the Native Image build
* tool
* @param memoryLimitMegabytes a memory limit for the build tool, in
@ -45,7 +43,6 @@ object NativeImage {
def buildNativeImage(
artifactName: String,
staticOnLinux: Boolean,
initializeAtBuildtime: Boolean = true,
additionalOptions: Seq[String] = Seq.empty,
memoryLimitMegabytes: Option[Int] = Some(15608),
initializeAtRuntime: Seq[String] = Seq.empty,
@ -100,6 +97,9 @@ object NativeImage {
Seq()
}
val quickBuildOption =
if (BuildInfo.isReleaseMode) Seq() else Seq("-Ob")
val memoryLimitOptions =
memoryLimitMegabytes.map(megs => s"-J-Xmx${megs}M").toSeq
@ -110,15 +110,14 @@ object NativeImage {
Seq(s"--initialize-at-run-time=$classes")
}
val initializeAtBuildtimeOptions =
if (initializeAtBuildtime) Seq("--initialize-at-build-time") else Seq()
var cmd =
Seq(nativeImagePath) ++
quickBuildOption ++
debugParameters ++ staticParameters ++ configs ++
Seq("--no-fallback", "--no-server") ++
initializeAtBuildtimeOptions ++
memoryLimitOptions ++ initializeAtRuntimeOptions ++
Seq("--initialize-at-build-time=") ++
initializeAtRuntimeOptions ++
memoryLimitOptions ++
additionalOptions;
if (mainClass.isEmpty) {

View File

@ -49,7 +49,7 @@ spec =
{
"headers": {
"Content-Length": "0",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -63,7 +63,7 @@ spec =
{
"headers": {
"Content-Length": "0",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -78,7 +78,7 @@ spec =
{
"headers": {
"Content-Length": "0",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -99,7 +99,7 @@ spec =
{
"headers": {
"Content-Length": "0",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -118,7 +118,7 @@ spec =
{
"headers": {
"Content-Length": "0",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -138,7 +138,7 @@ spec =
"headers": {
"Content-Length": "12",
"Content-Type": "text/plain",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -158,7 +158,7 @@ spec =
"headers": {
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -178,7 +178,7 @@ spec =
"headers": {
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -208,7 +208,7 @@ spec =
"headers": {
"Content-Length": "13",
"Content-Type": "application/json",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -231,7 +231,7 @@ spec =
"headers": {
"Content-Length": "13",
"Content-Type": "application/json",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -254,7 +254,7 @@ spec =
"headers": {
"Content-Length": "12",
"Content-Type": "application/octet-stream",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -274,7 +274,7 @@ spec =
{
"headers": {
"Content-Length": "0",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -290,7 +290,7 @@ spec =
"headers": {
"Content-Length": "13",
"Content-Type": "application/json",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",
@ -315,7 +315,7 @@ spec =
"headers": {
"Content-Length": "16",
"Content-Type": "application/json",
"User-Agent": "Java-http-client/11.0.13"
"User-Agent": "Java-http-client/11.0.17"
},
"origin": "127.0.0.1",
"url": "",

View File

@ -1,4 +1,4 @@
FROM ghcr.io/graalvm/graalvm-ce:java11-21.3.0
FROM ghcr.io/graalvm/graalvm-ce:java11-22.3.0
USER root

View File

@ -66,7 +66,7 @@ and then launch it with special `--dump-graphs` option:
enso$ ./built-distribution/enso-engine-0.0.0-dev-linux-amd64/enso-0.0.0-dev/bin/enso --dump-graphs --run yourprogram.enso
```
When executed on [GraalVM 21.3.0](http://graalvm.org) these options instruct the
When executed on [GraalVM 22.3.0](http://graalvm.org) these options instruct the
_Graal/Truffle compiler_ to dump files into `graal_dumps/_sometimestamp_`
directory. Generating these files takes a while - make sure `yourprogram.enso`
runs long enough for the system to warmup, compile the code and run at _full