diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index 84fcba88ff..ac094e360a 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -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 diff --git a/.github/workflows/release-publish-edition.yml b/.github/workflows/release-publish-edition.yml index c232e7e338..c7bd98e606 100644 --- a/.github/workflows/release-publish-edition.yml +++ b/.github/workflows/release-publish-edition.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ccc904e17..e817dda59d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/build.sbt b/build.sbt index df377fbd83..362151c72e 100644 --- a/build.sbt +++ b/build.sbt @@ -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, diff --git a/build/build/src/engine/context.rs b/build/build/src/engine/context.rs index 373b6ec052..056c6cf270 100644 --- a/build/build/src/engine/context.rs +++ b/build/build/src/engine/context.rs @@ -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 { diff --git a/build/ci_utils/src/cache/goodie/graalvm.rs b/build/ci_utils/src/cache/goodie/graalvm.rs index ea01336e61..e150baf1e5 100644 --- a/build/ci_utils/src/cache/goodie/graalvm.rs +++ b/build/ci_utils/src/cache/goodie/graalvm.rs @@ -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)); } } diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 55ad2fae1d..eaa062e110 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -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) diff --git a/docs/infrastructure/native-image.md b/docs/infrastructure/native-image.md index b01097c501..8ae0c49a5a 100644 --- a/docs/infrastructure/native-image.md +++ b/docs/infrastructure/native-image.md @@ -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 diff --git a/docs/infrastructure/upgrading-graalvm.md b/docs/infrastructure/upgrading-graalvm.md index 2838a96e55..9496cb5b47 100644 --- a/docs/infrastructure/upgrading-graalvm.md +++ b/docs/infrastructure/upgrading-graalvm.md @@ -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. diff --git a/engine/language-server/src/main/scala/org/enso/languageserver/io/ObservableOutputStream.scala b/engine/language-server/src/main/scala/org/enso/languageserver/io/ObservableOutputStream.scala index a292ca6ee4..6cb12517ff 100644 --- a/engine/language-server/src/main/scala/org/enso/languageserver/io/ObservableOutputStream.scala +++ b/engine/language-server/src/main/scala/org/enso/languageserver/io/ObservableOutputStream.scala @@ -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) } } diff --git a/engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/jni-config.json b/engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/jni-config.json similarity index 100% rename from engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/jni-config.json rename to engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/jni-config.json diff --git a/engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/predefined-classes-config.json b/engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/predefined-classes-config.json similarity index 100% rename from engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/predefined-classes-config.json rename to engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/predefined-classes-config.json diff --git a/engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/proxy-config.json b/engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/proxy-config.json similarity index 100% rename from engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/proxy-config.json rename to engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/proxy-config.json diff --git a/engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/reflect-config.json b/engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/reflect-config.json similarity index 100% rename from engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/reflect-config.json rename to engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/reflect-config.json diff --git a/engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/resource-config.json b/engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/resource-config.json similarity index 100% rename from engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/resource-config.json rename to engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/resource-config.json diff --git a/engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/serialization-config.json b/engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/serialization-config.json similarity index 100% rename from engine/runner-native/src/main/resources/META-INF/native-image/org/enso/runner/serialization-config.json rename to engine/runner/src/main/resources/META-INF/native-image/org/enso/runner/serialization-config.json diff --git a/engine/runner-native/src/test/resources/Factorial.enso b/engine/runner/src/test/resources/Factorial.enso similarity index 100% rename from engine/runner-native/src/test/resources/Factorial.enso rename to engine/runner/src/test/resources/Factorial.enso diff --git a/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java b/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java index 18b56ef095..099571d900 100644 --- a/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java +++ b/engine/runtime-instrument-repl-debugger/src/main/java/org/enso/interpreter/instrument/ReplDebuggerInstrument.java @@ -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) { diff --git a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java index c7943dbf20..2f03338801 100644 --- a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbContext.java @@ -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); } } diff --git a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java index a57b599a46..8516635ac0 100644 --- a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/EpbLanguage.java @@ -52,8 +52,8 @@ public class EpbLanguage extends TruffleLanguage { @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 diff --git a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java index 18badedc50..ed0d4996c2 100644 --- a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/node/CoercePrimitiveNode.java @@ -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; diff --git a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/ForeignParsingException.java b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/ForeignParsingException.java index 45da344ef0..5d0dce13bd 100644 --- a/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/ForeignParsingException.java +++ b/engine/runtime-language-epb/src/main/java/org/enso/interpreter/epb/runtime/ForeignParsingException.java @@ -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 + "'"; } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/Language.java b/engine/runtime/src/main/java/org/enso/interpreter/Language.java index 99b0d22594..b7e23b5fae 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/Language.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/Language.java @@ -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 { @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 { } /** - * 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 diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java index 77fbe615d0..738cf56514 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/BaseNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java index c652016a87..9eb2554372 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/ClosureRootNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java index 5190856ead..4a9750552f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/ExpressionNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/ExpressionNode.java index c3ad684f01..75f5a48c99 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/ExpressionNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/ExpressionNode.java @@ -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()); + } + } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java index 6cab9221b4..0abe83bcd3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/ApplicationNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java index 4f3256d112..d8487fad70 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java @@ -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()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java index d017dfcead..4fd7ccb5bc 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/LoopingCallOptimiserNode.java @@ -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("", FrameSlotKind.Object); - resultSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); - argsSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); - stateSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); - callerInfoSlot = descriptor.findOrAddFrameSlot("", FrameSlotKind.Object); + var descrBuilder = FrameDescriptor.newBuilder(); + functionSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "", null); + resultSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "", null); + argsSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "", null); + stateSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "", null); + callerInfoSlotIdx = descrBuilder.addSlot(FrameSlotKind.Object, "", 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()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/BlockNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/BlockNode.java index 3013f2cbd4..a7e4ff525e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/BlockNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/function/BlockNode.java @@ -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> 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 tag) { return super.hasTag(tag) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java index 3dbc36765b..b39319df53 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/callable/thunk/ForceNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java index e7f6fcd082..128a0ec527 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/caseexpr/CaseNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/permission/PermissionGuardNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/permission/PermissionGuardNode.java index 1c3068975c..8c26a3ca64 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/permission/PermissionGuardNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/controlflow/permission/PermissionGuardNode.java @@ -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; diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java index 486bddff19..612431fde8 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/meta/IsSameObjectNode.java @@ -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. + * + *

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); } } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/JoinThreadNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/JoinThreadNode.java index 30dbfa360f..3895218d51 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/JoinThreadNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/builtin/special/JoinThreadNode.java @@ -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 = "") public class JoinThreadNode extends Node { + @TruffleBoundary public Object execute(Object self) { try { ((Thread) self).join(); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java index b28597674a..7418ef8d74 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/debug/EvalNode.java @@ -79,7 +79,7 @@ public abstract class EvalNode extends BaseNode { ClosureRootNode framedNode = ClosureRootNode.build( context.getLanguage(), localScope, moduleScope, expr, null, "", false, false); - return Truffle.getRuntime().createCallTarget(framedNode); + return framedNode.getCallTarget(); } @Specialization( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java index 9ff6b0284f..9dd3bbe27d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/AssignmentNode.java @@ -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(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/ReadLocalVariableNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/ReadLocalVariableNode.java index a1108369e0..24e20ca24e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/scope/ReadLocalVariableNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/scope/ReadLocalVariableNode.java @@ -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). + * + *

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()); } /** diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java index ef7c25bbb5..a069197b99 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/UnresolvedSymbol.java @@ -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(); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 01df32d4b4..12b7a94d17 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -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()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java index d85fa64d07..9a2be042c3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/function/Function.java @@ -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); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java index a9acaacc35..d08c15e71e 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/data/Type.java @@ -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( diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java index 37854cbab2..80cdda5349 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/error/DataflowError.java @@ -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, diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/DebugLocalScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/DebugLocalScope.java new file mode 100644 index 0000000000..9bc9464d0c --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/DebugLocalScope.java @@ -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. + * + *

Some of the features that remain to be implemented are: + * + *

    + *
  • Module scopes. How to display imports in chrome devtools? Get inspiration from Python? + *
  • Evaluation of an arbitrary expression + *
+ */ +@ExportLibrary(InteropLibrary.class) +public class DebugLocalScope implements TruffleObject { + private final EnsoRootNode rootNode; + + /** All the bindings, including the parent scopes. */ + private final Map allBindings; + + /** + * The inner lists represent particular scope in a scope hierarchy. For example, for the following + * snippet: + * + *
+   * func =
+   *     x = 1
+   *     inner_func =
+   *         y = 2
+   *         y
+   *     inner_func
+   * 
+ * + * the value of this field (for `inner_func` scope) would be {@code [['x'], ['y']]} + */ + private final List> 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> 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> gatherBindingsByLevels(Map bindings) { + int maxParentLevel = + bindings.values().stream() + .max(Comparator.comparingInt(FramePointer::getParentLevel)) + .orElseThrow() + .getParentLevel(); + + // Get all binding names for a particular parent level + List> bindingsByLevels = new ArrayList<>(maxParentLevel + 1); + for (int level = 0; level < maxParentLevel + 1; level++) { + final int finalLevel = level; + List 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> 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 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 memberNames; + + ScopeMembers(List 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(); + } + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/FramePointer.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/FramePointer.java index e0ca6666a9..592133d966 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/FramePointer.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/FramePointer.java @@ -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; } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala b/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala index 0cbe885687..973c93a910 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/SerializationManager.scala @@ -51,7 +51,7 @@ class SerializationManager(compiler: Compiler) { TimeUnit.SECONDS, new LinkedBlockingDeque[Runnable](), (runnable: Runnable) => { - env.createThread(runnable) + env.createSystemThread(runnable) } ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 0063a7547a..1c683e5aae 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -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 diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index eb6e382284..6f906221f6 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -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. * diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala index f5da4629e5..65e83c4209 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/scope/LocalScope.scala @@ -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 = "<>" + + /** 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) + ) } diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java index ae2f9ef25c..c13f46c117 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/DebuggingEnsoTest.java @@ -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 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 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 expectedValues) { + Map 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 getStackFramesFromEvent(SuspendedEvent event) { + List 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(); - 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 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 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) diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala index 6daaf6a7db..bb4782732b 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/AliasAnalysisTest.scala @@ -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] diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/SuspendedArgumentsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/SuspendedArgumentsTest.scala index c7d21750fc..8574abee62 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/SuspendedArgumentsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/SuspendedArgumentsTest.scala @@ -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 = diff --git a/project/DistributionPackage.scala b/project/DistributionPackage.scala index 88fffdd7ab..fccc6775ae 100644 --- a/project/DistributionPackage.scala +++ b/project/DistributionPackage.scala @@ -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(" ")}'" + ) } } diff --git a/project/NativeImage.scala b/project/NativeImage.scala index 66eca47ffb..18242f8dde 100644 --- a/project/NativeImage.scala +++ b/project/NativeImage.scala @@ -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) { diff --git a/test/Tests/src/Network/Http_Spec.enso b/test/Tests/src/Network/Http_Spec.enso index 06e6c49275..369c36bf36 100644 --- a/test/Tests/src/Network/Http_Spec.enso +++ b/test/Tests/src/Network/Http_Spec.enso @@ -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": "", diff --git a/tools/ci/docker/Dockerfile b/tools/ci/docker/Dockerfile index bf34a7324f..cb52d1cfc2 100644 --- a/tools/ci/docker/Dockerfile +++ b/tools/ci/docker/Dockerfile @@ -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 diff --git a/tools/enso4igv/README.md b/tools/enso4igv/README.md index 3784ce7c55..e66d651ec4 100644 --- a/tools/enso4igv/README.md +++ b/tools/enso4igv/README.md @@ -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