From 53b24903e5cc69c9becd62a98c8a441e51619f95 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jun 2025 19:17:14 +0200 Subject: [PATCH 01/60] Separating JVM functionality into own jvm-channel project --- build.sbt | 37 +++++++++++- engine/runner/src/main/java/module-info.java | 1 + .../src/main/java/org/enso/runner/Main.java | 2 +- .../src/main/java/module-info.java | 8 +++ .../java/org/enso/jvm/channel}/Channel.java | 57 +++++++++++++++---- .../main/java/org/enso/jvm/channel}/JNI.java | 2 +- .../java/org/enso/jvm/channel}/JNIBoot.java | 2 +- .../org/enso/jvm/channel}/JNIDirectives.java | 2 +- .../enso/jvm/channel}/JNINativeInterface.java | 2 +- .../main/java/org/enso/jvm/channel}/JVM.java | 2 +- .../java/org/enso/jvm/channel}/PosixJVM.java | 2 +- .../org/enso/jvm/channel}/WindowsJVM.java | 4 +- .../src/main/java/module-info.java | 6 +- .../org/enso/os/environment/jni/TestMain.java | 1 + .../os/environment/jni/LoadClassTest.java | 13 ++--- 15 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 lib/java/jvm-channel/src/main/java/module-info.java rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/Channel.java (89%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/JNI.java (99%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/JNIBoot.java (97%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/JNIDirectives.java (97%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/JNINativeInterface.java (99%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/JVM.java (99%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/PosixJVM.java (98%) rename lib/java/{os-environment/src/main/java/org/enso/os/environment/jni => jvm-channel/src/main/java/org/enso/jvm/channel}/WindowsJVM.java (94%) diff --git a/build.sbt b/build.sbt index 2b5e987563ba..1cc050631b36 100644 --- a/build.sbt +++ b/build.sbt @@ -347,6 +347,7 @@ lazy val enso = (project in file(".")) `interpreter-dsl-test`, `jna-wrapper`, `json-rpc-server`, + `jvm-channel`, `language-server`, `language-server-deps-wrapper`, launcher, @@ -791,6 +792,7 @@ lazy val componentModulesPaths = (`logging-utils-akka` / Compile / exportedModuleBin).value, (`logging-service` / Compile / exportedModuleBin).value, (`logging-service-logback` / Compile / exportedModuleBin).value, + (`jvm-channel` / Compile / exportedModuleBin).value, (`os-environment` / Compile / exportedModuleBin).value, (`pkg` / Compile / exportedModuleBin).value, (`refactoring-utils` / Compile / exportedModuleBin).value, @@ -2789,10 +2791,13 @@ lazy val `runtime-language-epb` = "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion ), Compile / internalModuleDependencies := Seq( + (`jvm-channel` / Compile / exportedModule).value, (`ydoc-polyfill` / Compile / exportedModule).value, (`runtime-utils` / Compile / exportedModule).value ) ) + .dependsOn(`jvm-channel`) + .dependsOn(`persistance-dsl` % "provided") lazy val `runtime-language-arrow` = (project in file("engine/runtime-language-arrow")) @@ -3865,6 +3870,7 @@ lazy val `engine-runner` = project (`profiling-utils` / Compile / exportedModule).value, (`semver` / Compile / exportedModule).value, (`cli` / Compile / exportedModule).value, + (`jvm-channel` / Compile / exportedModule).value, (`os-environment` / Compile / exportedModule).value, (`distribution-manager` / Compile / exportedModule).value, (`editions` / Compile / exportedModule).value, @@ -4313,12 +4319,39 @@ lazy val `benchmarks-common` = ) .dependsOn(`polyglot-api`) +lazy val `jvm-channel` = + project + .in(file("lib/java/jvm-channel")) + .enablePlugins(JPMSPlugin) + .settings( + customFrgaalJavaCompilerSettings("24"), + scalaModuleDependencySetting, + libraryDependencies ++= slf4jApi ++ Seq( + "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", + "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", + "junit" % "junit" % junitVersion % Test, + "com.github.sbt" % "junit-interface" % junitIfVersion % Test + ), + Compile / moduleDependencies ++= slf4jApi ++ Seq( + "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion, + "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion, + "org.graalvm.sdk" % "word" % graalMavenPackagesVersion + ), + Compile / internalModuleDependencies ++= Seq( + (`engine-common` / Compile / exportedModule).value, + (`persistance` / Compile / exportedModule).value + ) + ) + .dependsOn(`engine-common`) + .dependsOn(`persistance`) + .dependsOn(`persistance-dsl` % "provided") + lazy val `os-environment` = project .in(file("lib/java/os-environment")) .enablePlugins(JPMSPlugin) .settings( - customFrgaalJavaCompilerSettings("24"), + frgaalJavaCompilerSetting, scalaModuleDependencySetting, libraryDependencies ++= slf4jApi ++ Seq( "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", @@ -4337,6 +4370,7 @@ lazy val `os-environment` = Compile / internalModuleDependencies ++= Seq( (`engine-common` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, + (`jvm-channel` / Compile / exportedModule).value, (`logging-utils` / Compile / exportedModule).value, (`logging-config` / Compile / exportedModule).value ), @@ -4391,6 +4425,7 @@ lazy val `os-environment` = .value, Test / fork := true ) + .dependsOn(`jvm-channel`) .dependsOn(`persistance`) .dependsOn(`persistance-dsl` % "provided") .dependsOn(`engine-common`) diff --git a/engine/runner/src/main/java/module-info.java b/engine/runner/src/main/java/module-info.java index 87205148d16f..ff1972894f0d 100644 --- a/engine/runner/src/main/java/module-info.java +++ b/engine/runner/src/main/java/module-info.java @@ -8,6 +8,7 @@ requires org.enso.librarymanager; requires org.enso.logging.config; requires org.enso.logging.utils; + requires org.enso.jvm.channel; requires org.enso.os.environment; requires org.enso.runtime.parser; requires org.enso.runtime.version.manager; diff --git a/engine/runner/src/main/java/org/enso/runner/Main.java b/engine/runner/src/main/java/org/enso/runner/Main.java index cf4e83cafd21..6a0b9a4c7d25 100644 --- a/engine/runner/src/main/java/org/enso/runner/Main.java +++ b/engine/runner/src/main/java/org/enso/runner/Main.java @@ -35,8 +35,8 @@ import org.enso.distribution.DistributionManager; import org.enso.distribution.Environment; import org.enso.editions.DefaultEdition; +import org.enso.jvm.channel.JVM; import org.enso.libraryupload.LibraryUploader.UploadFailedError; -import org.enso.os.environment.jni.JVM; import org.enso.pkg.Contact; import org.enso.pkg.PackageManager; import org.enso.pkg.PackageManager$; diff --git a/lib/java/jvm-channel/src/main/java/module-info.java b/lib/java/jvm-channel/src/main/java/module-info.java new file mode 100644 index 000000000000..3d8f38e1b417 --- /dev/null +++ b/lib/java/jvm-channel/src/main/java/module-info.java @@ -0,0 +1,8 @@ +module org.enso.jvm.channel { + requires org.enso.engine.common; + requires org.enso.persistance; + requires org.graalvm.nativeimage; + requires org.slf4j; + + exports org.enso.jvm.channel; +} diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java similarity index 89% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/Channel.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 34661819f9eb..238948f61c12 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import java.io.IOException; import java.lang.foreign.FunctionDescriptor; @@ -87,19 +87,48 @@ private Channel(long id, Persistance.Pool pool, long isolate, long callbackFn) { this.callbackFn = Linker.nativeLinker().downcallHandle(fnCallbackAddress, fnDescriptor); } + /** + * Mock constructor. Creates a channel that simulates sending of the messages inside of the same + * JVM. Useful for testing. + */ + private Channel(long id, Persistance.Pool pool) { + if (ImageInfo.inImageCode()) { + throw new IllegalStateException("Only usable in HotSpot"); + } + this.id = id; + this.pool = pool; + this.isolate = -2; + this.callbackFn = null; + this.env = null; + this.channelClass = null; + this.channelHandle = null; + } + /** * Factory method to initialize the Channel in the SubstrateVM. * - * @param jvm instance of HotSpot JVM to connect to + * @param jvm instance of HotSpot JVM to connect to (can be {@code null} to create a mock channel + * inside of a single JVM) * @param poolClass the class which has public default constructor and can supply an instance of * persistance pool to use for communication * @return channel for sending messages to the HotSpot JVM */ - public static synchronized Channel create( // - JVM jvm, // - Class> poolClass // - ) { + public static synchronized Channel create( + JVM jvm, Class> poolClass) { + Persistance.Pool pool; + try { + pool = poolClass.getConstructor().newInstance().get(); + } catch (ReflectiveOperationException ex) { + throw new IllegalArgumentException(ex); + } var id = idCounter++; + if (jvm == null) { + return new Channel(id, pool); + } + + if (!ImageInfo.inImageCode()) { + throw new IllegalStateException("Only usable from SubstrateVM"); + } var e = jvm.env(); var classNameWithSlashes = Channel.class.getName().replace('.', '/'); try (var classInC = CTypeConversion.toCString(classNameWithSlashes); @@ -119,7 +148,6 @@ public static synchronized Channel create( // var handleMethod = fn.getGetStaticMethodID().call(e, channelClass, handleInC.get(), handleSigInC.get()); - var pool = poolClass.getConstructor().newInstance().get(); var channel = new Channel(id, pool, e, channelClass, handleMethod); var arg = StackValue.get(4, JNI.JValue.class); @@ -133,8 +161,6 @@ public static synchronized Channel create( // ID_TO_CHANNEL.put(id, channel); return channel; - } catch (ReflectiveOperationException ex) { - throw new IllegalStateException(ex); } } @@ -199,12 +225,12 @@ private static long acceptRequestFromHotSpotJvm( } } - private static long handleWithChannel(Channel channel, ByteBuffer buf) throws Throwable { + private static long handleWithChannel(Channel channel, ByteBuffer buf) throws IOException { var ref = channel.pool.read(buf, null); var msg = ref.get(Function.class); @SuppressWarnings("unchecked") var res = msg.apply(channel); - var bytes = Persistables.POOL.write(res, null); + var bytes = channel.pool.write(res, null); buf.put(0, bytes); return bytes.length; } @@ -232,6 +258,13 @@ private long toSubstrateMessage(MemorySegment seg) { } } + private long toDirectMessage(ByteBuffer buf) throws IOException { + buf.position(0); + var len = handleWithChannel(this, buf); + buf.position(0); + return len; + } + private void checkForException(JNI.JNIEnv e) { var fn = e.getFunctions(); if (fn.getExceptionCheck().call(e)) { @@ -280,7 +313,7 @@ private R executeImpl( // var memory = MemorySegment.ofBuffer(buffer); memory.copyFrom(MemorySegment.ofArray(bytes)); address = memory.address(); - len = toSubstrateMessage(memory); + len = isolate == -2 ? toDirectMessage(buffer) : toSubstrateMessage(memory); } if (len == -2) { // signals exception diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNI.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java similarity index 99% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNI.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java index 630f8e14fc34..b3d239e87aa1 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNI.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import org.graalvm.nativeimage.c.CContext; import org.graalvm.nativeimage.c.constant.CConstant; diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNIBoot.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNIBoot.java similarity index 97% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNIBoot.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNIBoot.java index 4b1d15a13332..6c88a2844ecb 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNIBoot.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNIBoot.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import org.graalvm.nativeimage.c.CContext; import org.graalvm.nativeimage.c.function.CFunctionPointer; diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNIDirectives.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNIDirectives.java similarity index 97% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNIDirectives.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNIDirectives.java index 81a0d0d9b19e..6f71a54d218c 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNIDirectives.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNIDirectives.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import java.io.File; import java.util.List; diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNINativeInterface.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNINativeInterface.java similarity index 99% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNINativeInterface.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNINativeInterface.java index bc79381c0f8a..8d54c7a28afe 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JNINativeInterface.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNINativeInterface.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import org.graalvm.nativeimage.c.CContext; import org.graalvm.nativeimage.c.struct.CField; diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVM.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JVM.java similarity index 99% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVM.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JVM.java index af55973123de..253b517c3cb7 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVM.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JVM.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import java.io.File; import java.util.ArrayList; diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/PosixJVM.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/PosixJVM.java similarity index 98% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/PosixJVM.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/PosixJVM.java index fa3c3a99fcc3..ff04423fe691 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/PosixJVM.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/PosixJVM.java @@ -1,4 +1,4 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import java.io.File; import java.util.List; diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/WindowsJVM.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/WindowsJVM.java similarity index 94% rename from lib/java/os-environment/src/main/java/org/enso/os/environment/jni/WindowsJVM.java rename to lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/WindowsJVM.java index 62c63a2ca6eb..f304ce4f1906 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/WindowsJVM.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/WindowsJVM.java @@ -1,10 +1,10 @@ -package org.enso.os.environment.jni; +package org.enso.jvm.channel; import static org.graalvm.nativeimage.c.function.CFunction.Transition.NO_TRANSITION; import java.io.File; import java.util.List; -import org.enso.os.environment.jni.JNIBoot.JNICreateJavaVMPointer; +import org.enso.jvm.channel.JNIBoot.JNICreateJavaVMPointer; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.c.CContext; diff --git a/lib/java/os-environment/src/main/java/module-info.java b/lib/java/os-environment/src/main/java/module-info.java index f55f96b50114..366a6bad04c1 100644 --- a/lib/java/os-environment/src/main/java/module-info.java +++ b/lib/java/os-environment/src/main/java/module-info.java @@ -8,10 +8,14 @@ requires org.graalvm.nativeimage; requires org.slf4j; requires org.apache.commons.io; + requires org.enso.jvm.channel; exports org.enso.os.environment; - exports org.enso.os.environment.jni; exports org.enso.os.environment.chdir; exports org.enso.os.environment.trash; exports org.enso.os.environment.directories; + + // needed to perform tests + opens org.enso.os.environment.jni to + org.enso.jvm.channel; } diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java index 787790bc5fa1..5dba57038321 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java +++ b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import org.enso.jvm.channel.Channel; import org.enso.persist.Persistable; final class TestMain { diff --git a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java index 27a6923aa6c0..9063ae5fc2d4 100644 --- a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java +++ b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java @@ -1,16 +1,14 @@ package org.enso.os.environment.jni; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; import java.math.BigInteger; import java.nio.file.Files; import java.util.Random; -import org.enso.os.environment.jni.JNI.JValue; -import org.graalvm.nativeimage.StackValue; -import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.enso.jvm.channel.Channel; +import org.enso.jvm.channel.JVM; import org.junit.Before; import org.junit.Test; @@ -42,10 +40,6 @@ private static JVM jvm() { return impl; } - private static JNI.JNIEnv env() { - return jvm().env(); - } - private Channel channel; @Before @@ -53,7 +47,7 @@ public void initializeChannel() throws Exception { channel = Channel.create(jvm(), JVMPeer.class); } - @Test + /* @Test public void invokeParseShortMethod() { var env = env(); assertTrue("JNI created", env.isNonNull()); @@ -121,6 +115,7 @@ public void setSystemProperty() { strReleaseFn.call(env, res, chars); } } + */ @Test public void executeMainClass() throws Exception { From f0cff3dc9572d01534e0efd66a004340d9c16b1f Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 6 Jun 2025 19:18:41 +0200 Subject: [PATCH 02/60] Unit test using Channel functionality in a single JVM --- .../src/main/java/module-info.java | 1 + .../epb/ChannelInSingleJvmTest.java | 43 +++++++++++++++++++ .../epb/EpbLanguageDependenciesTest.java | 2 + .../persist/impl/PersistableProcessor.java | 10 ++++- 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java diff --git a/engine/runtime-language-epb/src/main/java/module-info.java b/engine/runtime-language-epb/src/main/java/module-info.java index 17a4474d0827..83718ad76449 100644 --- a/engine/runtime-language-epb/src/main/java/module-info.java +++ b/engine/runtime-language-epb/src/main/java/module-info.java @@ -4,6 +4,7 @@ requires org.graalvm.truffle; requires org.enso.runtime.utils; requires org.enso.ydoc.polyfill; + requires org.enso.jvm.channel; provides com.oracle.truffle.api.provider.TruffleLanguageProvider with org.enso.interpreter.epb.EpbLanguageProvider; diff --git a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java b/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java new file mode 100644 index 000000000000..46c15b697b09 --- /dev/null +++ b/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java @@ -0,0 +1,43 @@ +package org.enso.interpreter.epb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.function.Function; +import org.enso.jvm.channel.Channel; +import org.enso.persist.Persistable; +import org.junit.Test; + +public class ChannelInSingleJvmTest { + @Test + public void exchangeMessages() { + var ch = Channel.create(null, Persistables.class); + + var msg = new Increment(10); + + var newMsg = ch.execute(Increment.class, msg); + + assertNotNull("Got a value", newMsg); + assertEquals("10 + 1", 11, newMsg.valueToIncrement()); + assertEquals("Original value remains", 10, msg.valueToIncrement()); + } + + @Persistable(id = 8341) + static final class Increment implements Function { + int valueToIncrement; + + Increment(int valueToIncrement) { + this.valueToIncrement = valueToIncrement; + } + + int valueToIncrement() { + return valueToIncrement; + } + + @Override + public Increment apply(Channel t) { + valueToIncrement++; + return this; + } + } +} diff --git a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java b/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java index cedca482d1da..648a3be6e502 100644 --- a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java +++ b/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java @@ -1,5 +1,6 @@ import static org.junit.Assert.fail; +import org.junit.Ignore; import org.junit.Test; public class EpbLanguageDependenciesTest { @@ -35,6 +36,7 @@ public void avoidCats() throws Exception { fail("No class should be found: " + c); } + @Ignore // TBD: brought in by persistance @Test(expected = ClassNotFoundException.class) public void avoidSlf4j() throws Exception { var c = Class.forName("org.slf4j.Logger"); diff --git a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java index 7f763245f40b..fdf0fc556760 100644 --- a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java +++ b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java @@ -92,9 +92,12 @@ public boolean process(Set annotations, RoundEnvironment w.append("package " + entry.getKey() + ";\n"); w.append("import org.enso.persist.Persistance;\n"); - w.append("public final class Persistables extends Persistance.Pool {\n"); + w.append("import java.util.function.Supplier;\n"); + w.append( + "public final class Persistables extends Persistance.Pool implements" + + " Supplier {\n"); w.append(" public static final Persistance.Pool POOL = new Persistables();\n"); - w.append(" private Persistables() {\n"); + w.append(" public Persistables() {\n"); w.append(" super(\"").append(entry.getKey()).append("\","); var lineEnding = "\n"; for (var idName : props.entrySet()) { @@ -104,6 +107,9 @@ public boolean process(Set annotations, RoundEnvironment } w.append("\n );\n"); w.append(" }\n"); + w.append(" public Persistance.Pool get() {\n"); + w.append(" return this;\n"); + w.append(" }\n"); w.append("}\n"); } var out = processingEnv.getFiler().createResource(propsWhere, propsPkg, propsName); From 365d50deeeffa05602bccc7eb6e1867023d9ca5a Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 09:19:20 +0200 Subject: [PATCH 03/60] Testing ChannelInSingleJvm in jvm-channel project itself --- build.sbt | 3 --- engine/runtime-language-epb/src/main/java/module-info.java | 1 - .../org/enso/interpreter/epb/EpbLanguageDependenciesTest.java | 2 -- .../java/org/enso/jvm/channel}/ChannelInSingleJvmTest.java | 3 +-- 4 files changed, 1 insertion(+), 8 deletions(-) rename {engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb => lib/java/jvm-channel/src/test/java/org/enso/jvm/channel}/ChannelInSingleJvmTest.java (93%) diff --git a/build.sbt b/build.sbt index 1cc050631b36..f73ef67a0f36 100644 --- a/build.sbt +++ b/build.sbt @@ -2791,13 +2791,10 @@ lazy val `runtime-language-epb` = "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion ), Compile / internalModuleDependencies := Seq( - (`jvm-channel` / Compile / exportedModule).value, (`ydoc-polyfill` / Compile / exportedModule).value, (`runtime-utils` / Compile / exportedModule).value ) ) - .dependsOn(`jvm-channel`) - .dependsOn(`persistance-dsl` % "provided") lazy val `runtime-language-arrow` = (project in file("engine/runtime-language-arrow")) diff --git a/engine/runtime-language-epb/src/main/java/module-info.java b/engine/runtime-language-epb/src/main/java/module-info.java index 83718ad76449..17a4474d0827 100644 --- a/engine/runtime-language-epb/src/main/java/module-info.java +++ b/engine/runtime-language-epb/src/main/java/module-info.java @@ -4,7 +4,6 @@ requires org.graalvm.truffle; requires org.enso.runtime.utils; requires org.enso.ydoc.polyfill; - requires org.enso.jvm.channel; provides com.oracle.truffle.api.provider.TruffleLanguageProvider with org.enso.interpreter.epb.EpbLanguageProvider; diff --git a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java b/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java index 648a3be6e502..cedca482d1da 100644 --- a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java +++ b/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/EpbLanguageDependenciesTest.java @@ -1,6 +1,5 @@ import static org.junit.Assert.fail; -import org.junit.Ignore; import org.junit.Test; public class EpbLanguageDependenciesTest { @@ -36,7 +35,6 @@ public void avoidCats() throws Exception { fail("No class should be found: " + c); } - @Ignore // TBD: brought in by persistance @Test(expected = ClassNotFoundException.class) public void avoidSlf4j() throws Exception { var c = Class.forName("org.slf4j.Logger"); diff --git a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java similarity index 93% rename from engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java rename to lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java index 46c15b697b09..2574488c6603 100644 --- a/engine/runtime-language-epb/src/test/java/org/enso/interpreter/epb/ChannelInSingleJvmTest.java +++ b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java @@ -1,10 +1,9 @@ -package org.enso.interpreter.epb; +package org.enso.jvm.channel; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import java.util.function.Function; -import org.enso.jvm.channel.Channel; import org.enso.persist.Persistable; import org.junit.Test; From 837877d15ff84d50775d8f044b8b1535b9355718 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 09:57:35 +0200 Subject: [PATCH 04/60] jvm-interop module with dependency on Truffle --- build.sbt | 34 +++++++++++++++++-- .../src/main/java/module-info.java | 11 ++++++ .../org/enso/jvm/interop/OtherJvmObject.java | 5 +++ .../enso/jvm/interop/OtherJvmObjectTest.java | 12 +++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 lib/java/jvm-interop/src/main/java/module-info.java create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java create mode 100644 lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java diff --git a/build.sbt b/build.sbt index f73ef67a0f36..da61670219c8 100644 --- a/build.sbt +++ b/build.sbt @@ -348,6 +348,7 @@ lazy val enso = (project in file(".")) `jna-wrapper`, `json-rpc-server`, `jvm-channel`, + `jvm-interop`, `language-server`, `language-server-deps-wrapper`, launcher, @@ -793,6 +794,7 @@ lazy val componentModulesPaths = (`logging-service` / Compile / exportedModuleBin).value, (`logging-service-logback` / Compile / exportedModuleBin).value, (`jvm-channel` / Compile / exportedModuleBin).value, + (`jvm-interop` / Compile / exportedModuleBin).value, (`os-environment` / Compile / exportedModuleBin).value, (`pkg` / Compile / exportedModuleBin).value, (`refactoring-utils` / Compile / exportedModuleBin).value, @@ -4322,7 +4324,7 @@ lazy val `jvm-channel` = .enablePlugins(JPMSPlugin) .settings( customFrgaalJavaCompilerSettings("24"), - scalaModuleDependencySetting, + autoScalaLibrary := false, libraryDependencies ++= slf4jApi ++ Seq( "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", @@ -4343,13 +4345,41 @@ lazy val `jvm-channel` = .dependsOn(`persistance`) .dependsOn(`persistance-dsl` % "provided") +lazy val `jvm-interop` = + project + .in(file("lib/java/jvm-interop")) + .enablePlugins(JPMSPlugin) + .settings( + frgaalJavaCompilerSetting, + autoScalaLibrary := false, + libraryDependencies ++= slf4jApi ++ Seq( + "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided", + "org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided", + "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test, + "junit" % "junit" % junitVersion % Test, + "com.github.sbt" % "junit-interface" % junitIfVersion % Test + ), + Compile / moduleDependencies ++= slf4jApi ++ Seq( + "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion, + "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion, + "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion, + "org.graalvm.sdk" % "word" % graalMavenPackagesVersion + ), + Compile / internalModuleDependencies ++= Seq( + (`jvm-channel` / Compile / exportedModule).value, + (`engine-common` / Compile / exportedModule).value, + (`persistance` / Compile / exportedModule).value + ) + ) + .dependsOn(`jvm-channel`) + .dependsOn(`persistance-dsl` % "provided") + lazy val `os-environment` = project .in(file("lib/java/os-environment")) .enablePlugins(JPMSPlugin) .settings( frgaalJavaCompilerSetting, - scalaModuleDependencySetting, libraryDependencies ++= slf4jApi ++ Seq( "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", diff --git a/lib/java/jvm-interop/src/main/java/module-info.java b/lib/java/jvm-interop/src/main/java/module-info.java new file mode 100644 index 000000000000..97267ac8c04f --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/module-info.java @@ -0,0 +1,11 @@ +module org.enso.jvm.interop { + // requires org.slf4j; + // requires org.graalvm.nativeimage; + // requires org.graalvm.polyglot; + // requires org.graalvm.word; + // requires org.enso.engine.common; + + requires org.graalvm.truffle; + requires org.enso.jvm.channel; + requires org.enso.persistance; +} diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java new file mode 100644 index 000000000000..68c63a7f33fc --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -0,0 +1,5 @@ +package org.enso.jvm.interop; + +import com.oracle.truffle.api.interop.TruffleObject; + +final class OtherJvmObject implements TruffleObject {} diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java new file mode 100644 index 000000000000..9ea8ad8316b8 --- /dev/null +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -0,0 +1,12 @@ +package org.enso.jvm.interop; + +import org.junit.Assert; +import org.junit.Test; + +public class OtherJvmObjectTest { + @Test + public void connect() { + var to = new OtherJvmObject(); + Assert.fail("OKeyish" + to); + } +} From 701006efbb860468adfee5b5c50e9579148ba0cb Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 12:26:27 +0200 Subject: [PATCH 05/60] The enso language may not be enabled --- .../main/java/org/enso/test/utils/ContextUtils.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java index f69b7b82ea78..3d7e42938807 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java @@ -277,8 +277,14 @@ public Value asValue(Object obj) { */ private Value executeInContext(Callable callable) { var ctx = currentCtx(); - // Force initialization of the context - ctx.eval("enso", "value = 0"); + try { + // Force initialization of the context + ctx.eval("enso", "value = 0"); + } catch (Exception ex) { + if (!"Access to language 'enso' is not permitted. ".equals(ex.getMessage())) { + throw ex; + } + } var err = new Exception[1]; ctx.getPolyglotBindings() .putMember( From 9817a8e905620d07b1b6678a56e3a4a1daa576d0 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 12:50:59 +0200 Subject: [PATCH 06/60] Using Object[] as class argument yield proper AP error --- .../persist/impl/PersistableProcessor.java | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java index fdf0fc556760..0dc060edd29b 100644 --- a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java +++ b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java @@ -5,6 +5,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.TreeMap; @@ -25,6 +26,7 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor9; +import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import javax.tools.StandardLocation; import org.openide.util.lookup.ServiceProvider; @@ -127,6 +129,7 @@ public boolean process(Set annotations, RoundEnvironment } private String findFqn(Element e) { + Objects.requireNonNull(e); var inPackage = findNameInPackage(e); var pkg = processingEnv.getElementUtils().getPackageOf(e); return pkg.getQualifiedName() + "." + inPackage; @@ -226,6 +229,7 @@ && isVisibleFrom(e, orig)) var pkgName = eu.getPackageOf(orig).getQualifiedName().toString(); var className = "Persist" + findNameInPackage(typeElem).replace(".", "_"); var fo = processingEnv.getFiler().createSourceFile(pkgName + "." + className, orig); + var ok = true; try (var w = fo.openWriter()) { var id = readAnnoValue(anno, "id"); registerPersistablesClass(pkgName, className, Integer.parseInt(id)); @@ -256,8 +260,11 @@ && isVisibleFrom(e, orig)) if (tu.isSameType(eu.getTypeElement("java.lang.String").asType(), v.asType())) { w.append(" var ").append(v.getSimpleName()).append(" = in.readUTF();\n"); } else if (!v.asType().getKind().isPrimitive()) { - var type = tu.erasure(v.asType()); - var elem = (TypeElement) tu.asElement(type); + var elem = findTypeOrNull(tu, v, orig); + if (elem == null) { + ok = false; + continue; + } var name = findFqn(elem); if (canInline && shouldInline(elem)) { w.append(" var ") @@ -318,8 +325,11 @@ && isVisibleFrom(e, orig)) if (tu.isSameType(eu.getTypeElement("java.lang.String").asType(), v.asType())) { w.append(" out.writeUTF(obj.").append(v.getSimpleName()).append("());\n"); } else if (!v.asType().getKind().isPrimitive()) { - var type = tu.erasure(v.asType()); - var elem = (TypeElement) tu.asElement(type); + var elem = findTypeOrNull(tu, v, orig); + if (elem == null) { + ok = false; + continue; + } var name = findFqn(elem); if (canInline && shouldInline(elem)) { w.append(" out.writeInline(") @@ -350,7 +360,18 @@ && isVisibleFrom(e, orig)) w.append(" }\n"); w.append("}\n"); } - return true; + return ok; + } + + private TypeElement findTypeOrNull(Types tu, VariableElement v, Element orig) { + var type = tu.erasure(v.asType()); + var elem = (TypeElement) tu.asElement(type); + if (elem == null) { + processingEnv + .getMessager() + .printMessage(Kind.ERROR, "No persistable class for " + type, orig); + } + return elem; } private void registerPersistablesClass(Element elem, AnnotationMirror anno) { From 9c129deefaae7041fbbc86a7f5d710cf31e3efa5 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 13:36:43 +0200 Subject: [PATCH 07/60] Messages to find out if an object is an array --- build.sbt | 5 + .../org/enso/jvm/interop/OtherJvmObject.java | 29 ++++- .../org/enso/jvm/interop/OtherMessage.java | 101 ++++++++++++++++++ .../org/enso/jvm/interop/OtherResult.java | 6 ++ .../enso/jvm/interop/OtherJvmObjectTest.java | 40 ++++++- 5 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java diff --git a/build.sbt b/build.sbt index da61670219c8..822cd2bfa8db 100644 --- a/build.sbt +++ b/build.sbt @@ -4325,6 +4325,8 @@ lazy val `jvm-channel` = .settings( customFrgaalJavaCompilerSettings("24"), autoScalaLibrary := false, + (Test / fork) := true, + commands += WithDebugCommand.withDebug, libraryDependencies ++= slf4jApi ++ Seq( "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", @@ -4352,6 +4354,8 @@ lazy val `jvm-interop` = .settings( frgaalJavaCompilerSetting, autoScalaLibrary := false, + (Test / fork) := true, + commands += WithDebugCommand.withDebug, libraryDependencies ++= slf4jApi ++ Seq( "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided", "org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided", @@ -4373,6 +4377,7 @@ lazy val `jvm-interop` = ) .dependsOn(`jvm-channel`) .dependsOn(`persistance-dsl` % "provided") + .dependsOn(`test-utils` % Test) lazy val `os-environment` = project diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 68c63a7f33fc..8b05e7e3c4bd 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -1,5 +1,32 @@ package org.enso.jvm.interop; +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 com.oracle.truffle.api.library.Message; +import com.oracle.truffle.api.library.ReflectionLibrary; +import java.util.List; +import org.enso.jvm.channel.Channel; -final class OtherJvmObject implements TruffleObject {} +@ExportLibrary(ReflectionLibrary.class) +final class OtherJvmObject implements TruffleObject { + private final Channel channel; + private final long id; + + OtherJvmObject(Channel channel, long id) { + this.channel = channel; + this.id = id; + } + + @ExportMessage + Object send(Message message, Object[] args) throws Exception { + if (message.getLibraryClass() != InteropLibrary.class) { + throw UnsupportedMessageException.create(); + } + var msg = new OtherMessage(id, message, List.of(args)); + var res = channel.execute(OtherResult.class, msg); + return res.value(); + } +} diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java new file mode 100644 index 000000000000..f78b04350d33 --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -0,0 +1,101 @@ +package org.enso.jvm.interop; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.Message; +import com.oracle.truffle.api.library.ReflectionLibrary; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.enso.jvm.channel.Channel; +import org.enso.persist.Persistable; +import org.enso.persist.Persistance; + +@Persistable(id = 81901) +record OtherMessage(long id, Message message, List args) + implements Function { + private static final Map OBJECTS = new HashMap<>(); + + @Override + public OtherResult apply(Channel t) { + try { + var receiver = OBJECTS.get(id); + assert receiver instanceof TruffleObject; + var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); + return new OtherResult(res); + } catch (Exception ex) { + return new OtherResult(ex); + } + } + + static synchronized long registerObject(TruffleObject obj) { + var size = OBJECTS.size(); + OBJECTS.put((long) size, obj); + return size; + } + + @Persistable(id = 81902) + static final class PersistTruffleMessage extends Persistance { + public PersistTruffleMessage() { + super(Message.class, true, 81902); + } + + @Override + protected void writeObject(Message obj, Output out) throws IOException { + assert InteropLibrary.class == obj.getLibraryClass(); + out.writeUTF(obj.getSimpleName()); + } + + @Override + protected Message readObject(Input in) throws IOException, ClassNotFoundException { + var name = in.readUTF(); + return Message.resolve(InteropLibrary.class, name); + } + } + + @Persistable(id = 81903) + static final class PersistList extends Persistance { + public PersistList() { + super(List.class, true, 81903); + } + + @Override + protected void writeObject(List obj, Output out) throws IOException { + var size = obj.size(); + out.writeInt(size); + for (var i = 0; i < size; i++) { + out.writeObject(obj.get(i)); + } + } + + @Override + protected List readObject(Input in) throws IOException, ClassNotFoundException { + var size = in.readInt(); + var arr = new ArrayList(size); + while (size-- > 0) { + arr.add(in.readObject()); + } + return arr; + } + } + + @Persistable(id = 81904) + static final class PersistBoolean extends Persistance { + public PersistBoolean() { + super(Boolean.class, true, 81904); + } + + @Override + protected void writeObject(Boolean obj, Output out) throws IOException { + out.writeBoolean(obj); + } + + @Override + protected Boolean readObject(Input in) throws IOException, ClassNotFoundException { + return in.readBoolean(); + } + } +} diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java new file mode 100644 index 000000000000..d60af03659d9 --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java @@ -0,0 +1,6 @@ +package org.enso.jvm.interop; + +import org.enso.persist.Persistable; + +@Persistable(id = 81909, allowInlining = false) +record OtherResult(Object value) {} diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 9ea8ad8316b8..e15b5da0eeb6 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -1,12 +1,44 @@ package org.enso.jvm.interop; -import org.junit.Assert; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.oracle.truffle.api.interop.TruffleObject; +import java.math.BigDecimal; +import org.enso.jvm.channel.Channel; +import org.enso.test.utils.ContextUtils; +import org.junit.ClassRule; import org.junit.Test; public class OtherJvmObjectTest { + @ClassRule public static final ContextUtils ctx = ContextUtils.newBuilder("js").build(); + private static final Channel CHANNEL = Channel.create(null, Persistables.class); + @Test - public void connect() { - var to = new OtherJvmObject(); - Assert.fail("OKeyish" + to); + public void wrapBigDecimal() { + var bigReal = new BigDecimal("432.322"); + var bigValue = ctx.asValue(bigReal); + var bigUnwrap = ctx.unwrapValue(bigValue); + assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); + + var id = OtherMessage.registerObject((TruffleObject) bigUnwrap); + var other = new OtherJvmObject(CHANNEL, id); + var otherValue = ctx.asValue(other); + + assertFalse("Decimal isn't array", otherValue.hasArrayElements()); + } + + @Test + public void wrapArray() { + var bigReal = new String[] {"Ahoj", "there"}; + var bigValue = ctx.asValue(bigReal); + var bigUnwrap = ctx.unwrapValue(bigValue); + assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); + + var id = OtherMessage.registerObject((TruffleObject) bigUnwrap); + var other = new OtherJvmObject(CHANNEL, id); + var otherValue = ctx.asValue(other); + + assertTrue("Aray is array", otherValue.hasArrayElements()); } } From 245242126d2fbb7d50bcb0cc767f4e4c13246e1f Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 13:49:50 +0200 Subject: [PATCH 08/60] Access String array elements --- .../org/enso/jvm/interop/OtherMessage.java | 34 +++++++++++++++++++ .../enso/jvm/interop/OtherJvmObjectTest.java | 4 +++ 2 files changed, 38 insertions(+) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index f78b04350d33..b63884f42826 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -98,4 +98,38 @@ protected Boolean readObject(Input in) throws IOException, ClassNotFoundExceptio return in.readBoolean(); } } + + @Persistable(id = 81905) + static final class PersistLong extends Persistance { + public PersistLong() { + super(Long.class, true, 81905); + } + + @Override + protected void writeObject(Long obj, Output out) throws IOException { + out.writeLong(obj); + } + + @Override + protected Long readObject(Input in) throws IOException, ClassNotFoundException { + return in.readLong(); + } + } + + @Persistable(id = 81906) + static final class PersistString extends Persistance { + public PersistString() { + super(String.class, true, 81906); + } + + @Override + protected void writeObject(String obj, Output out) throws IOException { + out.writeUTF(obj); + } + + @Override + protected String readObject(Input in) throws IOException, ClassNotFoundException { + return in.readUTF(); + } + } } diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index e15b5da0eeb6..ff3090fb1c9c 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -1,5 +1,6 @@ package org.enso.jvm.interop; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -40,5 +41,8 @@ public void wrapArray() { var otherValue = ctx.asValue(other); assertTrue("Aray is array", otherValue.hasArrayElements()); + assertEquals("Two elements", 2, otherValue.getArraySize()); + assertEquals("Ahoj", otherValue.getArrayElement(0).asString()); + assertEquals("there", otherValue.getArrayElement(1).asString()); } } From b96c9ff405e01cccfbd668bd604bab532fbe1c12 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 14:00:20 +0200 Subject: [PATCH 09/60] Transfer all primitive types --- .../org/enso/jvm/interop/OtherMessage.java | 118 +++++++++++++++++- .../enso/jvm/interop/OtherJvmObjectTest.java | 16 ++- 2 files changed, 125 insertions(+), 9 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index b63884f42826..0568fd68c655 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -82,10 +82,14 @@ protected List readObject(Input in) throws IOException, ClassNotFoundException { } } - @Persistable(id = 81904) + // + // primitive types + // + + @Persistable(id = 101) static final class PersistBoolean extends Persistance { public PersistBoolean() { - super(Boolean.class, true, 81904); + super(Boolean.class, true, 101); } @Override @@ -99,10 +103,61 @@ protected Boolean readObject(Input in) throws IOException, ClassNotFoundExceptio } } - @Persistable(id = 81905) + @Persistable(id = 102) + static final class PersistByte extends Persistance { + public PersistByte() { + super(Byte.class, true, 102); + } + + @Override + protected void writeObject(Byte obj, Output out) throws IOException { + out.writeByte(obj); + } + + @Override + protected Byte readObject(Input in) throws IOException, ClassNotFoundException { + return in.readByte(); + } + } + + @Persistable(id = 103) + static final class PersistShort extends Persistance { + public PersistShort() { + super(Short.class, true, 103); + } + + @Override + protected void writeObject(Short obj, Output out) throws IOException { + out.writeShort(obj); + } + + @Override + protected Short readObject(Input in) throws IOException, ClassNotFoundException { + return in.readShort(); + } + } + + @Persistable(id = 104) + static final class PersistInteger extends Persistance { + public PersistInteger() { + super(Integer.class, true, 104); + } + + @Override + protected void writeObject(Integer obj, Output out) throws IOException { + out.writeInt(obj); + } + + @Override + protected Integer readObject(Input in) throws IOException, ClassNotFoundException { + return in.readInt(); + } + } + + @Persistable(id = 105) static final class PersistLong extends Persistance { public PersistLong() { - super(Long.class, true, 81905); + super(Long.class, true, 105); } @Override @@ -116,10 +171,61 @@ protected Long readObject(Input in) throws IOException, ClassNotFoundException { } } - @Persistable(id = 81906) + @Persistable(id = 106) + static final class PersistFloat extends Persistance { + public PersistFloat() { + super(Float.class, true, 106); + } + + @Override + protected void writeObject(Float obj, Output out) throws IOException { + out.writeFloat(obj); + } + + @Override + protected Float readObject(Input in) throws IOException, ClassNotFoundException { + return in.readFloat(); + } + } + + @Persistable(id = 107) + static final class PersistDouble extends Persistance { + public PersistDouble() { + super(Double.class, true, 107); + } + + @Override + protected void writeObject(Double obj, Output out) throws IOException { + out.writeDouble(obj); + } + + @Override + protected Double readObject(Input in) throws IOException, ClassNotFoundException { + return in.readDouble(); + } + } + + @Persistable(id = 108) + static final class PersistCharacter extends Persistance { + public PersistCharacter() { + super(Character.class, true, 108); + } + + @Override + protected void writeObject(Character obj, Output out) throws IOException { + out.writeChar(obj); + } + + @Override + protected Character readObject(Input in) throws IOException, ClassNotFoundException { + return in.readChar(); + } + } + + @Persistable(id = 109) static final class PersistString extends Persistance { public PersistString() { - super(String.class, true, 81906); + super(String.class, true, 109); } @Override diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index ff3090fb1c9c..b379a2b22807 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -31,7 +31,10 @@ public void wrapBigDecimal() { @Test public void wrapArray() { - var bigReal = new String[] {"Ahoj", "there"}; + var bigReal = + new Object[] { + "Ahoj", 't', (byte) 1, (short) 2, (int) 3, (long) 4, (float) 5, (double) 6, true + }; var bigValue = ctx.asValue(bigReal); var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); @@ -41,8 +44,15 @@ public void wrapArray() { var otherValue = ctx.asValue(other); assertTrue("Aray is array", otherValue.hasArrayElements()); - assertEquals("Two elements", 2, otherValue.getArraySize()); + assertEquals("Few elements", 9, otherValue.getArraySize()); assertEquals("Ahoj", otherValue.getArrayElement(0).asString()); - assertEquals("there", otherValue.getArrayElement(1).asString()); + assertEquals("t", otherValue.getArrayElement(1).asString()); + assertEquals(1, otherValue.getArrayElement(2).asInt()); + assertEquals(2, otherValue.getArrayElement(3).asInt()); + assertEquals(3, otherValue.getArrayElement(4).asInt()); + assertEquals(4, otherValue.getArrayElement(5).asLong()); + assertEquals(5.0, otherValue.getArrayElement(6).asFloat(), 0.1); + assertEquals(6.0, otherValue.getArrayElement(7).asDouble(), 0.1); + assertEquals(true, otherValue.getArrayElement(8).asBoolean()); } } From dd9287c08218cda317966dd8d9c87a940237530a Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 14:18:46 +0200 Subject: [PATCH 10/60] Invoke methods on the other objects --- .../org/enso/jvm/interop/OtherMessage.java | 28 ++++++++++++++++++- .../enso/jvm/interop/OtherJvmObjectTest.java | 1 + 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index 0568fd68c655..f4e5ed06b9d7 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -57,9 +57,35 @@ protected Message readObject(Input in) throws IOException, ClassNotFoundExceptio } @Persistable(id = 81903) + static final class PersistObjectArray extends Persistance { + public PersistObjectArray() { + super(Object[].class, true, 81903); + } + + @Override + protected void writeObject(Object[] obj, Output out) throws IOException { + var size = obj.length; + out.writeInt(size); + for (var i = 0; i < size; i++) { + out.writeObject(obj[i]); + } + } + + @Override + protected Object[] readObject(Input in) throws IOException, ClassNotFoundException { + var size = in.readInt(); + var arr = new Object[size]; + for (var i = 0; i < size; i++) { + arr[i] = in.readObject(); + } + return arr; + } + } + + @Persistable(id = 81904) static final class PersistList extends Persistance { public PersistList() { - super(List.class, true, 81903); + super(List.class, true, 81904); } @Override diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index b379a2b22807..79f72dd8066c 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -27,6 +27,7 @@ public void wrapBigDecimal() { var otherValue = ctx.asValue(other); assertFalse("Decimal isn't array", otherValue.hasArrayElements()); + assertEquals(bigReal.toPlainString(), otherValue.invokeMember("toPlainString").asString()); } @Test From 73f087b8758912e94b7ea928cc1a9dec751f3540 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 15:21:59 +0200 Subject: [PATCH 11/60] Able to invoke BigDecimal.add(BigDecimal) in the other JVM --- .../org/enso/jvm/interop/OtherJvmObject.java | 4 +++ .../org/enso/jvm/interop/OtherMessage.java | 25 +++++++++++++++++++ .../enso/jvm/interop/OtherJvmObjectTest.java | 4 +++ 3 files changed, 33 insertions(+) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 8b05e7e3c4bd..7d994d555cd0 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -20,6 +20,10 @@ final class OtherJvmObject implements TruffleObject { this.id = id; } + long id() { + return id; + } + @ExportMessage Object send(Message message, Object[] args) throws Exception { if (message.getLibraryClass() != InteropLibrary.class) { diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index f4e5ed06b9d7..f36d021021b9 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -37,6 +37,31 @@ static synchronized long registerObject(TruffleObject obj) { return size; } + @Persistable(id = 1) + static final class PersistTruffleObject extends Persistance { + public PersistTruffleObject() { + super(TruffleObject.class, true, 1); + } + + @Override + protected void writeObject(TruffleObject obj, Output out) throws IOException { + if (obj instanceof OtherJvmObject other) { + out.writeLong(other.id()); + } else { + var id = registerObject(obj); + out.writeLong(id); + } + } + + @Override + protected TruffleObject readObject(Input in) throws IOException, ClassNotFoundException { + var id = in.readLong(); + var cached = OBJECTS.get(id); + assert cached != null; + return cached; + } + } + @Persistable(id = 81902) static final class PersistTruffleMessage extends Persistance { public PersistTruffleMessage() { diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 79f72dd8066c..de3285cd6f31 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -28,6 +28,10 @@ public void wrapBigDecimal() { assertFalse("Decimal isn't array", otherValue.hasArrayElements()); assertEquals(bigReal.toPlainString(), otherValue.invokeMember("toPlainString").asString()); + + var twiceReal = bigReal.add(bigReal); + var twiceValue = otherValue.invokeMember("add", otherValue); + assertEquals(twiceReal.toBigInteger(), twiceValue.invokeMember("toBigInteger").asBigInteger()); } @Test From 3ed9c4a03712217babdf99d876b20027d7f65f85 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 18:38:01 +0200 Subject: [PATCH 12/60] Transfer of any TruffleObject yields OtherJvmObject and backwards --- .../org/enso/jvm/interop/OtherJvmObject.java | 11 ++++- .../org/enso/jvm/interop/OtherMessage.java | 43 ++++++++++++++++--- .../enso/jvm/interop/OtherJvmObjectTest.java | 5 +++ 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 7d994d555cd0..4056f1d34f2f 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -16,6 +16,7 @@ final class OtherJvmObject implements TruffleObject { private final long id; OtherJvmObject(Channel channel, long id) { + assert id > 0; this.channel = channel; this.id = id; } @@ -30,7 +31,13 @@ Object send(Message message, Object[] args) throws Exception { throw UnsupportedMessageException.create(); } var msg = new OtherMessage(id, message, List.of(args)); - var res = channel.execute(OtherResult.class, msg); - return res.value(); + var reply = channel.execute(OtherResult.class, msg); + + if (reply.value() instanceof OtherJvmObject toBind) { + assert toBind.channel == null; + return new OtherJvmObject(channel, toBind.id); + } else { + return reply.value(); + } } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index f36d021021b9..54441711113e 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -5,6 +5,7 @@ import com.oracle.truffle.api.library.Message; import com.oracle.truffle.api.library.ReflectionLibrary; import java.io.IOException; +import java.math.BigInteger; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -32,7 +33,7 @@ public OtherResult apply(Channel t) { } static synchronized long registerObject(TruffleObject obj) { - var size = OBJECTS.size(); + var size = OBJECTS.size() + 1; OBJECTS.put((long) size, obj); return size; } @@ -49,16 +50,20 @@ protected void writeObject(TruffleObject obj, Output out) throws IOException { out.writeLong(other.id()); } else { var id = registerObject(obj); - out.writeLong(id); + out.writeLong(-id); } } @Override protected TruffleObject readObject(Input in) throws IOException, ClassNotFoundException { var id = in.readLong(); - var cached = OBJECTS.get(id); - assert cached != null; - return cached; + if (id < 0) { + return new OtherJvmObject(null, -id); + } else { + var cached = OBJECTS.get(id); + assert cached != null; + return cached; + } } } @@ -273,7 +278,11 @@ protected Character readObject(Input in) throws IOException, ClassNotFoundExcept } } - @Persistable(id = 109) + // + // interop types + // + + @Persistable(id = 111) static final class PersistString extends Persistance { public PersistString() { super(String.class, true, 109); @@ -289,4 +298,26 @@ protected String readObject(Input in) throws IOException, ClassNotFoundException return in.readUTF(); } } + + @Persistable(id = 112) + static final class PersistBigInteger extends Persistance { + public PersistBigInteger() { + super(BigInteger.class, true, 112); + } + + @Override + protected void writeObject(BigInteger obj, Output out) throws IOException { + var arr = obj.toByteArray(); + out.writeInt(arr.length); + out.write(arr); + } + + @Override + protected BigInteger readObject(Input in) throws IOException, ClassNotFoundException { + var len = in.readInt(); + var arr = new byte[len]; + in.readFully(arr); + return new BigInteger(arr); + } + } } diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index de3285cd6f31..90234d65b769 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -32,6 +32,11 @@ public void wrapBigDecimal() { var twiceReal = bigReal.add(bigReal); var twiceValue = otherValue.invokeMember("add", otherValue); assertEquals(twiceReal.toBigInteger(), twiceValue.invokeMember("toBigInteger").asBigInteger()); + assertTrue("It is OtherJvmObject", ctx.unwrapValue(twiceValue) instanceof OtherJvmObject); + + var minusValue = twiceValue.invokeMember("subtract", otherValue); + assertEquals(bigReal.toString(), minusValue.invokeMember("toString").asString()); + assertTrue("OtherJvmObject for minus", ctx.unwrapValue(minusValue) instanceof OtherJvmObject); } @Test From 7a4024de3cac82f358318ecd999f6fac7a9eeb6d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 19:07:53 +0200 Subject: [PATCH 13/60] Report duplicated registrations --- .../java/org/enso/persist/impl/PersistableProcessor.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java index 0dc060edd29b..df7a748c9d7c 100644 --- a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java +++ b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java @@ -398,7 +398,14 @@ private void registerPersistablesClass(String pkgName, String className, int id) pkg = new TreeMap<>(); registeredClasses.put(pkgName, pkg); } - pkg.put(id, className); + var prev = pkg.put(id, className); + if (prev != null) { + processingEnv + .getMessager() + .printMessage( + Kind.ERROR, + "Duplicated registration with id=" + id + " by " + className + " and " + prev); + } } private boolean isVisibleFrom(Element e, Element from) { From 44edc03d31138b180e5999bb10ff55a3fb31b670 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 20:19:01 +0200 Subject: [PATCH 14/60] Invoke java.lang.Short.valueOf in the other JVM --- .../src/main/java/module-info.java | 2 +- .../org/enso/jvm/interop/OtherMessage.java | 59 ++++++++++++------- .../enso/jvm/interop/TruffleClassLoader.java | 50 ++++++++++++++++ .../enso/jvm/interop/OtherJvmObjectTest.java | 13 ++++ 4 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java diff --git a/lib/java/jvm-interop/src/main/java/module-info.java b/lib/java/jvm-interop/src/main/java/module-info.java index 97267ac8c04f..14a56f7fb4b1 100644 --- a/lib/java/jvm-interop/src/main/java/module-info.java +++ b/lib/java/jvm-interop/src/main/java/module-info.java @@ -1,10 +1,10 @@ module org.enso.jvm.interop { // requires org.slf4j; // requires org.graalvm.nativeimage; - // requires org.graalvm.polyglot; // requires org.graalvm.word; // requires org.enso.engine.common; + requires org.graalvm.polyglot; requires org.graalvm.truffle; requires org.enso.jvm.channel; requires org.enso.persistance; diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index 54441711113e..1ede2b85523e 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -16,10 +16,17 @@ import org.enso.persist.Persistance; @Persistable(id = 81901) -record OtherMessage(long id, Message message, List args) - implements Function { +record OtherMessage( // sends a message to the other side + long id, Message message, List args // with ReflectionLibrary-like arguments + ) implements Function { private static final Map OBJECTS = new HashMap<>(); + static synchronized long registerObject(TruffleObject obj) { + var size = OBJECTS.size() + 1; + OBJECTS.put((long) size, obj); + return size; + } + @Override public OtherResult apply(Channel t) { try { @@ -32,15 +39,22 @@ public OtherResult apply(Channel t) { } } - static synchronized long registerObject(TruffleObject obj) { - var size = OBJECTS.size() + 1; - OBJECTS.put((long) size, obj); - return size; + @Persistable(id = 81905) + record LoadClass(String name) implements Function { + @Override + public OtherResult apply(Channel t) { + try { + var clazzRaw = TruffleClassLoader.loadClass(name); + return new OtherResult(clazzRaw); + } catch (ClassNotFoundException ex) { + throw new IllegalStateException(ex); + } + } } @Persistable(id = 1) static final class PersistTruffleObject extends Persistance { - public PersistTruffleObject() { + PersistTruffleObject() { super(TruffleObject.class, true, 1); } @@ -69,7 +83,7 @@ protected TruffleObject readObject(Input in) throws IOException, ClassNotFoundEx @Persistable(id = 81902) static final class PersistTruffleMessage extends Persistance { - public PersistTruffleMessage() { + PersistTruffleMessage() { super(Message.class, true, 81902); } @@ -88,7 +102,8 @@ protected Message readObject(Input in) throws IOException, ClassNotFoundExceptio @Persistable(id = 81903) static final class PersistObjectArray extends Persistance { - public PersistObjectArray() { + + PersistObjectArray() { super(Object[].class, true, 81903); } @@ -114,7 +129,7 @@ protected Object[] readObject(Input in) throws IOException, ClassNotFoundExcepti @Persistable(id = 81904) static final class PersistList extends Persistance { - public PersistList() { + PersistList() { super(List.class, true, 81904); } @@ -141,10 +156,9 @@ protected List readObject(Input in) throws IOException, ClassNotFoundException { // // primitive types // - @Persistable(id = 101) static final class PersistBoolean extends Persistance { - public PersistBoolean() { + PersistBoolean() { super(Boolean.class, true, 101); } @@ -161,7 +175,7 @@ protected Boolean readObject(Input in) throws IOException, ClassNotFoundExceptio @Persistable(id = 102) static final class PersistByte extends Persistance { - public PersistByte() { + PersistByte() { super(Byte.class, true, 102); } @@ -178,7 +192,8 @@ protected Byte readObject(Input in) throws IOException, ClassNotFoundException { @Persistable(id = 103) static final class PersistShort extends Persistance { - public PersistShort() { + + PersistShort() { super(Short.class, true, 103); } @@ -195,7 +210,7 @@ protected Short readObject(Input in) throws IOException, ClassNotFoundException @Persistable(id = 104) static final class PersistInteger extends Persistance { - public PersistInteger() { + PersistInteger() { super(Integer.class, true, 104); } @@ -212,7 +227,7 @@ protected Integer readObject(Input in) throws IOException, ClassNotFoundExceptio @Persistable(id = 105) static final class PersistLong extends Persistance { - public PersistLong() { + PersistLong() { super(Long.class, true, 105); } @@ -229,7 +244,7 @@ protected Long readObject(Input in) throws IOException, ClassNotFoundException { @Persistable(id = 106) static final class PersistFloat extends Persistance { - public PersistFloat() { + PersistFloat() { super(Float.class, true, 106); } @@ -246,7 +261,8 @@ protected Float readObject(Input in) throws IOException, ClassNotFoundException @Persistable(id = 107) static final class PersistDouble extends Persistance { - public PersistDouble() { + + PersistDouble() { super(Double.class, true, 107); } @@ -263,7 +279,7 @@ protected Double readObject(Input in) throws IOException, ClassNotFoundException @Persistable(id = 108) static final class PersistCharacter extends Persistance { - public PersistCharacter() { + PersistCharacter() { super(Character.class, true, 108); } @@ -281,10 +297,9 @@ protected Character readObject(Input in) throws IOException, ClassNotFoundExcept // // interop types // - @Persistable(id = 111) static final class PersistString extends Persistance { - public PersistString() { + PersistString() { super(String.class, true, 109); } @@ -301,7 +316,7 @@ protected String readObject(Input in) throws IOException, ClassNotFoundException @Persistable(id = 112) static final class PersistBigInteger extends Persistance { - public PersistBigInteger() { + PersistBigInteger() { super(BigInteger.class, true, 112); } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java new file mode 100644 index 000000000000..1d0ecc867410 --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -0,0 +1,50 @@ +package org.enso.jvm.interop; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import org.graalvm.polyglot.Value; + +@ExportLibrary(value = InteropLibrary.class) +final class TruffleClassLoader implements TruffleObject { + + private Object value; + + private TruffleClassLoader() {} + + static TruffleObject loadClass(String className) throws ClassNotFoundException { + java.lang.Class clazz = Class.forName(className); + org.graalvm.polyglot.Value clazzValue = Value.asValue(clazz).getMember("static"); + org.enso.jvm.interop.TruffleClassLoader holderRaw = new TruffleClassLoader(); + org.graalvm.polyglot.Value holderValue = Value.asValue(holderRaw); + holderValue.putMember("any", clazzValue); + java.lang.Object clazzRaw = holderRaw.value; + return (TruffleObject) clazzRaw; + } + + @ExportMessage + void writeMember(String name, Object value) { + this.value = value; + } + + @ExportMessage + boolean hasMembers() { + return false; + } + + @ExportMessage + boolean isMemberModifiable(String member) { + return true; + } + + @ExportMessage + boolean isMemberInsertable(String member) { + return false; + } + + @ExportMessage + Object getMembers(boolean includeInternal) { + return this; + } +} diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 90234d65b769..ab2e4837c85b 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -65,4 +65,17 @@ public void wrapArray() { assertEquals(6.0, otherValue.getArrayElement(7).asDouble(), 0.1); assertEquals(true, otherValue.getArrayElement(8).asBoolean()); } + + @Test + public void loadAClassMessage() { + var msg = new OtherMessage.LoadClass("java.lang.Short"); + var shortRaw = CHANNEL.execute(OtherResult.class, msg).value(); + if (shortRaw instanceof OtherJvmObject other) { + shortRaw = new OtherJvmObject(CHANNEL, other.id()); + } + var shortValue = ctx.asValue(shortRaw); + + var value = shortValue.invokeMember("valueOf", "32531"); + assertEquals(32531, value.asInt()); + } } From 0b8c4772453a1c1b25b1a95df983d189334a153a Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 21:03:04 +0200 Subject: [PATCH 15/60] loadClass yields an exception --- .../java/org/enso/jvm/channel/Channel.java | 10 +++--- .../org/enso/jvm/interop/OtherMessage.java | 33 ++++++++++++++----- .../org/enso/jvm/interop/OtherResult.java | 8 ++--- .../enso/jvm/interop/OtherJvmObjectTest.java | 16 ++++++++- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 238948f61c12..935ab45b23ba 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -192,7 +192,7 @@ private static boolean createJvmPeerChannel( * gets serialized and transferred back to us. Deserialized and the value is then returned * from this method */ - public final R execute(Class resultType, Function msg) { + public final R execute(Class resultType, Function msg) { return executeImpl(pool, resultType, msg); } @@ -291,10 +291,10 @@ private void checkForException(JNI.JNIEnv e) { } } - private R executeImpl( // - Persistance.Pool pool, // - Class replyType, // - Function msg // + private R executeImpl( // handles this.execute + Persistance.Pool pool, // the pool with persitance + Class replyType, // requested return type + Function msg // function to serde to the other JVM ) { var address = 0L; try { diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index 1ede2b85523e..75e2c36761fb 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -18,9 +18,25 @@ @Persistable(id = 81901) record OtherMessage( // sends a message to the other side long id, Message message, List args // with ReflectionLibrary-like arguments - ) implements Function { + ) implements Function> { private static final Map OBJECTS = new HashMap<>(); + @Persistable(id = 81908, allowInlining = false) + record OtherValue(T value) implements OtherResult {} + + @Persistable(id = 81909, allowInlining = false) + record OtherException(String msg) implements OtherResult { + + static OtherException create(Exception ex) { + return new OtherException<>(ex.getMessage()); + } + + @Override + public V value() throws IllegalStateException { + throw new IllegalStateException(msg()); + } + } + static synchronized long registerObject(TruffleObject obj) { var size = OBJECTS.size() + 1; OBJECTS.put((long) size, obj); @@ -28,26 +44,27 @@ static synchronized long registerObject(TruffleObject obj) { } @Override - public OtherResult apply(Channel t) { + public OtherResult apply(Channel t) { try { var receiver = OBJECTS.get(id); assert receiver instanceof TruffleObject; var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); - return new OtherResult(res); + return new OtherValue<>(res); } catch (Exception ex) { - return new OtherResult(ex); + return OtherException.create(ex); } } @Persistable(id = 81905) - record LoadClass(String name) implements Function { + record LoadClass(String name) + implements Function> { @Override - public OtherResult apply(Channel t) { + public OtherResult apply(Channel t) { try { var clazzRaw = TruffleClassLoader.loadClass(name); - return new OtherResult(clazzRaw); + return new OtherValue<>(clazzRaw); } catch (ClassNotFoundException ex) { - throw new IllegalStateException(ex); + return OtherException.create(ex); } } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java index d60af03659d9..80c9ec9ae75d 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java @@ -1,6 +1,6 @@ package org.enso.jvm.interop; -import org.enso.persist.Persistable; - -@Persistable(id = 81909, allowInlining = false) -record OtherResult(Object value) {} +sealed interface OtherResult // either R or E + permits OtherMessage.OtherValue, OtherMessage.OtherException { + R value() throws E; +} diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index ab2e4837c85b..35bf62f7fdeb 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -1,13 +1,16 @@ package org.enso.jvm.interop; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import com.oracle.truffle.api.interop.TruffleObject; import java.math.BigDecimal; import org.enso.jvm.channel.Channel; import org.enso.test.utils.ContextUtils; +import org.hamcrest.core.StringContains; import org.junit.ClassRule; import org.junit.Test; @@ -67,7 +70,7 @@ public void wrapArray() { } @Test - public void loadAClassMessage() { + public void loadClassViaMessage() throws Exception { var msg = new OtherMessage.LoadClass("java.lang.Short"); var shortRaw = CHANNEL.execute(OtherResult.class, msg).value(); if (shortRaw instanceof OtherJvmObject other) { @@ -78,4 +81,15 @@ public void loadAClassMessage() { var value = shortValue.invokeMember("valueOf", "32531"); assertEquals(32531, value.asInt()); } + + @Test + public void classNotFoundError() throws Exception { + var msg = new OtherMessage.LoadClass("java.lang.unknown.Clazz"); + try { + var shortRaw = CHANNEL.execute(OtherResult.class, msg).value(); + fail("Should yield an exception: " + shortRaw); + } catch (IllegalStateException ex) { + assertThat(ex.getMessage(), StringContains.containsString("java.lang.unknown.Clazz")); + } + } } From ba92d27267e3e7c8753c2aec29ac1b85a76c4085 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 9 Jun 2025 21:18:52 +0200 Subject: [PATCH 16/60] LoadClass really throws ClassNotFoundException --- .../java/org/enso/jvm/channel/Channel.java | 5 ++- .../org/enso/jvm/interop/OtherMessage.java | 37 +++++++++++++------ .../org/enso/jvm/interop/OtherResult.java | 2 +- .../enso/jvm/interop/OtherJvmObjectTest.java | 4 +- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 935ab45b23ba..72d770eda407 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -192,8 +192,9 @@ private static boolean createJvmPeerChannel( * gets serialized and transferred back to us. Deserialized and the value is then returned * from this method */ - public final R execute(Class resultType, Function msg) { - return executeImpl(pool, resultType, msg); + @SuppressWarnings("unchecked") + public final R execute(Class resultType, Function msg) { + return (R) executeImpl(pool, resultType, msg); } private static final CEntryPointLiteral CALLBACK_FN = diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java index 75e2c36761fb..ce3855191257 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java @@ -22,18 +22,31 @@ record OtherMessage( // sends a message to the other side private static final Map OBJECTS = new HashMap<>(); @Persistable(id = 81908, allowInlining = false) - record OtherValue(T value) implements OtherResult {} + record ReturnValue(T value) implements OtherResult { + static ReturnValue create(T value) { + return new ReturnValue<>(value); + } + } @Persistable(id = 81909, allowInlining = false) - record OtherException(String msg) implements OtherResult { + record ThrowException(int kind, String msg) implements OtherResult { - static OtherException create(Exception ex) { - return new OtherException<>(ex.getMessage()); + static ThrowException create(E ex) { + var kind = + switch (ex.getClass().getName()) { + case "java.lang.ClassNotFoundException" -> 1; + default -> 0; + }; + return new ThrowException<>(kind, ex.getMessage()); } @Override - public V value() throws IllegalStateException { - throw new IllegalStateException(msg()); + @SuppressWarnings("unchecked") + public V value() throws E { + switch (kind) { + case 1 -> throw (E) new ClassNotFoundException(msg()); + default -> throw new IllegalStateException(msg()); + } } } @@ -49,22 +62,22 @@ static synchronized long registerObject(TruffleObject obj) { var receiver = OBJECTS.get(id); assert receiver instanceof TruffleObject; var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); - return new OtherValue<>(res); + return new ReturnValue<>(res); } catch (Exception ex) { - return OtherException.create(ex); + return ThrowException.create(ex); } } @Persistable(id = 81905) record LoadClass(String name) - implements Function> { + implements Function> { @Override - public OtherResult apply(Channel t) { + public OtherResult apply(Channel t) { try { var clazzRaw = TruffleClassLoader.loadClass(name); - return new OtherValue<>(clazzRaw); + return ReturnValue.create(clazzRaw); } catch (ClassNotFoundException ex) { - return OtherException.create(ex); + return ThrowException.create(ex); } } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java index 80c9ec9ae75d..b54058c6198f 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java @@ -1,6 +1,6 @@ package org.enso.jvm.interop; sealed interface OtherResult // either R or E - permits OtherMessage.OtherValue, OtherMessage.OtherException { + permits OtherMessage.ReturnValue, OtherMessage.ThrowException { R value() throws E; } diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 35bf62f7fdeb..c0e0b2026f58 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -83,12 +83,12 @@ public void loadClassViaMessage() throws Exception { } @Test - public void classNotFoundError() throws Exception { + public void classNotFoundError() { var msg = new OtherMessage.LoadClass("java.lang.unknown.Clazz"); try { var shortRaw = CHANNEL.execute(OtherResult.class, msg).value(); fail("Should yield an exception: " + shortRaw); - } catch (IllegalStateException ex) { + } catch (ClassNotFoundException ex) { assertThat(ex.getMessage(), StringContains.containsString("java.lang.unknown.Clazz")); } } From cec892c34a4fb6b0feabaa30a038ea81f6a9a578 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 11:32:57 +0200 Subject: [PATCH 17/60] Provide support for additional PolyglotSymbolResolver extensions --- engine/common/src/main/java/module-info.java | 4 ++ .../enso/common/PolyglotSymbolResolver.java | 49 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java diff --git a/engine/common/src/main/java/module-info.java b/engine/common/src/main/java/module-info.java index 743e0c752743..67b29920945b 100644 --- a/engine/common/src/main/java/module-info.java +++ b/engine/common/src/main/java/module-info.java @@ -1,3 +1,5 @@ +import org.enso.common.PolyglotSymbolResolver; + module org.enso.engine.common { requires org.graalvm.nativeimage; requires org.graalvm.polyglot; @@ -6,4 +8,6 @@ requires org.slf4j; exports org.enso.common; + + uses PolyglotSymbolResolver; } diff --git a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java new file mode 100644 index 000000000000..74759597e2e1 --- /dev/null +++ b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java @@ -0,0 +1,49 @@ +package org.enso.common; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.ServiceLoader; + +/** Generic support for loading Java polyglot symbols. */ +public abstract class PolyglotSymbolResolver { + private static final Collection ALL; + + static { + var arr = new ArrayList(); + for (var l : ServiceLoader.load(PolyglotSymbolResolver.class)) { + arr.add(l); + } + ALL = Collections.unmodifiableList(arr); + } + + /** + * Search all providers for given name. + * + * @param name dot separated name to search for + * @return non-null object representing the name + * @throws java.lang.ClassNotFoundException if no name was found + */ + public static Object loadClass(String name) throws ClassNotFoundException { + var ex = new ClassNotFoundException(); + for (var p : ALL) { + try { + var found = p.handleLoadClass(name); + assert found != null; + return found; + } catch (ClassNotFoundException cnfe) { + ex = cnfe; + } + } + throw ex; + } + + /** + * Subclasses implement this method to seach for provided name + * + * @param name dot separated name to search for + * @return non-null object representing the name + * @throws java.lang.ClassNotFoundException if no name was found + */ + protected abstract Object handleLoadClass(String name) throws ClassNotFoundException; +} From cf3309c5e08e1d876cf68bf0b641283f3b875849 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 11:34:48 +0200 Subject: [PATCH 18/60] Environment is associated with every DistributionManager --- engine/runner/src/main/java/org/enso/runner/JavaFinder.java | 2 +- .../runtimeversionmanager/components/GraalVersionManager.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/runner/src/main/java/org/enso/runner/JavaFinder.java b/engine/runner/src/main/java/org/enso/runner/JavaFinder.java index edc5bca29201..a8dc8e7e9c9d 100644 --- a/engine/runner/src/main/java/org/enso/runner/JavaFinder.java +++ b/engine/runner/src/main/java/org/enso/runner/JavaFinder.java @@ -79,7 +79,7 @@ private static Path findJavaExecutableInDistributionRuntimes() { if (distributionManager.isRunningPortable()) { logger.trace("Running in portable distribution"); } - var graalVersionManager = new GraalVersionManager(distributionManager, env); + var graalVersionManager = new GraalVersionManager(distributionManager); var versionUsedForBuild = new GraalVMVersion(BuildVersion.graalVersion(), BuildVersion.javaVersion()); var runtimeWithExactVersionMatch = graalVersionManager.findGraalRuntime(versionUsedForBuild); diff --git a/lib/scala/runtime-version-manager/src/main/java/org/enso/runtimeversionmanager/components/GraalVersionManager.java b/lib/scala/runtime-version-manager/src/main/java/org/enso/runtimeversionmanager/components/GraalVersionManager.java index e90feb0bda49..c0c630598f8e 100644 --- a/lib/scala/runtime-version-manager/src/main/java/org/enso/runtimeversionmanager/components/GraalVersionManager.java +++ b/lib/scala/runtime-version-manager/src/main/java/org/enso/runtimeversionmanager/components/GraalVersionManager.java @@ -18,9 +18,9 @@ public final class GraalVersionManager { private final Environment environment; private static final Logger logger = LoggerFactory.getLogger(GraalVersionManager.class); - public GraalVersionManager(DistributionManager distributionManager, Environment environment) { + public GraalVersionManager(DistributionManager distributionManager) { this.distributionManager = distributionManager; - this.environment = environment; + this.environment = distributionManager.env(); } /** From 2b58d273824b096c8c298333aed57a34a026e5db Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 11:35:42 +0200 Subject: [PATCH 19/60] Sending messages must be behind @TruffleBoundary --- .../src/main/java/org/enso/jvm/interop/OtherJvmObject.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 4056f1d34f2f..172f4fe9f91c 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -1,5 +1,6 @@ package org.enso.jvm.interop; +import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnsupportedMessageException; @@ -25,6 +26,7 @@ long id() { return id; } + @CompilerDirectives.TruffleBoundary @ExportMessage Object send(Message message, Object[] args) throws Exception { if (message.getLibraryClass() != InteropLibrary.class) { From e41d0462d69a4c2d391adcca1160ffe243f508ff Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 11:40:29 +0200 Subject: [PATCH 20/60] Use OtherSymbolResolver (via PolyglotSymbolResolver API) to load Java classes from a HotSpot JVM --- build.sbt | 41 ++++++---- .../src/main/java/org/enso/runner/Main.java | 11 +++ .../org/enso/runner/reflect-config.json | 5 ++ .../enso/interpreter/runtime/EnsoContext.java | 74 +++++++++++++++---- .../src/main/java/module-info.java | 14 +++- .../enso/jvm/interop/OtherSymbolResolver.java | 73 ++++++++++++++++++ 6 files changed, 188 insertions(+), 30 deletions(-) create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java diff --git a/build.sbt b/build.sbt index c379e034f3d4..43ec48735abd 100644 --- a/build.sbt +++ b/build.sbt @@ -3841,6 +3841,10 @@ lazy val `engine-runner` = project NativeImage.additionalCp := { val runnerDeps = (Compile / fullClasspath).value.map(_.data.getAbsolutePath) + val jvmInteropDeps = + (`jvm-interop` / Compile / fullClasspath).value.map( + _.data.getAbsolutePath + ) val runtimeDeps = (`runtime` / Compile / fullClasspath).value.map(_.data.getAbsolutePath) val loggingDeps = @@ -3874,6 +3878,7 @@ lazy val `engine-runner` = project } val core = ( runnerDeps ++ + jvmInteropDeps ++ runtimeDeps ++ loggingDeps ++ replDebugInstr ++ @@ -4305,17 +4310,19 @@ lazy val `jvm-interop` = (Test / fork) := true, commands += WithDebugCommand.withDebug, libraryDependencies ++= slf4jApi ++ Seq( - "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided", - "org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided", - "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test, - "junit" % "junit" % junitVersion % Test, - "com.github.sbt" % "junit-interface" % junitIfVersion % Test + "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided", + "org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided", + "org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided", + "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test, + "junit" % "junit" % junitVersion % Test, + "com.github.sbt" % "junit-interface" % junitIfVersion % Test ), Compile / moduleDependencies ++= slf4jApi ++ Seq( - "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion, - "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion, - "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion, - "org.graalvm.sdk" % "word" % graalMavenPackagesVersion + "org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion, + "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion, + "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion, + "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion, + "org.graalvm.sdk" % "word" % graalMavenPackagesVersion ), Compile / internalModuleDependencies ++= Seq( (`jvm-channel` / Compile / exportedModule).value, @@ -4334,23 +4341,26 @@ lazy val `os-environment` = .settings( frgaalJavaCompilerSetting, libraryDependencies ++= slf4jApi ++ Seq( - "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", - "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", - "commons-io" % "commons-io" % commonsIoVersion, - "junit" % "junit" % junitVersion % Test, - "com.github.sbt" % "junit-interface" % junitIfVersion % Test + "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", + "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", + "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided", + "commons-io" % "commons-io" % commonsIoVersion, + "junit" % "junit" % junitVersion % Test, + "com.github.sbt" % "junit-interface" % junitIfVersion % Test ), Compile / moduleDependencies ++= slf4jApi ++ Seq( "commons-io" % "commons-io" % commonsIoVersion, "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion, "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion, "com.typesafe" % "config" % typesafeConfigVersion, - "org.graalvm.sdk" % "word" % graalMavenPackagesVersion + "org.graalvm.sdk" % "word" % graalMavenPackagesVersion, + "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion ), Compile / internalModuleDependencies ++= Seq( (`engine-common` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, (`jvm-channel` / Compile / exportedModule).value, + (`jvm-interop` / Compile / exportedModule).value, (`logging-utils` / Compile / exportedModule).value, (`logging-config` / Compile / exportedModule).value ), @@ -4406,6 +4416,7 @@ lazy val `os-environment` = Test / fork := true ) .dependsOn(`jvm-channel`) + .dependsOn(`jvm-interop`) .dependsOn(`persistance`) .dependsOn(`persistance-dsl` % "provided") .dependsOn(`engine-common`) diff --git a/engine/runner/src/main/java/org/enso/runner/Main.java b/engine/runner/src/main/java/org/enso/runner/Main.java index 6a0b9a4c7d25..2786ba3f99ff 100644 --- a/engine/runner/src/main/java/org/enso/runner/Main.java +++ b/engine/runner/src/main/java/org/enso/runner/Main.java @@ -1576,6 +1576,17 @@ private void launch(String[] args) throws IOException, InterruptedException, URI } } + if (System.getProperty("java.home") == null) { + assert HostEnsoUtils.isAot() : "Otherwise java.home would be defined"; + var exe = JavaFinder.findJavaExecutable(); + if (exe != null) { + System.setProperty("java.home", exe.getParentFile().getParentFile().getAbsolutePath()); + } + } + var home = System.getProperty("java.home"); + System.err.println("javaHome: " + home); + assert home != null && new File(home).exists() : "There must be home: " + home; + handleLaunch(originalCwdOrNull, line, logLevel, logMasking[0]); } diff --git a/engine/runner/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 index d42920f37fdc..b856194edc12 100644 --- a/engine/runner/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 @@ -61,6 +61,11 @@ "queryAllPublicMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, +{ + "name":"org.enso.jvm.interop.Persistables", + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +}, { "name":"ch.qos.logback.classic.pattern.DateConverter", "methods":[{"name":"","parameterTypes":[] }] diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index c1174e6a3276..83080edc4aaf 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -29,6 +29,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Optional; import java.util.Set; @@ -38,7 +39,9 @@ import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; +import org.enso.common.HostEnsoUtils; import org.enso.common.LanguageInfo; +import org.enso.common.PolyglotSymbolResolver; import org.enso.common.RuntimeOptions; import org.enso.compiler.Compiler; import org.enso.compiler.core.EnsoParser; @@ -579,6 +582,35 @@ public boolean isColorTerminalOutput() { return false; } + interface ClassLookup { + Object loadClass(String name) throws ClassNotFoundException, InteropException; + + static Object lookupJavaClass( + String className, ClassLookup fn, Collection collectExceptions) { + var binaryName = new StringBuilder(className); + for (; ; ) { + var fqn = binaryName.toString(); + try { + System.err.println("fqn check: " + fqn); + var hostSymbol = fn.loadClass(fqn); + System.err.println(" res: " + hostSymbol); + if (hostSymbol != null) { + return hostSymbol; + } + } catch (ClassNotFoundException | RuntimeException | InteropException ex) { + ex.printStackTrace(); + collectExceptions.add(ex); + } + var at = fqn.lastIndexOf('.'); + if (at < 0) { + break; + } + binaryName.setCharAt(at, '$'); + } + return null; + } + } + /** * Tries to lookup a Java class (host symbol in Truffle terminology) by its fully qualified name. * This method also tries to lookup inner classes. More specifically, if the provided name @@ -590,30 +622,44 @@ public boolean isColorTerminalOutput() { */ @TruffleBoundary public TruffleObject lookupJavaClass(String className) { - var binaryName = new StringBuilder(className); var collectedExceptions = new ArrayList(); - for (; ; ) { - var fqn = binaryName.toString(); - try { - var hostSymbol = lookupHostSymbol(fqn); - if (hostSymbol != null) { - return (TruffleObject) hostSymbol; - } - } catch (ClassNotFoundException | RuntimeException | InteropException ex) { - collectedExceptions.add(ex); + + { + var hostSymbol = + ClassLookup.lookupJavaClass( + className, // name to search for + this::lookupHostSymbol, // ask the classloader + collectedExceptions // put here all exceptions + ); + if (hostSymbol instanceof TruffleObject) { + System.err.println(" returning host symbol " + hostSymbol); + return (TruffleObject) hostSymbol; } - var at = fqn.lastIndexOf('.'); - if (at < 0) { - break; + } + System.err.println(" try deeper if " + HostEnsoUtils.isAot()); + if (HostEnsoUtils.isAot()) { + var javaHome = System.getProperty("java.home"); + System.err.println(" with javaHOme: " + javaHome); + logger.info( + () -> String.format("Class %s not found, trying to turn on JVM %s", className, javaHome)); + var hostSymbol = + ClassLookup.lookupJavaClass( + className, // name to search for + PolyglotSymbolResolver::loadClass, // pluggable polyglot searches + collectedExceptions // collect exceptions + ); + if (hostSymbol instanceof TruffleObject) { + return (TruffleObject) hostSymbol; } - binaryName.setCharAt(at, '$'); } + var level = Level.WARNING; for (var ex : collectedExceptions) { logger.log(level, ex.getMessage()); level = Level.FINE; logger.log(Level.FINE, null, ex); } + return getBuiltins().error().makeMissingPolyglotImportError(className); } diff --git a/lib/java/jvm-interop/src/main/java/module-info.java b/lib/java/jvm-interop/src/main/java/module-info.java index 14a56f7fb4b1..c5e907425fdc 100644 --- a/lib/java/jvm-interop/src/main/java/module-info.java +++ b/lib/java/jvm-interop/src/main/java/module-info.java @@ -1,11 +1,23 @@ +import org.enso.common.PolyglotSymbolResolver; +import org.enso.jvm.interop.OtherSymbolResolver; + module org.enso.jvm.interop { // requires org.slf4j; // requires org.graalvm.nativeimage; // requires org.graalvm.word; - // requires org.enso.engine.common; + + requires org.enso.engine.common; + + provides PolyglotSymbolResolver with + OtherSymbolResolver; requires org.graalvm.polyglot; requires org.graalvm.truffle; requires org.enso.jvm.channel; + + opens org.enso.jvm.interop to + org.enso.jvm.channel; + requires org.enso.persistance; + requires static org.openide.util.lookup.RELEASE180; } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java new file mode 100644 index 000000000000..1e71f169db9a --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java @@ -0,0 +1,73 @@ +package org.enso.jvm.interop; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Properties; +import org.enso.common.HostEnsoUtils; +import org.enso.common.PolyglotSymbolResolver; +import org.enso.jvm.channel.Channel; +import org.enso.jvm.channel.JVM; + +/** Resolves symbols via interop messages to the "other" HotSpot JVM. */ +@org.openide.util.lookup.ServiceProvider(service = PolyglotSymbolResolver.class) +public final class OtherSymbolResolver extends PolyglotSymbolResolver { + private Channel jvm; + + @Override + protected Object handleLoadClass(String name) throws ClassNotFoundException { + if (!HostEnsoUtils.isAot()) { + throw new ClassNotFoundException("Only works in AOT mode!"); + } + if (jvm == null) { + try { + jvm = initializeChannel(); + } catch (IOException | URISyntaxException ex) { + throw new ClassNotFoundException("Cannot initialize JVM", ex); + } + } + var result = jvm.execute(OtherResult.class, new OtherMessage.LoadClass(name)); + return result.value(); + } + + private Channel initializeChannel() throws IOException, URISyntaxException { + var home = System.getProperty("java.home"); + if (home == null) { + throw new IOException("No java.home specified"); + } + var javaHome = new File(home); + if (!javaHome.exists()) { + throw new IOException("JVM doesn't exists: " + javaHome); + } + + var loc = getClass().getProtectionDomain().getCodeSource().getLocation(); + var component = new File(loc.toURI().resolve("..")).getAbsoluteFile(); + if (!component.getName().equals("component")) { + component = new File(component, "component"); + } + + var commandAndArgs = new ArrayList(); + var assertsOn = false; + assert assertsOn = true; + if (assertsOn) { + commandAndArgs.add("-ea"); + } + Properties props = null; + if (props != null) { + for (var e : props.entrySet()) { + commandAndArgs.add("-D" + e.getKey() + "=" + e.getValue()); + } + } + commandAndArgs.add("--sun-misc-unsafe-memory-access=allow"); + commandAndArgs.add("--enable-native-access=org.graalvm.truffle"); + commandAndArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED"); + if (!component.isDirectory()) { + throw new IOException("Cannot find " + component + " directory"); + } + commandAndArgs.add("--module-path=" + component.getPath()); + commandAndArgs.add("-Djdk.module.main=org.enso.jvm.interop"); + var jvm = JVM.create(javaHome, commandAndArgs.toArray(new String[0])); + return Channel.create(jvm, Persistables.class); + } +} From d6960ec495bed5777e1716ff35ec191b35e2922e Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 16:45:16 +0200 Subject: [PATCH 21/60] debug log message when setting value of java.home property --- engine/runner/src/main/java/org/enso/runner/Main.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/engine/runner/src/main/java/org/enso/runner/Main.java b/engine/runner/src/main/java/org/enso/runner/Main.java index 2786ba3f99ff..b88c2658a8c5 100644 --- a/engine/runner/src/main/java/org/enso/runner/Main.java +++ b/engine/runner/src/main/java/org/enso/runner/Main.java @@ -1580,13 +1580,11 @@ private void launch(String[] args) throws IOException, InterruptedException, URI assert HostEnsoUtils.isAot() : "Otherwise java.home would be defined"; var exe = JavaFinder.findJavaExecutable(); if (exe != null) { - System.setProperty("java.home", exe.getParentFile().getParentFile().getAbsolutePath()); + var path = exe.getParentFile().getParentFile().getAbsolutePath(); + System.setProperty("java.home", path); + LOGGER.debug("Setting java.home property for AOT mode to {}", path); } } - var home = System.getProperty("java.home"); - System.err.println("javaHome: " + home); - assert home != null && new File(home).exists() : "There must be home: " + home; - handleLaunch(originalCwdOrNull, line, logLevel, logMasking[0]); } From 31eff03733e5d29bcaa70b66e647365d93265197 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 16:46:16 +0200 Subject: [PATCH 22/60] Using Context with HostAccess.ALL --- .../enso/jvm/interop/TruffleClassLoader.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index 1d0ecc867410..78092e7d88e1 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -4,21 +4,36 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; -import org.graalvm.polyglot.Value; +import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.HostAccess; @ExportLibrary(value = InteropLibrary.class) final class TruffleClassLoader implements TruffleObject { + private static Context ctx; private Object value; private TruffleClassLoader() {} + private static synchronized Context ctx() { + if (ctx == null) { + ctx = + Context.newBuilder() // no dynamic languages needed + .allowHostAccess(HostAccess.ALL) // all public members + .build(); + } + return ctx; + } + static TruffleObject loadClass(String className) throws ClassNotFoundException { - java.lang.Class clazz = Class.forName(className); - org.graalvm.polyglot.Value clazzValue = Value.asValue(clazz).getMember("static"); - org.enso.jvm.interop.TruffleClassLoader holderRaw = new TruffleClassLoader(); - org.graalvm.polyglot.Value holderValue = Value.asValue(holderRaw); - holderValue.putMember("any", clazzValue); + var context = ctx(); + + var clazz = Class.forName(className); + var clazzValue1 = context.asValue(clazz); + var clazzValue2 = clazzValue1.getMember("static"); + var holderRaw = new TruffleClassLoader(); + var holderValue = context.asValue(holderRaw); + holderValue.putMember("any", clazzValue2); java.lang.Object clazzRaw = holderRaw.value; return (TruffleObject) clazzRaw; } From fc8f92edbc450b3d6c76afd2d1909414840df51c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 17:08:47 +0200 Subject: [PATCH 23/60] Removing debugging printf statatements --- .../main/java/org/enso/interpreter/runtime/EnsoContext.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 83080edc4aaf..966554419366 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -591,14 +591,11 @@ static Object lookupJavaClass( for (; ; ) { var fqn = binaryName.toString(); try { - System.err.println("fqn check: " + fqn); var hostSymbol = fn.loadClass(fqn); - System.err.println(" res: " + hostSymbol); if (hostSymbol != null) { return hostSymbol; } } catch (ClassNotFoundException | RuntimeException | InteropException ex) { - ex.printStackTrace(); collectExceptions.add(ex); } var at = fqn.lastIndexOf('.'); @@ -632,14 +629,11 @@ public TruffleObject lookupJavaClass(String className) { collectedExceptions // put here all exceptions ); if (hostSymbol instanceof TruffleObject) { - System.err.println(" returning host symbol " + hostSymbol); return (TruffleObject) hostSymbol; } } - System.err.println(" try deeper if " + HostEnsoUtils.isAot()); if (HostEnsoUtils.isAot()) { var javaHome = System.getProperty("java.home"); - System.err.println(" with javaHOme: " + javaHome); logger.info( () -> String.format("Class %s not found, trying to turn on JVM %s", className, javaHome)); var hostSymbol = From 1018884215d5919dcf7c20a4f21e061cbbc10993 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 17:09:49 +0200 Subject: [PATCH 24/60] Renaming to use OtherJvm prefix --- .../src/main/java/module-info.java | 4 ++-- ...OtherMessage.java => OtherJvmMessage.java} | 15 +++++++------ .../org/enso/jvm/interop/OtherJvmObject.java | 13 ++++++----- .../org/enso/jvm/interop/OtherJvmResult.java | 18 +++++++++++++++ ...olver.java => OtherJvmSymbolResolver.java} | 22 ++++++++----------- .../org/enso/jvm/interop/OtherResult.java | 6 ----- .../enso/jvm/interop/OtherJvmObjectTest.java | 12 +++++----- 7 files changed, 51 insertions(+), 39 deletions(-) rename lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/{OtherMessage.java => OtherJvmMessage.java} (95%) create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmResult.java rename lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/{OtherSymbolResolver.java => OtherJvmSymbolResolver.java} (80%) delete mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java diff --git a/lib/java/jvm-interop/src/main/java/module-info.java b/lib/java/jvm-interop/src/main/java/module-info.java index c5e907425fdc..9c3aa429a703 100644 --- a/lib/java/jvm-interop/src/main/java/module-info.java +++ b/lib/java/jvm-interop/src/main/java/module-info.java @@ -1,5 +1,5 @@ import org.enso.common.PolyglotSymbolResolver; -import org.enso.jvm.interop.OtherSymbolResolver; +import org.enso.jvm.interop.OtherJvmSymbolResolver; module org.enso.jvm.interop { // requires org.slf4j; @@ -9,7 +9,7 @@ requires org.enso.engine.common; provides PolyglotSymbolResolver with - OtherSymbolResolver; + OtherJvmSymbolResolver; requires org.graalvm.polyglot; requires org.graalvm.truffle; diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java similarity index 95% rename from lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java rename to lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index ce3855191257..0303cc020290 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -16,20 +16,21 @@ import org.enso.persist.Persistance; @Persistable(id = 81901) -record OtherMessage( // sends a message to the other side +record OtherJvmMessage( // sends a message to the other side long id, Message message, List args // with ReflectionLibrary-like arguments - ) implements Function> { + ) implements Function> { private static final Map OBJECTS = new HashMap<>(); @Persistable(id = 81908, allowInlining = false) - record ReturnValue(T value) implements OtherResult { + record ReturnValue(T value) implements OtherJvmResult { static ReturnValue create(T value) { return new ReturnValue<>(value); } } @Persistable(id = 81909, allowInlining = false) - record ThrowException(int kind, String msg) implements OtherResult { + record ThrowException(int kind, String msg) + implements OtherJvmResult { static ThrowException create(E ex) { var kind = @@ -57,7 +58,7 @@ static synchronized long registerObject(TruffleObject obj) { } @Override - public OtherResult apply(Channel t) { + public OtherJvmResult apply(Channel t) { try { var receiver = OBJECTS.get(id); assert receiver instanceof TruffleObject; @@ -70,9 +71,9 @@ static synchronized long registerObject(TruffleObject obj) { @Persistable(id = 81905) record LoadClass(String name) - implements Function> { + implements Function> { @Override - public OtherResult apply(Channel t) { + public OtherJvmResult apply(Channel t) { try { var clazzRaw = TruffleClassLoader.loadClass(name); return ReturnValue.create(clazzRaw); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 172f4fe9f91c..7031a4ca5ae0 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -32,14 +32,17 @@ Object send(Message message, Object[] args) throws Exception { if (message.getLibraryClass() != InteropLibrary.class) { throw UnsupportedMessageException.create(); } - var msg = new OtherMessage(id, message, List.of(args)); - var reply = channel.execute(OtherResult.class, msg); + var msg = new OtherJvmMessage(id, message, List.of(args)); + var reply = channel.execute(OtherJvmResult.class, msg); + return bindToChannel(reply.value(), channel); + } - if (reply.value() instanceof OtherJvmObject toBind) { + static Object bindToChannel(Object v, Channel ch) { + if (v instanceof OtherJvmObject toBind) { assert toBind.channel == null; - return new OtherJvmObject(channel, toBind.id); + return new OtherJvmObject(ch, toBind.id); } else { - return reply.value(); + return v; } } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmResult.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmResult.java new file mode 100644 index 000000000000..a2836b417dd4 --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmResult.java @@ -0,0 +1,18 @@ +package org.enso.jvm.interop; + +/** + * Interface describing a possible reply from the other JVM. + * + * @param the type of result, when the operation succeeds + * @param the type of exception when the operation fails + */ +sealed interface OtherJvmResult // Either R or E + permits OtherJvmMessage.ReturnValue, OtherJvmMessage.ThrowException { + /** + * Either returns the computed result or throws exception. + * + * @return the value + * @throws the exception if value couldn't be computed + */ + R value() throws E; +} diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java similarity index 80% rename from lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java rename to lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java index 1e71f169db9a..23f502c74de3 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherSymbolResolver.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; -import java.util.Properties; import org.enso.common.HostEnsoUtils; import org.enso.common.PolyglotSymbolResolver; import org.enso.jvm.channel.Channel; @@ -12,23 +11,23 @@ /** Resolves symbols via interop messages to the "other" HotSpot JVM. */ @org.openide.util.lookup.ServiceProvider(service = PolyglotSymbolResolver.class) -public final class OtherSymbolResolver extends PolyglotSymbolResolver { - private Channel jvm; +public final class OtherJvmSymbolResolver extends PolyglotSymbolResolver { + private Channel channel; @Override protected Object handleLoadClass(String name) throws ClassNotFoundException { if (!HostEnsoUtils.isAot()) { throw new ClassNotFoundException("Only works in AOT mode!"); } - if (jvm == null) { + if (channel == null) { try { - jvm = initializeChannel(); + channel = initializeChannel(); } catch (IOException | URISyntaxException ex) { throw new ClassNotFoundException("Cannot initialize JVM", ex); } } - var result = jvm.execute(OtherResult.class, new OtherMessage.LoadClass(name)); - return result.value(); + var result = channel.execute(OtherJvmResult.class, new OtherJvmMessage.LoadClass(name)); + return OtherJvmObject.bindToChannel(result.value(), channel); } private Channel initializeChannel() throws IOException, URISyntaxException { @@ -53,14 +52,11 @@ private Channel initializeChannel() throws IOException, URISyntaxException { if (assertsOn) { commandAndArgs.add("-ea"); } - Properties props = null; - if (props != null) { - for (var e : props.entrySet()) { - commandAndArgs.add("-D" + e.getKey() + "=" + e.getValue()); - } - } commandAndArgs.add("--sun-misc-unsafe-memory-access=allow"); + commandAndArgs.add("-Dpolyglot.engine.WarnInterpreterOnly=false"); + commandAndArgs.add("-Dtruffle.UseFallbackRuntime=true"); commandAndArgs.add("--enable-native-access=org.graalvm.truffle"); + commandAndArgs.add("--enable-native-access=org.enso.jvm.channel"); commandAndArgs.add("--add-opens=java.base/java.nio=ALL-UNNAMED"); if (!component.isDirectory()) { throw new IOException("Cannot find " + component + " directory"); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java deleted file mode 100644 index b54058c6198f..000000000000 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherResult.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.enso.jvm.interop; - -sealed interface OtherResult // either R or E - permits OtherMessage.ReturnValue, OtherMessage.ThrowException { - R value() throws E; -} diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index c0e0b2026f58..072afa06f337 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -25,7 +25,7 @@ public void wrapBigDecimal() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherMessage.registerObject((TruffleObject) bigUnwrap); + var id = OtherJvmMessage.registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var otherValue = ctx.asValue(other); @@ -52,7 +52,7 @@ public void wrapArray() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherMessage.registerObject((TruffleObject) bigUnwrap); + var id = OtherJvmMessage.registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var otherValue = ctx.asValue(other); @@ -71,8 +71,8 @@ public void wrapArray() { @Test public void loadClassViaMessage() throws Exception { - var msg = new OtherMessage.LoadClass("java.lang.Short"); - var shortRaw = CHANNEL.execute(OtherResult.class, msg).value(); + var msg = new OtherJvmMessage.LoadClass("java.lang.Short"); + var shortRaw = CHANNEL.execute(OtherJvmResult.class, msg).value(); if (shortRaw instanceof OtherJvmObject other) { shortRaw = new OtherJvmObject(CHANNEL, other.id()); } @@ -84,9 +84,9 @@ public void loadClassViaMessage() throws Exception { @Test public void classNotFoundError() { - var msg = new OtherMessage.LoadClass("java.lang.unknown.Clazz"); + var msg = new OtherJvmMessage.LoadClass("java.lang.unknown.Clazz"); try { - var shortRaw = CHANNEL.execute(OtherResult.class, msg).value(); + var shortRaw = CHANNEL.execute(OtherJvmResult.class, msg).value(); fail("Should yield an exception: " + shortRaw); } catch (ClassNotFoundException ex) { assertThat(ex.getMessage(), StringContains.containsString("java.lang.unknown.Clazz")); From 9e75dd508dda85313431a1492548528226a39fe2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 18:06:41 +0200 Subject: [PATCH 25/60] Environment is associated with DistributionManager --- .../DefaultDistributionConfiguration.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala index 59e4b9b70345..d87e1ccc0324 100644 --- a/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala +++ b/lib/scala/project-manager/src/main/scala/org/enso/projectmanager/versionmanagement/DefaultDistributionConfiguration.scala @@ -61,11 +61,10 @@ object DefaultDistributionConfiguration userInterface: RuntimeVersionManagementUserInterface ): RuntimeVersionManager = new RuntimeVersionManager( - environment = this.environment, - userInterface = userInterface, - distributionManager = distributionManager, - graalVersionManager = - new GraalVersionManager(distributionManager, environment), + environment = this.environment, + userInterface = userInterface, + distributionManager = distributionManager, + graalVersionManager = new GraalVersionManager(distributionManager), temporaryDirectoryManager = temporaryDirectoryManager, resourceManager = resourceManager, engineReleaseProvider = engineReleaseProvider, From 14d78f0b37e7a6dc66c8439807cc85b2c9ad31d2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 18:26:24 +0200 Subject: [PATCH 26/60] Easier to support execute message --- .../enso/jvm/interop/TruffleClassLoader.java | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index 78092e7d88e1..0cc8c81bb071 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -33,33 +33,19 @@ static TruffleObject loadClass(String className) throws ClassNotFoundException { var clazzValue2 = clazzValue1.getMember("static"); var holderRaw = new TruffleClassLoader(); var holderValue = context.asValue(holderRaw); - holderValue.putMember("any", clazzValue2); - java.lang.Object clazzRaw = holderRaw.value; + holderValue.execute(clazzValue2); + var clazzRaw = holderRaw.value; return (TruffleObject) clazzRaw; } @ExportMessage - void writeMember(String name, Object value) { - this.value = value; - } - - @ExportMessage - boolean hasMembers() { - return false; + final Object execute(Object[] values) { + this.value = values[0]; + return this; } @ExportMessage - boolean isMemberModifiable(String member) { + final boolean isExecutable() { return true; } - - @ExportMessage - boolean isMemberInsertable(String member) { - return false; - } - - @ExportMessage - Object getMembers(boolean includeInternal) { - return this; - } } From 2bf6895c0c56209a335c2815167af38b46786512 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 10 Jun 2025 19:15:43 +0200 Subject: [PATCH 27/60] Enter and leave the context before sending a message --- .../src/main/java/org/enso/jvm/interop/OtherJvmMessage.java | 3 +++ .../src/main/java/org/enso/jvm/interop/OtherJvmObject.java | 3 +++ .../src/main/java/org/enso/jvm/interop/TruffleClassLoader.java | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index 0303cc020290..bf715644580f 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -60,12 +60,15 @@ static synchronized long registerObject(TruffleObject obj) { @Override public OtherJvmResult apply(Channel t) { try { + TruffleClassLoader.ctx().enter(); var receiver = OBJECTS.get(id); assert receiver instanceof TruffleObject; var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); return new ReturnValue<>(res); } catch (Exception ex) { return ThrowException.create(ex); + } finally { + TruffleClassLoader.ctx().leave(); } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 7031a4ca5ae0..aea6ba55a927 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -30,6 +30,9 @@ long id() { @ExportMessage Object send(Message message, Object[] args) throws Exception { if (message.getLibraryClass() != InteropLibrary.class) { + if (message.getParameterCount() == 1 && message.getReturnType() == boolean.class) { + return false; + } throw UnsupportedMessageException.create(); } var msg = new OtherJvmMessage(id, message, List.of(args)); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index 0cc8c81bb071..dbae0278b323 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -15,7 +15,7 @@ final class TruffleClassLoader implements TruffleObject { private TruffleClassLoader() {} - private static synchronized Context ctx() { + static synchronized Context ctx() { if (ctx == null) { ctx = Context.newBuilder() // no dynamic languages needed From 3c81a1e1eab5544cfa04b7b241700a501490dc0a Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 11 Jun 2025 08:04:46 +0200 Subject: [PATCH 28/60] Delegate to default library implementation for non-InteropLibrary messages --- .../org/enso/jvm/interop/OtherJvmObject.java | 20 +++++++++++-------- .../enso/jvm/interop/OtherJvmObjectTest.java | 15 ++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index aea6ba55a927..23e58ec47487 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -3,7 +3,6 @@ import com.oracle.truffle.api.CompilerDirectives; 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 com.oracle.truffle.api.library.Message; @@ -13,6 +12,9 @@ @ExportLibrary(ReflectionLibrary.class) final class OtherJvmObject implements TruffleObject { + /** receiver for other than InteropLibrary messages */ + private static final Object POJO = new Object(); + private final Channel channel; private final long id; @@ -30,14 +32,16 @@ long id() { @ExportMessage Object send(Message message, Object[] args) throws Exception { if (message.getLibraryClass() != InteropLibrary.class) { - if (message.getParameterCount() == 1 && message.getReturnType() == boolean.class) { - return false; - } - throw UnsupportedMessageException.create(); + // we need to invoke default implementation of library + // to handle the message in a proper way + // hence provide POJO as a receiver + return ReflectionLibrary.getUncached().send(POJO, message, args); + } else { + // proper dispatch to the other JVM + var msg = new OtherJvmMessage(id, message, List.of(args)); + var reply = channel.execute(OtherJvmResult.class, msg); + return bindToChannel(reply.value(), channel); } - var msg = new OtherJvmMessage(id, message, List.of(args)); - var reply = channel.execute(OtherJvmResult.class, msg); - return bindToChannel(reply.value(), channel); } static Object bindToChannel(Object v, Channel ch) { diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 072afa06f337..5fca2d32f656 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -8,6 +8,7 @@ import com.oracle.truffle.api.interop.TruffleObject; import java.math.BigDecimal; +import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.jvm.channel.Channel; import org.enso.test.utils.ContextUtils; import org.hamcrest.core.StringContains; @@ -92,4 +93,18 @@ public void classNotFoundError() { assertThat(ex.getMessage(), StringContains.containsString("java.lang.unknown.Clazz")); } } + + @Test + public void messageFromAnUnsupportedLibrary() { + var bigReal = new BigDecimal("-1.1"); + var bigValue = ctx.asValue(bigReal); + var bigUnwrap = ctx.unwrapValue(bigValue); + assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); + + var id = OtherJvmMessage.registerObject((TruffleObject) bigUnwrap); + var other = new OtherJvmObject(CHANNEL, id); + + var noType = TypesLibrary.getUncached().hasType(other); + assertFalse("Other JVM objects don't have type", noType); + } } From bb92781ffdd506f44cf8d7a1ab641111a26e277b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 11 Jun 2025 09:25:38 +0200 Subject: [PATCH 29/60] Even mock Channel has two connected instances --- .../src/main/java/org/enso/jvm/channel/Channel.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 72d770eda407..5590774acd9e 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -47,6 +47,7 @@ public final class Channel implements AutoCloseable { private final MethodHandle callbackFn; private final JNI.JClass channelClass; private final JNI.JMethodID channelHandle; + private final Channel otherMockChannel; /** The SubstrateVM side of a channel. */ private Channel( @@ -62,6 +63,7 @@ private Channel( this.callbackFn = null; this.channelClass = handleClass; this.channelHandle = handleFn; + this.otherMockChannel = null; } /** The HotSpot JVM side of a channel. */ @@ -75,6 +77,7 @@ private Channel(long id, Persistance.Pool pool, long isolate, long callbackFn) { this.env = null; this.channelClass = null; this.channelHandle = null; + this.otherMockChannel = null; var fnCallbackAddress = MemorySegment.ofAddress(callbackFn); var fnDescriptor = @@ -91,7 +94,7 @@ private Channel(long id, Persistance.Pool pool, long isolate, long callbackFn) { * Mock constructor. Creates a channel that simulates sending of the messages inside of the same * JVM. Useful for testing. */ - private Channel(long id, Persistance.Pool pool) { + private Channel(Channel otherOrNull, long id, Persistance.Pool pool) { if (ImageInfo.inImageCode()) { throw new IllegalStateException("Only usable in HotSpot"); } @@ -102,6 +105,7 @@ private Channel(long id, Persistance.Pool pool) { this.env = null; this.channelClass = null; this.channelHandle = null; + this.otherMockChannel = otherOrNull != null ? otherOrNull : new Channel(this, id, pool); } /** @@ -123,7 +127,7 @@ public static synchronized Channel create( } var id = idCounter++; if (jvm == null) { - return new Channel(id, pool); + return new Channel(null, id, pool); } if (!ImageInfo.inImageCode()) { @@ -260,8 +264,9 @@ private long toSubstrateMessage(MemorySegment seg) { } private long toDirectMessage(ByteBuffer buf) throws IOException { + assert otherMockChannel != null; buf.position(0); - var len = handleWithChannel(this, buf); + var len = handleWithChannel(otherMockChannel, buf); buf.position(0); return len; } From 0b970a26ce0243b6bbdd1e4b368440425a0325ca Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 11 Jun 2025 12:27:58 +0200 Subject: [PATCH 30/60] Giving Channel.getData() to store local info on both JVM sides --- .../java/org/enso/jvm/channel/Channel.java | 105 ++++++++++++------ .../jvm/channel/ChannelInSingleJvmTest.java | 51 ++++++++- .../org/enso/jvm/interop/OtherJvmMessage.java | 22 ++-- .../org/enso/jvm/interop/OtherJvmObject.java | 6 +- .../org/enso/jvm/interop/OtherJvmPool.java | 27 +++++ .../jvm/interop/OtherJvmSymbolResolver.java | 22 ++-- .../enso/jvm/interop/OtherJvmObjectTest.java | 8 +- .../org/enso/os/environment/jni/TestMain.java | 16 +-- .../os/environment/jni/LoadClassTest.java | 2 +- .../persist/impl/PersistableProcessor.java | 10 +- 10 files changed, 185 insertions(+), 84 deletions(-) create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 5590774acd9e..b1c4e03f9b16 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -26,8 +26,14 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.WordFactory; -/** Channel connects two {@link JVM} instances. */ -public final class Channel implements AutoCloseable { +/** + * Channel connects two {@link JVM} instances. A "channel" creates two identical instances of the + * {@code Channel} on both sides of the "channel" - e.g. in each of the JVMs. The instances are + * initialized with the same {@link Persistance.Pool}, so they both understand the same messages. + * + * @param internal data of the channel + */ +public final class Channel implements AutoCloseable { /** * @GuardedBy("Channel.class") */ @@ -38,6 +44,9 @@ public final class Channel implements AutoCloseable { */ private static long idCounter = 1; + /** data associated with the channel */ + private final Data data; + /** persistance pool associated with this channel object */ private final Persistance.Pool pool; @@ -47,16 +56,18 @@ public final class Channel implements AutoCloseable { private final MethodHandle callbackFn; private final JNI.JClass channelClass; private final JNI.JMethodID channelHandle; - private final Channel otherMockChannel; + private final Channel otherMockChannel; /** The SubstrateVM side of a channel. */ private Channel( long id, + Data data, Persistance.Pool pool, JNI.JNIEnv env, JNI.JClass handleClass, JNI.JMethodID handleFn) { this.id = id; + this.data = data; this.pool = pool; this.env = env; this.isolate = -1; @@ -67,11 +78,12 @@ private Channel( } /** The HotSpot JVM side of a channel. */ - private Channel(long id, Persistance.Pool pool, long isolate, long callbackFn) { + private Channel(long id, Data data, Persistance.Pool pool, long isolate, long callbackFn) { if (ImageInfo.inImageCode()) { throw new IllegalStateException("Only usable in HotSpot"); } this.id = id; + this.data = data; this.pool = pool; this.isolate = isolate; this.env = null; @@ -94,40 +106,44 @@ private Channel(long id, Persistance.Pool pool, long isolate, long callbackFn) { * Mock constructor. Creates a channel that simulates sending of the messages inside of the same * JVM. Useful for testing. */ - private Channel(Channel otherOrNull, long id, Persistance.Pool pool) { + private Channel( + Data myData, Channel otherOrNull, Data otherData, long id, Persistance.Pool pool) { if (ImageInfo.inImageCode()) { throw new IllegalStateException("Only usable in HotSpot"); } this.id = id; + this.data = myData; this.pool = pool; this.isolate = -2; this.callbackFn = null; this.env = null; this.channelClass = null; this.channelHandle = null; - this.otherMockChannel = otherOrNull != null ? otherOrNull : new Channel(this, id, pool); + this.otherMockChannel = + otherOrNull != null + ? otherOrNull // use other channel when provided + : // otherwise allocate new and pass this reference to it + new Channel<>(otherData, this, null, id, pool); } /** * Factory method to initialize the Channel in the SubstrateVM. * + * @param type of internal data as well as provider of the pool * @param jvm instance of HotSpot JVM to connect to (can be {@code null} to create a mock channel * inside of a single JVM) - * @param poolClass the class which has public default constructor and can supply an instance of - * persistance pool to use for communication + * @param dataAndPoolClass the class which has public default constructor and can supply an + * instance of persistance pool to use for communication * @return channel for sending messages to the HotSpot JVM */ - public static synchronized Channel create( - JVM jvm, Class> poolClass) { - Persistance.Pool pool; - try { - pool = poolClass.getConstructor().newInstance().get(); - } catch (ReflectiveOperationException ex) { - throw new IllegalArgumentException(ex); - } + public static synchronized > Channel create( + JVM jvm, Class dataAndPoolClass) { + var data = newInstance(dataAndPoolClass); + Persistance.Pool pool = data.get(); var id = idCounter++; if (jvm == null) { - return new Channel(null, id, pool); + var otherData = newInstance(dataAndPoolClass); + return new Channel<>(data, null, otherData, id, pool); } if (!ImageInfo.inImageCode()) { @@ -136,7 +152,7 @@ public static synchronized Channel create( var e = jvm.env(); var classNameWithSlashes = Channel.class.getName().replace('.', '/'); try (var classInC = CTypeConversion.toCString(classNameWithSlashes); - var poolClassInC = CTypeConversion.toCString(poolClass.getName()); + var poolClassInC = CTypeConversion.toCString(dataAndPoolClass.getName()); var createInC = CTypeConversion.toCString("createJvmPeerChannel"); var createSigInC = CTypeConversion.toCString("(JJJLjava/lang/String;)Z"); // var handleInC = CTypeConversion.toCString("handleJvmMessage"); @@ -152,7 +168,7 @@ public static synchronized Channel create( var handleMethod = fn.getGetStaticMethodID().call(e, channelClass, handleInC.get(), handleSigInC.get()); - var channel = new Channel(id, pool, e, channelClass, handleMethod); + var channel = new Channel<>(id, data, pool, e, channelClass, handleMethod); var arg = StackValue.get(4, JNI.JValue.class); arg.addressOf(0).setLong(id); @@ -168,16 +184,16 @@ public static synchronized Channel create( } } - /** Allocates new channel with given ID in the HotSpot VM. Called via JNI/foreign interface. */ - private static boolean createJvmPeerChannel( - long id, long threadId, long callbackFn, String poolClassName) throws Throwable { - @SuppressWarnings("unchecked") - var factory = - (Supplier) Class.forName(poolClassName).getConstructor().newInstance(); - var pool = factory.get(); - var channel = new Channel(id, pool, threadId, callbackFn); - var prev = ID_TO_CHANNEL.put(id, channel); - return prev == null; + /** + * Getter for data associated with the channel. Each instance of {@code Channel} on both sides of + * the "channel" gets different instance of {@code Data}. The data may be used in the functions + * that implement the logic in {@link #execute} message processing. + * + * @return data associated with this channel + * @see #execute + */ + public final Data getData() { + return data; } /** @@ -197,8 +213,33 @@ private static boolean createJvmPeerChannel( * from this method */ @SuppressWarnings("unchecked") - public final R execute(Class resultType, Function msg) { - return (R) executeImpl(pool, resultType, msg); + public final R execute( + Class resultType, Function, R> msg) { + return (R) executeImpl(pool, resultType, (Function) msg); + } + + // + // implementation + // + + private static T newInstance(Class poolClass) { + try { + return poolClass.getConstructor().newInstance(); + } catch (ReflectiveOperationException ex) { + throw new IllegalArgumentException(ex); + } + } + + /** Allocates new channel with given ID in the HotSpot VM. Called via JNI/foreign interface. */ + @SuppressWarnings("unchecked") + private static boolean createJvmPeerChannel( + long id, long threadId, long callbackFn, String poolClassName) throws Throwable { + var dataAndPoolClass = Class.forName(poolClassName); + var data = (Supplier) newInstance(dataAndPoolClass); + var pool = data.get(); + var channel = new Channel<>(id, data, pool, threadId, callbackFn); + var prev = ID_TO_CHANNEL.put(id, channel); + return prev == null; } private static final CEntryPointLiteral CALLBACK_FN = @@ -300,7 +341,7 @@ private void checkForException(JNI.JNIEnv e) { private R executeImpl( // handles this.execute Persistance.Pool pool, // the pool with persitance Class replyType, // requested return type - Function msg // function to serde to the other JVM + Function, ? extends R> msg // function to serde to the other JVM ) { var address = 0L; try { diff --git a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java index 2574488c6603..f20bce18f282 100644 --- a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java +++ b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java @@ -4,13 +4,29 @@ import static org.junit.Assert.assertNotNull; import java.util.function.Function; +import java.util.function.Supplier; import org.enso.persist.Persistable; +import org.enso.persist.Persistance; import org.junit.Test; public class ChannelInSingleJvmTest { + public static final class PrivateData implements Supplier { + static int countInstances; + int counter; + + public PrivateData() { + countInstances++; + } + + @Override + public Persistance.Pool get() { + return Persistables.POOL; + } + } + @Test - public void exchangeMessages() { - var ch = Channel.create(null, Persistables.class); + public void exchangeMessageThatModifiesItself() { + var ch = Channel.create(null, PrivateData.class); var msg = new Increment(10); @@ -21,8 +37,25 @@ public void exchangeMessages() { assertEquals("Original value remains", 10, msg.valueToIncrement()); } + @Test + public void exchangeMessageThatModifiesPrivateData() { + PrivateData.countInstances = 0; + var ch = Channel.create(null, PrivateData.class); + assertEquals("Two channels & data created", 2, PrivateData.countInstances); + assertEquals("By default we are at zero", 0, ch.getData().counter); + + var msg = new AssignPrivateData(10); + var newMsg = ch.execute(AssignPrivateData.class, msg); + + assertEquals("PrivateData.counter hasn't been changed", 0, ch.getData().counter); + + assertNotNull("Got a value", newMsg); + assertEquals("10 + 1", 11, newMsg.valueToSet()); + assertEquals("Original value remains", 10, msg.valueToSet()); + } + @Persistable(id = 8341) - static final class Increment implements Function { + static final class Increment implements Function { int valueToIncrement; Increment(int valueToIncrement) { @@ -34,9 +67,19 @@ int valueToIncrement() { } @Override - public Increment apply(Channel t) { + public Increment apply(Object ignore) { valueToIncrement++; return this; } } + + @Persistable(id = 8342) + static record AssignPrivateData(int valueToSet) + implements Function, AssignPrivateData> { + @Override + public AssignPrivateData apply(Channel t) { + t.getData().counter = valueToSet; + return new AssignPrivateData(t.getData().counter + 1); + } + } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index bf715644580f..618631de30ff 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -7,9 +7,7 @@ import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Function; import org.enso.jvm.channel.Channel; import org.enso.persist.Persistable; @@ -18,9 +16,9 @@ @Persistable(id = 81901) record OtherJvmMessage( // sends a message to the other side long id, Message message, List args // with ReflectionLibrary-like arguments - ) implements Function> { - private static final Map OBJECTS = new HashMap<>(); - + ) + implements Function< + Channel, OtherJvmResult> { @Persistable(id = 81908, allowInlining = false) record ReturnValue(T value) implements OtherJvmResult { static ReturnValue create(T value) { @@ -51,17 +49,11 @@ public V value() throws E { } } - static synchronized long registerObject(TruffleObject obj) { - var size = OBJECTS.size() + 1; - OBJECTS.put((long) size, obj); - return size; - } - @Override - public OtherJvmResult apply(Channel t) { + public OtherJvmResult apply(Channel t) { try { TruffleClassLoader.ctx().enter(); - var receiver = OBJECTS.get(id); + var receiver = t.getData().findObject(id); assert receiver instanceof TruffleObject; var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); return new ReturnValue<>(res); @@ -97,7 +89,7 @@ protected void writeObject(TruffleObject obj, Output out) throws IOException { if (obj instanceof OtherJvmObject other) { out.writeLong(other.id()); } else { - var id = registerObject(obj); + var id = OtherJvmPool.registerObject(obj); out.writeLong(-id); } } @@ -108,7 +100,7 @@ protected TruffleObject readObject(Input in) throws IOException, ClassNotFoundEx if (id < 0) { return new OtherJvmObject(null, -id); } else { - var cached = OBJECTS.get(id); + var cached = OtherJvmPool.findObject(id); assert cached != null; return cached; } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 23e58ec47487..73a627459ce6 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -15,10 +15,10 @@ final class OtherJvmObject implements TruffleObject { /** receiver for other than InteropLibrary messages */ private static final Object POJO = new Object(); - private final Channel channel; + private final Channel channel; private final long id; - OtherJvmObject(Channel channel, long id) { + OtherJvmObject(Channel channel, long id) { assert id > 0; this.channel = channel; this.id = id; @@ -44,7 +44,7 @@ Object send(Message message, Object[] args) throws Exception { } } - static Object bindToChannel(Object v, Channel ch) { + static Object bindToChannel(Object v, Channel ch) { if (v instanceof OtherJvmObject toBind) { assert toBind.channel == null; return new OtherJvmObject(ch, toBind.id); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java new file mode 100644 index 000000000000..4c9551c649f4 --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -0,0 +1,27 @@ +package org.enso.jvm.interop; + +import com.oracle.truffle.api.interop.TruffleObject; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.enso.persist.Persistance; + +/** Pool of Truffle objects associated with {@link Channel}. */ +public final class OtherJvmPool implements Supplier { + private static final Map OBJECTS = new HashMap<>(); + + static synchronized long registerObject(TruffleObject obj) { + var size = OBJECTS.size() + 1; + OBJECTS.put((long) size, obj); + return size; + } + + static synchronized TruffleObject findObject(long id) { + return OBJECTS.get(id); + } + + @Override + public final Persistance.Pool get() { + return Persistables.POOL; + } +} diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java index 23f502c74de3..a43514e7f1ea 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java @@ -12,13 +12,10 @@ /** Resolves symbols via interop messages to the "other" HotSpot JVM. */ @org.openide.util.lookup.ServiceProvider(service = PolyglotSymbolResolver.class) public final class OtherJvmSymbolResolver extends PolyglotSymbolResolver { - private Channel channel; + private Channel channel; @Override protected Object handleLoadClass(String name) throws ClassNotFoundException { - if (!HostEnsoUtils.isAot()) { - throw new ClassNotFoundException("Only works in AOT mode!"); - } if (channel == null) { try { channel = initializeChannel(); @@ -30,7 +27,17 @@ protected Object handleLoadClass(String name) throws ClassNotFoundException { return OtherJvmObject.bindToChannel(result.value(), channel); } - private Channel initializeChannel() throws IOException, URISyntaxException { + private Channel initializeChannel() throws IOException, URISyntaxException { + var jvm = + HostEnsoUtils.isAot() + ? // normally we run in AOT mode + initializeJvm() // then create HotSpot JVM + : // but for debugging purposes we can also + null; // emulate the connection in a single JVM + return Channel.create(jvm, OtherJvmPool.class); + } + + private JVM initializeJvm() throws IOException, URISyntaxException { var home = System.getProperty("java.home"); if (home == null) { throw new IOException("No java.home specified"); @@ -39,13 +46,11 @@ private Channel initializeChannel() throws IOException, URISyntaxException { if (!javaHome.exists()) { throw new IOException("JVM doesn't exists: " + javaHome); } - var loc = getClass().getProtectionDomain().getCodeSource().getLocation(); var component = new File(loc.toURI().resolve("..")).getAbsoluteFile(); if (!component.getName().equals("component")) { component = new File(component, "component"); } - var commandAndArgs = new ArrayList(); var assertsOn = false; assert assertsOn = true; @@ -63,7 +68,6 @@ private Channel initializeChannel() throws IOException, URISyntaxException { } commandAndArgs.add("--module-path=" + component.getPath()); commandAndArgs.add("-Djdk.module.main=org.enso.jvm.interop"); - var jvm = JVM.create(javaHome, commandAndArgs.toArray(new String[0])); - return Channel.create(jvm, Persistables.class); + return JVM.create(javaHome, commandAndArgs.toArray(new String[0])); } } diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 5fca2d32f656..c1ce49b230dd 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -17,7 +17,7 @@ public class OtherJvmObjectTest { @ClassRule public static final ContextUtils ctx = ContextUtils.newBuilder("js").build(); - private static final Channel CHANNEL = Channel.create(null, Persistables.class); + private static final Channel CHANNEL = Channel.create(null, OtherJvmPool.class); @Test public void wrapBigDecimal() { @@ -26,7 +26,7 @@ public void wrapBigDecimal() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherJvmMessage.registerObject((TruffleObject) bigUnwrap); + var id = OtherJvmPool.registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var otherValue = ctx.asValue(other); @@ -53,7 +53,7 @@ public void wrapArray() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherJvmMessage.registerObject((TruffleObject) bigUnwrap); + var id = OtherJvmPool.registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var otherValue = ctx.asValue(other); @@ -101,7 +101,7 @@ public void messageFromAnUnsupportedLibrary() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherJvmMessage.registerObject((TruffleObject) bigUnwrap); + var id = OtherJvmPool.registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var noType = TypesLibrary.getUncached().hasType(other); diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java index 5dba57038321..2b220884580f 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java +++ b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java @@ -34,9 +34,9 @@ static BigInteger factorial(long n) { } @Persistable(id = 430607) - record RequestFactorial(long n) implements Function { + record RequestFactorial(long n) implements Function, Void> { @Override - public Void apply(Channel channel) { + public Void apply(Channel channel) { var res = factorial(n).toString(); channel.execute(Void.class, new ReportResult(n, res)); return null; @@ -44,9 +44,9 @@ public Void apply(Channel channel) { } @Persistable(id = 430608) - record ComputeFactorial(long n) implements Function { + record ComputeFactorial(long n) implements Function { @Override - public BigInteger apply(Channel channel) { + public BigInteger apply(Object ignore) { var res = factorial(n); return res; } @@ -64,9 +64,9 @@ public Void apply(Channel otherVM) { } @Persistable(id = 430609) - record CountDownAndReturn(long value, long acc) implements Function { + record CountDownAndReturn(long value, long acc) implements Function, Long> { @Override - public Long apply(Channel otherVM) { + public Long apply(Channel otherVM) { if (value <= 1) { return acc; } else { @@ -76,9 +76,9 @@ public Long apply(Channel otherVM) { } @Persistable(id = 430610) - record CountDownAndThrow(long value, long acc) implements Function { + record CountDownAndThrow(long value, long acc) implements Function, Void> { @Override - public Void apply(Channel otherVM) { + public Void apply(Channel otherVM) { if (value <= 1) { throw new IllegalStateException("" + acc); } else { diff --git a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java index 9063ae5fc2d4..53bc3e0ec8df 100644 --- a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java +++ b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java @@ -40,7 +40,7 @@ private static JVM jvm() { return impl; } - private Channel channel; + private Channel channel; @Before public void initializeChannel() throws Exception { diff --git a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java index df7a748c9d7c..6d2d99dca8df 100644 --- a/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java +++ b/lib/java/persistance-dsl/src/main/java/org/enso/persist/impl/PersistableProcessor.java @@ -94,12 +94,9 @@ public boolean process(Set annotations, RoundEnvironment w.append("package " + entry.getKey() + ";\n"); w.append("import org.enso.persist.Persistance;\n"); - w.append("import java.util.function.Supplier;\n"); - w.append( - "public final class Persistables extends Persistance.Pool implements" - + " Supplier {\n"); + w.append("public final class Persistables extends Persistance.Pool {\n"); w.append(" public static final Persistance.Pool POOL = new Persistables();\n"); - w.append(" public Persistables() {\n"); + w.append(" private Persistables() {\n"); w.append(" super(\"").append(entry.getKey()).append("\","); var lineEnding = "\n"; for (var idName : props.entrySet()) { @@ -109,9 +106,6 @@ public boolean process(Set annotations, RoundEnvironment } w.append("\n );\n"); w.append(" }\n"); - w.append(" public Persistance.Pool get() {\n"); - w.append(" return this;\n"); - w.append(" }\n"); w.append("}\n"); } var out = processingEnv.getFiler().createResource(propsWhere, propsPkg, propsName); From 3c7b2a61789d543b5b215cb010e4fdcbddaca4bf Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 11 Jun 2025 14:48:42 +0200 Subject: [PATCH 31/60] Making os-environment/test pass again --- build.sbt | 16 ++--- .../main/java/org/enso/jvm/channel/JNI.java | 2 +- .../os/environment/jni/LoadClassTest.java | 70 ------------------- 3 files changed, 7 insertions(+), 81 deletions(-) diff --git a/build.sbt b/build.sbt index 43ec48735abd..9b8f31b51dbc 100644 --- a/build.sbt +++ b/build.sbt @@ -4341,26 +4341,23 @@ lazy val `os-environment` = .settings( frgaalJavaCompilerSetting, libraryDependencies ++= slf4jApi ++ Seq( - "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", - "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", - "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided", - "commons-io" % "commons-io" % commonsIoVersion, - "junit" % "junit" % junitVersion % Test, - "com.github.sbt" % "junit-interface" % junitIfVersion % Test + "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion % "provided", + "org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % "provided", + "commons-io" % "commons-io" % commonsIoVersion, + "junit" % "junit" % junitVersion % Test, + "com.github.sbt" % "junit-interface" % junitIfVersion % Test ), Compile / moduleDependencies ++= slf4jApi ++ Seq( "commons-io" % "commons-io" % commonsIoVersion, "org.graalvm.sdk" % "nativeimage" % graalMavenPackagesVersion, "org.graalvm.polyglot" % "polyglot" % graalMavenPackagesVersion, "com.typesafe" % "config" % typesafeConfigVersion, - "org.graalvm.sdk" % "word" % graalMavenPackagesVersion, - "org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion + "org.graalvm.sdk" % "word" % graalMavenPackagesVersion ), Compile / internalModuleDependencies ++= Seq( (`engine-common` / Compile / exportedModule).value, (`persistance` / Compile / exportedModule).value, (`jvm-channel` / Compile / exportedModule).value, - (`jvm-interop` / Compile / exportedModule).value, (`logging-utils` / Compile / exportedModule).value, (`logging-config` / Compile / exportedModule).value ), @@ -4416,7 +4413,6 @@ lazy val `os-environment` = Test / fork := true ) .dependsOn(`jvm-channel`) - .dependsOn(`jvm-interop`) .dependsOn(`persistance`) .dependsOn(`persistance-dsl` % "provided") .dependsOn(`engine-common`) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java index b3d239e87aa1..a42002ea5b0f 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/JNI.java @@ -18,7 +18,7 @@ import org.graalvm.word.PointerBase; @CContext(JNIDirectives.class) -public final class JNI { +final class JNI { @CConstant static native int JNI_OK(); diff --git a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java index 53bc3e0ec8df..9b851316af6b 100644 --- a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java +++ b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java @@ -47,76 +47,6 @@ public void initializeChannel() throws Exception { channel = Channel.create(jvm(), JVMPeer.class); } - /* @Test - public void invokeParseShortMethod() { - var env = env(); - assertTrue("JNI created", env.isNonNull()); - - var findClassFn = env.getFunctions().getFindClass(); - var getStaticMethodIDFn = env.getFunctions().getGetStaticMethodID(); - var newStringFn = env.getFunctions().getNewStringUTF(); - var callStaticMethodFn = env.getFunctions().getCallStaticIntMethodA(); - - try (var shortName = CTypeConversion.toCString("java/lang/Short"); - var valueOfName = CTypeConversion.toCString("parseShort"); - var valueOfSig = CTypeConversion.toCString("(Ljava/lang/String;)S"); - var toParse = CTypeConversion.toCString("345"); ) { - var Short = findClassFn.call(env, shortName.get()); - - assertTrue("Short class is loaded", Short.isNonNull()); - - var valueOf = getStaticMethodIDFn.call(env, Short, valueOfName.get(), valueOfSig.get()); - assertTrue("valueOf method found", valueOf.isNonNull()); - - var args = StackValue.get(JNI.JValue.class); - var str = newStringFn.call(env, toParse.get()); - args.setJObject(str); - var res = callStaticMethodFn.call(env, Short, valueOf, args); - assertEquals(345, res); - } - } - - @Test - public void setSystemProperty() { - var env = env(); - assertTrue("JNI created", env.isNonNull()); - - var findClassFn = env.getFunctions().getFindClass(); - var getStaticMethodIDFn = env.getFunctions().getGetStaticMethodID(); - var newStringFn = env.getFunctions().getNewStringUTF(); - var strLengthFn = env.getFunctions().getGetStringUTFLength(); - var strCharsFn = env.getFunctions().getGetStringUTFChars(); - var strReleaseFn = env.getFunctions().getReleaseStringUTFChars(); - var callStaticMethodFn = env.getFunctions().getCallStaticObjectMethodA(); - - try (var systemName = CTypeConversion.toCString("java/lang/System"); - var getPropertyName = CTypeConversion.toCString("getProperty"); - var getPropertySig = CTypeConversion.toCString("(Ljava/lang/String;)Ljava/lang/String;"); - var propName = CTypeConversion.toCString("say"); ) { - var System = findClassFn.call(env, systemName.get()); - - assertTrue("System class is loaded", System.isNonNull()); - - var valueOf = - getStaticMethodIDFn.call(env, System, getPropertyName.get(), getPropertySig.get()); - assertTrue("getProperty method found", valueOf.isNonNull()); - - var args = StackValue.get(JNI.JValue.class); - var str = newStringFn.call(env, propName.get()); - args.setJObject(str); - var res = (JNI.JString) callStaticMethodFn.call(env, System, valueOf, args); - assertTrue("There should be a property 'say' defined", res.isNonNull()); - var len = strLengthFn.call(env, res); - assertEquals("'Ahoj' has four letters", 4, len); - var valueFalse = StackValue.get(JValue.class); - valueFalse.setBoolean(false); - var chars = strCharsFn.call(env, res, valueFalse); - assertEquals("Ahoj", CTypeConversion.toJavaString(chars)); - strReleaseFn.call(env, res, chars); - } - } - */ - @Test public void executeMainClass() throws Exception { var out = File.createTempFile("check-main", ".log"); From f76710dd417a3350ce45d253616104aa56e89790 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 11 Jun 2025 14:55:21 +0200 Subject: [PATCH 32/60] Fixing typo --- .../src/main/java/org/enso/common/PolyglotSymbolResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java index 74759597e2e1..56bc6af08365 100644 --- a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java +++ b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java @@ -39,10 +39,10 @@ public static Object loadClass(String name) throws ClassNotFoundException { } /** - * Subclasses implement this method to seach for provided name + * Subclasses implement this method to search for class with the provided name. * * @param name dot separated name to search for - * @return non-null object representing the name + * @return non-{@code null} object representing the name * @throws java.lang.ClassNotFoundException if no name was found */ protected abstract Object handleLoadClass(String name) throws ClassNotFoundException; From 673b8c0f304837432610a0ace8eb1f2d1422bae0 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Jun 2025 16:55:08 +0200 Subject: [PATCH 33/60] Add entries to classpath --- .../enso/common/PolyglotSymbolResolver.java | 9 +++++ .../enso/interpreter/runtime/EnsoContext.java | 28 +++++++-------- .../org/enso/jvm/interop/OtherJvmMessage.java | 11 +++++- .../jvm/interop/OtherJvmSymbolResolver.java | 31 ++++++++++++----- .../enso/jvm/interop/TruffleClassLoader.java | 34 ++++++++++++------- 5 files changed, 77 insertions(+), 36 deletions(-) diff --git a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java index 56bc6af08365..2c8bd8e2222b 100644 --- a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java +++ b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java @@ -1,5 +1,6 @@ package org.enso.common; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -38,6 +39,12 @@ public static Object loadClass(String name) throws ClassNotFoundException { throw ex; } + public static void addToClassPath(URL url) { + for (var p : ALL) { + p.handleAddToClassPath(url); + } + } + /** * Subclasses implement this method to search for class with the provided name. * @@ -46,4 +53,6 @@ public static Object loadClass(String name) throws ClassNotFoundException { * @throws java.lang.ClassNotFoundException if no name was found */ protected abstract Object handleLoadClass(String name) throws ClassNotFoundException; + + protected abstract void handleAddToClassPath(URL url); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 966554419366..88cf23163cd1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -39,7 +39,6 @@ import java.util.function.Consumer; import java.util.function.Supplier; import java.util.logging.Level; -import org.enso.common.HostEnsoUtils; import org.enso.common.LanguageInfo; import org.enso.common.PolyglotSymbolResolver; import org.enso.common.RuntimeOptions; @@ -504,6 +503,7 @@ public void addToClassPath(TruffleFile file) { try { var url = file.toUri().toURL(); hostClassLoader.add(url); + PolyglotSymbolResolver.addToClassPath(url); } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } @@ -621,7 +621,7 @@ static Object lookupJavaClass( public TruffleObject lookupJavaClass(String className) { var collectedExceptions = new ArrayList(); - { + if (true) { // set this to false to simulate classloading via Channel var hostSymbol = ClassLookup.lookupJavaClass( className, // name to search for @@ -632,19 +632,17 @@ public TruffleObject lookupJavaClass(String className) { return (TruffleObject) hostSymbol; } } - if (HostEnsoUtils.isAot()) { - var javaHome = System.getProperty("java.home"); - logger.info( - () -> String.format("Class %s not found, trying to turn on JVM %s", className, javaHome)); - var hostSymbol = - ClassLookup.lookupJavaClass( - className, // name to search for - PolyglotSymbolResolver::loadClass, // pluggable polyglot searches - collectedExceptions // collect exceptions - ); - if (hostSymbol instanceof TruffleObject) { - return (TruffleObject) hostSymbol; - } + var javaHome = System.getProperty("java.home"); + logger.info( + () -> String.format("Class %s not found, trying to turn on JVM %s", className, javaHome)); + var hostSymbol = + ClassLookup.lookupJavaClass( + className, // name to search for + PolyglotSymbolResolver::loadClass, // pluggable polyglot searches + collectedExceptions // collect exceptions + ); + if (hostSymbol instanceof TruffleObject) { + return (TruffleObject) hostSymbol; } var level = Level.WARNING; diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index 618631de30ff..6107a89f50a0 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -70,7 +70,7 @@ record LoadClass(String name) @Override public OtherJvmResult apply(Channel t) { try { - var clazzRaw = TruffleClassLoader.loadClass(name); + var clazzRaw = TruffleClassLoader.loadClassObject(name); return ReturnValue.create(clazzRaw); } catch (ClassNotFoundException ex) { return ThrowException.create(ex); @@ -78,6 +78,15 @@ public OtherJvmResult apply(Channel t) { } } + @Persistable(id = 81906) + record AddToClassPath(String url) implements Function, Void> { + @Override + public Void apply(Channel t) { + TruffleClassLoader.addToClassPath(url); + return null; + } + } + @Persistable(id = 1) static final class PersistTruffleObject extends Persistance { PersistTruffleObject() { diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java index a43514e7f1ea..e04a987c34bd 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; +import java.net.URL; import java.util.ArrayList; import org.enso.common.HostEnsoUtils; import org.enso.common.PolyglotSymbolResolver; @@ -14,17 +15,31 @@ public final class OtherJvmSymbolResolver extends PolyglotSymbolResolver { private Channel channel; + private Channel getChannel() throws URISyntaxException, IOException { + if (channel == null) { + channel = initializeChannel(); + } + return channel; + } + @Override protected Object handleLoadClass(String name) throws ClassNotFoundException { - if (channel == null) { - try { - channel = initializeChannel(); - } catch (IOException | URISyntaxException ex) { - throw new ClassNotFoundException("Cannot initialize JVM", ex); - } + try { + var ch = getChannel(); + var result = ch.execute(OtherJvmResult.class, new OtherJvmMessage.LoadClass(name)); + return OtherJvmObject.bindToChannel(result.value(), ch); + } catch (IOException | URISyntaxException ex) { + throw new ClassNotFoundException(name, ex); + } + } + + @Override + protected void handleAddToClassPath(URL url) { + try { + getChannel().execute(Void.class, new OtherJvmMessage.AddToClassPath(url.toString())); + } catch (URISyntaxException | IOException ex) { + ex.printStackTrace(); } - var result = channel.execute(OtherJvmResult.class, new OtherJvmMessage.LoadClass(name)); - return OtherJvmObject.bindToChannel(result.value(), channel); } private Channel initializeChannel() throws IOException, URISyntaxException { diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index dbae0278b323..139163fe8e67 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -4,16 +4,23 @@ import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.library.ExportLibrary; import com.oracle.truffle.api.library.ExportMessage; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.HostAccess; @ExportLibrary(value = InteropLibrary.class) -final class TruffleClassLoader implements TruffleObject { +final class TruffleClassLoader extends URLClassLoader implements TruffleObject { + private static final TruffleClassLoader DEFAULT = new TruffleClassLoader(); private static Context ctx; - private Object value; - private TruffleClassLoader() {} + private TruffleClassLoader() { + super(new URL[0]); + } static synchronized Context ctx() { if (ctx == null) { @@ -25,17 +32,20 @@ static synchronized Context ctx() { return ctx; } - static TruffleObject loadClass(String className) throws ClassNotFoundException { - var context = ctx(); + static void addToClassPath(String url) { + try { + DEFAULT.addURL(new URI(url).toURL()); + } catch (MalformedURLException | URISyntaxException ex) { + ex.printStackTrace(); + } + } - var clazz = Class.forName(className); - var clazzValue1 = context.asValue(clazz); + static TruffleObject loadClassObject(String className) throws ClassNotFoundException { + var clazz = DEFAULT.loadClass(className); + var clazzValue1 = ctx().asValue(clazz); var clazzValue2 = clazzValue1.getMember("static"); - var holderRaw = new TruffleClassLoader(); - var holderValue = context.asValue(holderRaw); - holderValue.execute(clazzValue2); - var clazzRaw = holderRaw.value; - return (TruffleObject) clazzRaw; + ctx().asValue(DEFAULT).execute(clazzValue2); + return (TruffleObject) DEFAULT.value; } @ExportMessage From dda69c84b8b51d4087ee3024db3393292d65772e Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Thu, 12 Jun 2025 17:09:29 +0200 Subject: [PATCH 34/60] org.enso.jvm.interop needs a reflective registration to construct its pool of messages --- .../native-image/org/enso/runner/reflect-config.json | 5 ----- .../native-image/org/enso/jvm/interop/reflect-config.json | 7 +++++++ 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 lib/java/jvm-interop/src/main/resources/META-INF/native-image/org/enso/jvm/interop/reflect-config.json diff --git a/engine/runner/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 index b856194edc12..d42920f37fdc 100644 --- a/engine/runner/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 @@ -61,11 +61,6 @@ "queryAllPublicMethods":true, "methods":[{"name":"","parameterTypes":[] }] }, -{ - "name":"org.enso.jvm.interop.Persistables", - "queryAllPublicMethods":true, - "methods":[{"name":"","parameterTypes":[] }] -}, { "name":"ch.qos.logback.classic.pattern.DateConverter", "methods":[{"name":"","parameterTypes":[] }] diff --git a/lib/java/jvm-interop/src/main/resources/META-INF/native-image/org/enso/jvm/interop/reflect-config.json b/lib/java/jvm-interop/src/main/resources/META-INF/native-image/org/enso/jvm/interop/reflect-config.json new file mode 100644 index 000000000000..3182340809dc --- /dev/null +++ b/lib/java/jvm-interop/src/main/resources/META-INF/native-image/org/enso/jvm/interop/reflect-config.json @@ -0,0 +1,7 @@ +[ +{ + "name":"org.enso.jvm.interop.OtherJvmPool", + "queryAllPublicMethods":true, + "methods":[{"name":"","parameterTypes":[] }] +} +] From 139914d0f9897716657266b6d60ec5d7582e6c54 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 07:32:59 +0200 Subject: [PATCH 35/60] Associate readResolve and writeReplace with Pool --- .../enso/interpreter/caches/CacheUtils.java | 10 ++- .../interpreter/caches/ImportExportCache.java | 8 +- .../enso/interpreter/caches/ModuleCache.java | 8 +- .../interpreter/caches/SuggestionsCache.java | 7 +- .../java/org/enso/jvm/channel/Channel.java | 8 +- .../java/org/enso/persist/Persistance.java | 43 +++++++--- .../org/enso/persist/PersistanceTest.java | 84 ++++++++++++------- 7 files changed, 108 insertions(+), 60 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java index 372c09948c02..4fb1ebec8db3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java @@ -13,6 +13,7 @@ import org.enso.compiler.context.CompilerContext; import org.enso.compiler.core.ir.ProcessingPass; import org.enso.editions.LibraryName; +import org.enso.persist.Persistance; import org.enso.pkg.SourceFile; import org.enso.polyglot.Suggestion; import org.enso.text.Hex; @@ -22,7 +23,12 @@ private CacheUtils() {} private static int BUFFER_SIZE = 1024; - static Function writeReplace(CompilerContext context, boolean keepUUIDs) { + static Persistance.Pool createPool(CompilerContext context, boolean keepUUIDs) { + return Persistance.Pool.withReplaceRewrite( + PersistUtils.POOL, readResolve(context), writeReplace(context, keepUUIDs)); + } + + private static Function writeReplace(CompilerContext context, boolean keepUUIDs) { return (obj) -> switch (obj) { case ProcessingPass.Metadata metadata -> metadata.prepareForSerialization(context); @@ -32,7 +38,7 @@ static Function writeReplace(CompilerContext context, boolean ke }; } - static Function readResolve(CompilerContext context) { + private static Function readResolve(CompilerContext context) { return (obj) -> switch (obj) { case ProcessingPass.Metadata metadata -> { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java index 32d1c2f0b35d..975760d88f91 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java @@ -61,9 +61,8 @@ public byte[] metadata(String sourceDigest, String blobDigest, CachedBindings en @Override public byte[] serialize(EnsoContext context, CachedBindings entry) throws IOException { - var arr = - PersistUtils.POOL.write( - entry.bindings(), CacheUtils.writeReplace(context.getCompiler().context(), false)); + var pool = CacheUtils.createPool(context.getCompiler().context(), true); + var arr = pool.write(entry.bindings()); return arr; } @@ -71,7 +70,8 @@ public byte[] serialize(EnsoContext context, CachedBindings entry) throws IOExce public CachedBindings deserialize( EnsoContext context, ByteBuffer data, Metadata meta, TruffleLogger logger) throws IOException { - var ref = PersistUtils.POOL.read(data, CacheUtils.readResolve(context.getCompiler().context())); + var pool = CacheUtils.createPool(context.getCompiler().context(), true); + var ref = pool.read(data); var bindings = ref.get(MapToBindings.class); return new CachedBindings(libraryName, bindings, Optional.empty()); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/ModuleCache.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/ModuleCache.java index e115e891b97a..5729598b7602 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/ModuleCache.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/ModuleCache.java @@ -57,9 +57,8 @@ public byte[] metadata(String sourceDigest, String blobDigest, CachedModule entr @Override public byte[] serialize(EnsoContext context, CachedModule entry) throws IOException { - var arr = - PersistUtils.POOL.write( - entry.moduleIR(), CacheUtils.writeReplace(context.getCompiler().context(), true)); + var pool = CacheUtils.createPool(context.getCompiler().context(), true); + var arr = pool.write(entry.moduleIR()); return arr; } @@ -67,7 +66,8 @@ public byte[] serialize(EnsoContext context, CachedModule entry) throws IOExcept public CachedModule deserialize( EnsoContext context, ByteBuffer data, Metadata meta, TruffleLogger logger) throws IOException { - var ref = PersistUtils.POOL.read(data, CacheUtils.readResolve(context.getCompiler().context())); + var pool = CacheUtils.createPool(context.getCompiler().context(), true); + var ref = pool.read(data); var mod = ref.get(Module.class); return new CachedModule( mod, CompilationStage.valueOf(meta.compilationStage()), module.getSource()); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/SuggestionsCache.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/SuggestionsCache.java index 6f63766ab7d5..a083db1d8b81 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/SuggestionsCache.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/SuggestionsCache.java @@ -70,15 +70,16 @@ public byte[] metadata(String sourceDigest, String blobDigest, CachedSuggestions @Override public byte[] serialize(EnsoContext context, CachedSuggestions entry) throws IOException { - return PersistUtils.POOL.write( - entry, CacheUtils.writeReplace(context.getCompiler().context(), true)); + var pool = CacheUtils.createPool(context.getCompiler().context(), true); + return pool.write(entry); } @Override public CachedSuggestions deserialize( EnsoContext context, ByteBuffer data, Metadata meta, TruffleLogger logger) throws IOException { - var ref = PersistUtils.POOL.read(data, CacheUtils.readResolve(context.getCompiler().context())); + var pool = CacheUtils.createPool(context.getCompiler().context(), true); + var ref = pool.read(data); var cachedSuggestions = ref.get(CachedSuggestions.class); return cachedSuggestions; } diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index b1c4e03f9b16..7edc9d966a02 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -272,11 +272,11 @@ private static long acceptRequestFromHotSpotJvm( } private static long handleWithChannel(Channel channel, ByteBuffer buf) throws IOException { - var ref = channel.pool.read(buf, null); + var ref = channel.pool.read(buf); var msg = ref.get(Function.class); @SuppressWarnings("unchecked") var res = msg.apply(channel); - var bytes = channel.pool.write(res, null); + var bytes = channel.pool.write(res); buf.put(0, bytes); return bytes.length; } @@ -345,7 +345,7 @@ private R executeImpl( // handles this.execute ) { var address = 0L; try { - var bytes = pool.write(msg, null); + var bytes = pool.write(msg); var size = Math.max(bytes.length, 4096); long len; ByteBuffer buffer; @@ -374,7 +374,7 @@ private R executeImpl( // handles this.execute assert len >= 0; buffer.position(0); buffer.limit((int) len); - var result = pool.read(buffer, null); + var result = pool.read(buffer); return result.get(replyType); } catch (IOException ex) { throw new IllegalStateException(ex); diff --git a/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java b/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java index 9b92b2b85881..ea6fe78dc487 100644 --- a/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java +++ b/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java @@ -177,6 +177,8 @@ final T readWith(Input in) { */ public abstract static class Pool { private final String name; + private final Function readResolve; + private final Function writeReplace; private final Persistance[] all; /** @@ -190,7 +192,17 @@ public abstract static class Pool { */ @SafeVarargs protected Pool(String displayName, Persistance... instances) { + this(displayName, null, null, instances); + } + + private Pool( + String displayName, + Function readResolve, + Function writeReplace, + Persistance... instances) { this.name = displayName; + this.readResolve = readResolve; + this.writeReplace = writeReplace; this.all = instances; } @@ -215,32 +227,43 @@ public static Pool merge(Pool... pools) { return new Pool(sb.toString(), all.toArray(new Persistance[0])) {}; } + /** + * Associates write replace and/or read resolve functions with the pool. + * + * @param pool pool to associate + * @param readResolve either {@code null} or function to call for each object being stored to + * provide a replacement + * @param writeReplace {@code null} or a function that allows to convert each object before + * storing it down + * @return new pool unchanged except being associated with resolve and replace functions instead + * of any previous functions associated + */ + public static Pool withReplaceRewrite( + Pool pool, Function readResolve, Function writeReplace) { + return new Pool(pool.name, readResolve, writeReplace, pool.all) {}; + } + /** * Read object written down by {@link #write} from an array.
* {@snippet file="org/enso/persist/PersistanceTest.java" region="read"}
* {@snippet file="org/enso/persist/PersistanceTest.java" region="read"} * * @param arr the stored bytes - * @param readResolve either {@code null} or function to call for each object being stored to - * provide a replacement * @return the read object * @throws java.io.IOException when an I/O problem happens */ - public Reference read(byte[] arr, Function readResolve) throws IOException { - return read(ByteBuffer.wrap(arr), readResolve); + public Reference read(byte[] arr) throws IOException { + return read(ByteBuffer.wrap(arr)); } /** * Read object written down by {@link #write} from a byte buffer. * * @param buf the stored bytes - * @param readResolve either {@code null} or function to call for each object being stored to - * provide a replacement * @return the read object * @throws java.io.IOException when an I/O problem happens */ - public Reference read(ByteBuffer buf, Function readResolve) - throws IOException { + public Reference read(ByteBuffer buf) throws IOException { var map = new PerMap(all); return PerInputImpl.readObject(map, buf, readResolve); } @@ -250,12 +273,10 @@ public Reference read(ByteBuffer buf, Function readResolve) * back to object. * * @param obj the object to persist - * @param writeReplace {@code null} or a function that allows to convert each object before - * storing it down * @return the array of bytes * @throws IOException when an I/O problem happens */ - public byte[] write(Object obj, Function writeReplace) throws IOException { + public byte[] write(Object obj) throws IOException { var map = new PerMap(all); return PerGenerator.writeObject(map, obj, writeReplace); } diff --git a/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java b/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java index 6855760adfb3..5ef369a0989b 100644 --- a/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java +++ b/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.util.UUID; -import java.util.function.Function; import java.util.function.Supplier; import org.junit.Test; @@ -16,13 +15,13 @@ public class PersistanceTest { public void testUUIDPersistance() throws Exception { // @start region="write" var obj = UUID.randomUUID(); - var buffer = Persistables.POOL.write(obj, null); + var buffer = Persistables.POOL.write(obj); assertNotNull("Byte array is returned", buffer); assertNotEquals("It has non-zero length", 0, buffer.length); // @end region="write" // @start region="read" - var ref = Persistables.POOL.read(buffer, null); + var ref = Persistables.POOL.read(buffer); var loaded = ref.get(UUID.class); assertEquals("The same object was recreated", obj, loaded); // @end region="read" @@ -30,87 +29,108 @@ public void testUUIDPersistance() throws Exception { @Test public void readResolve() throws Exception { + var poolWith = + Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj, + null); + var in = new Service(5); - var arr = Persistables.POOL.write(in, (Function) null); + var arr = poolWith.write(in); - var plain = Persistables.POOL.read(arr, (Function) null); + var plain = Persistables.POOL.read(arr); assertEquals("Remains five", 5, plain.get(Service.class).value()); - var multiOnRead = - Persistables.POOL.read( - arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var multiOnRead = poolWith.read(arr); assertEquals("Multiplied on read", 15, multiOnRead.get(Service.class).value()); } @Test public void writeReplace() throws Exception { + var poolWith = + Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + null, + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new Service(5); - var arr = - Persistables.POOL.write( - in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var arr = poolWith.write(in); - var plain = Persistables.POOL.read(arr, (Function) null); + var plain = poolWith.read(arr); assertEquals("Multiplied on write", 15, plain.get(Service.class).value()); } @Test public void readResolveInline() throws Exception { + var poolWith = + Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj, + null); + var in = new ServiceSupply(new Service(5)); - var arr = Persistables.POOL.write(in, (Function) null); + var arr = poolWith.write(in); - var plain = Persistables.POOL.read(arr, (Function) null); + var plain = Persistables.POOL.read(arr); assertEquals("Remains five", 5, plain.get(ServiceSupply.class).supply().value()); - var multiOnRead = - Persistables.POOL.read( - arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var multiOnRead = poolWith.read(arr); assertEquals("Multiplied on read", 15, multiOnRead.get(ServiceSupply.class).supply().value()); } @Test public void writeReplaceInline() throws Exception { + var poolWith = + Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + null, + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new ServiceSupply(new Service(5)); - var arr = - Persistables.POOL.write( - in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var arr = poolWith.write(in); - var plain = Persistables.POOL.read(arr, (Function) null); + var plain = Persistables.POOL.read(arr); assertEquals("Multiplied on write", 15, plain.get(ServiceSupply.class).supply().value()); } @Test public void readResolveReference() throws Exception { + var poolWith = + Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj, + null); + var in = new IntegerSupply(new Service(5)); - var arr = Persistables.POOL.write(in, (Function) null); + var arr = poolWith.write(in); - var plain = Persistables.POOL.read(arr, (Function) null); + var plain = Persistables.POOL.read(arr); assertEquals("Remains five", 5, (int) plain.get(IntegerSupply.class).supply().get()); assertEquals("Remains five 2", 5, (int) plain.get(IntegerSupply.class).supply().get()); - var multiOnRead = - Persistables.POOL.read( - arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var multiOnRead = poolWith.read(arr); assertEquals( "Multiplied on read", 15, (int) multiOnRead.get(IntegerSupply.class).supply().get()); } @Test public void writeReplaceReference() throws Exception { + var poolWith = + Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + null, + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new IntegerSupply(new Service(5)); - var arr = - Persistables.POOL.write( - in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var arr = poolWith.write(in); - var plain = Persistables.POOL.read(arr, (Function) null); + var plain = poolWith.read(arr); assertEquals("Multiplied on write", 15, (int) plain.get(IntegerSupply.class).supply().get()); } static T serde(Class clazz, T l, int expectedSize) throws IOException { - var arr = Persistables.POOL.write(l, (Function) null); + var arr = Persistables.POOL.write(l); if (expectedSize >= 0) { assertEquals(expectedSize, arr.length - 12); } - var ref = Persistables.POOL.read(arr, (Function) null); + var ref = Persistables.POOL.read(arr); return ref.get(clazz); } From 0af66892c64814fcfdf9c77671910e1bc4aaea36 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 08:23:20 +0200 Subject: [PATCH 36/60] DistributionManager is already associated with an environment --- app/gui/.dev-env | 2 +- .../scala/org/enso/distribution/locking/ConcurrencyTest.scala | 2 +- .../runtimeversionmanager/test/RuntimeVersionManagerTest.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/gui/.dev-env b/app/gui/.dev-env index e2df0edbeb62..29f136759558 160000 --- a/app/gui/.dev-env +++ b/app/gui/.dev-env @@ -1 +1 @@ -Subproject commit e2df0edbeb62884a9ecbdaf4f574e94044804b4d +Subproject commit 29f136759558bf3893e4a139e09192de1d00f181 diff --git a/lib/scala/runtime-version-manager/src/test/scala/org/enso/distribution/locking/ConcurrencyTest.scala b/lib/scala/runtime-version-manager/src/test/scala/org/enso/distribution/locking/ConcurrencyTest.scala index 895515302fb3..e41f85170ad0 100644 --- a/lib/scala/runtime-version-manager/src/test/scala/org/enso/distribution/locking/ConcurrencyTest.scala +++ b/lib/scala/runtime-version-manager/src/test/scala/org/enso/distribution/locking/ConcurrencyTest.scala @@ -140,7 +140,7 @@ class ConcurrencyTest } } - val graalVersionManager = new GraalVersionManager(distributionManager, env) + val graalVersionManager = new GraalVersionManager(distributionManager) val temporaryDirectoryManager = TemporaryDirectoryManager(distributionManager, resourceManager) val componentsManager = new RuntimeVersionManager( diff --git a/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala index 0fb469c2bdc1..036075b23982 100644 --- a/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala +++ b/lib/scala/runtime-version-manager/src/test/scala/org/enso/runtimeversionmanager/test/RuntimeVersionManagerTest.scala @@ -51,7 +51,7 @@ class RuntimeVersionManagerTest ): (DistributionManager, RuntimeVersionManager, Environment) = { val env = fakeInstalledEnvironment(environmentOverrides) val distributionManager = new PortableDistributionManager(env) - val graalVersionManager = new GraalVersionManager(distributionManager, env) + val graalVersionManager = new GraalVersionManager(distributionManager) val resourceManager = TestLocalResourceManager.create() val temporaryDirectoryManager = From 7dabb5264c4156dbd0a8ca3c0582d69a437ed53c Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 09:04:55 +0200 Subject: [PATCH 37/60] Putting the configuration of a Channel into a dedicated Config --- .../java/org/enso/jvm/channel/Channel.java | 44 ++++++++++++------- .../jvm/channel/ChannelInSingleJvmTest.java | 13 +++--- .../org/enso/jvm/interop/OtherJvmMessage.java | 2 +- .../org/enso/jvm/interop/OtherJvmPool.java | 6 +-- .../org/enso/os/environment/jni/JVMPeer.java | 6 +-- 5 files changed, 41 insertions(+), 30 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 7edc9d966a02..6b0778dca690 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -11,7 +11,6 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; -import java.util.function.Supplier; import org.enso.persist.Persistance; import org.graalvm.nativeimage.CurrentIsolate; import org.graalvm.nativeimage.ImageInfo; @@ -33,7 +32,7 @@ * * @param internal data of the channel */ -public final class Channel implements AutoCloseable { +public final class Channel implements AutoCloseable { /** * @GuardedBy("Channel.class") */ @@ -132,18 +131,18 @@ private Channel( * @param type of internal data as well as provider of the pool * @param jvm instance of HotSpot JVM to connect to (can be {@code null} to create a mock channel * inside of a single JVM) - * @param dataAndPoolClass the class which has public default constructor and can supply an - * instance of persistance pool to use for communication + * @param configClass the class which has public default constructor and can supply an instance of + * persistance pool to use for communication * @return channel for sending messages to the HotSpot JVM */ - public static synchronized > Channel create( - JVM jvm, Class dataAndPoolClass) { - var data = newInstance(dataAndPoolClass); - Persistance.Pool pool = data.get(); + public static synchronized Channel create( + JVM jvm, Class configClass) { + var config = newInstance(configClass); + Persistance.Pool pool = config.pool(); var id = idCounter++; if (jvm == null) { - var otherData = newInstance(dataAndPoolClass); - return new Channel<>(data, null, otherData, id, pool); + var otherData = newInstance(configClass); + return new Channel<>(config, null, otherData, id, pool); } if (!ImageInfo.inImageCode()) { @@ -152,7 +151,7 @@ public static synchronized > Channel cre var e = jvm.env(); var classNameWithSlashes = Channel.class.getName().replace('.', '/'); try (var classInC = CTypeConversion.toCString(classNameWithSlashes); - var poolClassInC = CTypeConversion.toCString(dataAndPoolClass.getName()); + var poolClassInC = CTypeConversion.toCString(configClass.getName()); var createInC = CTypeConversion.toCString("createJvmPeerChannel"); var createSigInC = CTypeConversion.toCString("(JJJLjava/lang/String;)Z"); // var handleInC = CTypeConversion.toCString("handleJvmMessage"); @@ -168,7 +167,7 @@ public static synchronized > Channel cre var handleMethod = fn.getGetStaticMethodID().call(e, channelClass, handleInC.get(), handleSigInC.get()); - var channel = new Channel<>(id, data, pool, e, channelClass, handleMethod); + var channel = new Channel<>(id, config, pool, e, channelClass, handleMethod); var arg = StackValue.get(4, JNI.JValue.class); arg.addressOf(0).setLong(id); @@ -192,7 +191,7 @@ public static synchronized > Channel cre * @return data associated with this channel * @see #execute */ - public final Data getData() { + public final Data getConfig() { return data; } @@ -234,9 +233,9 @@ private static T newInstance(Class poolClass) { @SuppressWarnings("unchecked") private static boolean createJvmPeerChannel( long id, long threadId, long callbackFn, String poolClassName) throws Throwable { - var dataAndPoolClass = Class.forName(poolClassName); - var data = (Supplier) newInstance(dataAndPoolClass); - var pool = data.get(); + var configClass = Class.forName(poolClassName); + var data = (Config) newInstance(configClass); + var pool = data.pool(); var channel = new Channel<>(id, data, pool, threadId, callbackFn); var prev = ID_TO_CHANNEL.put(id, channel); return prev == null; @@ -416,4 +415,17 @@ private boolean printStackTrace(Throwable ex, boolean userCode) { } return false; } + + /** + * Set of methods necessary for construction of a {@link Channel}. Subclasses must have a public + * default constructor accessible via reflection from the {@link Channel#create} method. + */ + public abstract static class Config { + /** + * Creates instance of pool for persisting messages. + * + * @return the pool to use when sending messages + */ + public abstract Persistance.Pool pool(); + } } diff --git a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java index f20bce18f282..52780780b425 100644 --- a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java +++ b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java @@ -4,13 +4,12 @@ import static org.junit.Assert.assertNotNull; import java.util.function.Function; -import java.util.function.Supplier; import org.enso.persist.Persistable; import org.enso.persist.Persistance; import org.junit.Test; public class ChannelInSingleJvmTest { - public static final class PrivateData implements Supplier { + public static final class PrivateData extends Channel.Config { static int countInstances; int counter; @@ -19,7 +18,7 @@ public PrivateData() { } @Override - public Persistance.Pool get() { + public Persistance.Pool pool() { return Persistables.POOL; } } @@ -42,12 +41,12 @@ public void exchangeMessageThatModifiesPrivateData() { PrivateData.countInstances = 0; var ch = Channel.create(null, PrivateData.class); assertEquals("Two channels & data created", 2, PrivateData.countInstances); - assertEquals("By default we are at zero", 0, ch.getData().counter); + assertEquals("By default we are at zero", 0, ch.getConfig().counter); var msg = new AssignPrivateData(10); var newMsg = ch.execute(AssignPrivateData.class, msg); - assertEquals("PrivateData.counter hasn't been changed", 0, ch.getData().counter); + assertEquals("PrivateData.counter hasn't been changed", 0, ch.getConfig().counter); assertNotNull("Got a value", newMsg); assertEquals("10 + 1", 11, newMsg.valueToSet()); @@ -78,8 +77,8 @@ static record AssignPrivateData(int valueToSet) implements Function, AssignPrivateData> { @Override public AssignPrivateData apply(Channel t) { - t.getData().counter = valueToSet; - return new AssignPrivateData(t.getData().counter + 1); + t.getConfig().counter = valueToSet; + return new AssignPrivateData(t.getConfig().counter + 1); } } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index 6107a89f50a0..c4a5d01ab3bf 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -53,7 +53,7 @@ public V value() throws E { public OtherJvmResult apply(Channel t) { try { TruffleClassLoader.ctx().enter(); - var receiver = t.getData().findObject(id); + var receiver = t.getConfig().findObject(id); assert receiver instanceof TruffleObject; var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); return new ReturnValue<>(res); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java index 4c9551c649f4..18151fe89bf9 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -3,11 +3,11 @@ import com.oracle.truffle.api.interop.TruffleObject; import java.util.HashMap; import java.util.Map; -import java.util.function.Supplier; +import org.enso.jvm.channel.Channel; import org.enso.persist.Persistance; /** Pool of Truffle objects associated with {@link Channel}. */ -public final class OtherJvmPool implements Supplier { +public final class OtherJvmPool extends Channel.Config { private static final Map OBJECTS = new HashMap<>(); static synchronized long registerObject(TruffleObject obj) { @@ -21,7 +21,7 @@ static synchronized TruffleObject findObject(long id) { } @Override - public final Persistance.Pool get() { + public final Persistance.Pool pool() { return Persistables.POOL; } } diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java index 288faa107bf7..62aced565451 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java +++ b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java @@ -4,16 +4,16 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; +import org.enso.jvm.channel.Channel; import org.enso.persist.Persistable; import org.enso.persist.Persistance; -public final class JVMPeer implements Supplier { +public final class JVMPeer extends Channel.Config { public JVMPeer() {} @Override - public Persistance.Pool get() { + public Persistance.Pool pool() { return Persistables.POOL; } From d8d74931e912b8ea82975cad26d65ab6eb4b96bd Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 09:27:35 +0200 Subject: [PATCH 38/60] Reverting back to develop version --- app/gui/.dev-env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/gui/.dev-env b/app/gui/.dev-env index 29f136759558..e2df0edbeb62 160000 --- a/app/gui/.dev-env +++ b/app/gui/.dev-env @@ -1 +1 @@ -Subproject commit 29f136759558bf3893e4a139e09192de1d00f181 +Subproject commit e2df0edbeb62884a9ecbdaf4f574e94044804b4d From 82c431ecd43bae8862f8b0ca60794651885d86f8 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 09:48:51 +0200 Subject: [PATCH 39/60] Using createPool(Channel) to avoid global state in OtherJvmPool --- .../java/org/enso/jvm/channel/Channel.java | 31 ++++++-------- .../jvm/channel/ChannelInSingleJvmTest.java | 2 +- .../org/enso/jvm/interop/OtherJvmMessage.java | 13 ++---- .../org/enso/jvm/interop/OtherJvmPool.java | 42 +++++++++++++++++-- .../enso/jvm/interop/OtherJvmObjectTest.java | 6 +-- .../org/enso/os/environment/jni/JVMPeer.java | 2 +- 6 files changed, 58 insertions(+), 38 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 6b0778dca690..079291fd5427 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -59,31 +59,25 @@ public final class Channel implements AutoCloseable /** The SubstrateVM side of a channel. */ private Channel( - long id, - Data data, - Persistance.Pool pool, - JNI.JNIEnv env, - JNI.JClass handleClass, - JNI.JMethodID handleFn) { + long id, Data data, JNI.JNIEnv env, JNI.JClass handleClass, JNI.JMethodID handleFn) { this.id = id; this.data = data; - this.pool = pool; this.env = env; this.isolate = -1; this.callbackFn = null; this.channelClass = handleClass; this.channelHandle = handleFn; this.otherMockChannel = null; + this.pool = data.createPool(this); } /** The HotSpot JVM side of a channel. */ - private Channel(long id, Data data, Persistance.Pool pool, long isolate, long callbackFn) { + private Channel(long id, Data data, long isolate, long callbackFn) { if (ImageInfo.inImageCode()) { throw new IllegalStateException("Only usable in HotSpot"); } this.id = id; this.data = data; - this.pool = pool; this.isolate = isolate; this.env = null; this.channelClass = null; @@ -99,20 +93,19 @@ private Channel(long id, Data data, Persistance.Pool pool, long isolate, long ca ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); this.callbackFn = Linker.nativeLinker().downcallHandle(fnCallbackAddress, fnDescriptor); + this.pool = data.createPool(this); } /** * Mock constructor. Creates a channel that simulates sending of the messages inside of the same * JVM. Useful for testing. */ - private Channel( - Data myData, Channel otherOrNull, Data otherData, long id, Persistance.Pool pool) { + private Channel(Data myData, Channel otherOrNull, Data otherData, long id) { if (ImageInfo.inImageCode()) { throw new IllegalStateException("Only usable in HotSpot"); } this.id = id; this.data = myData; - this.pool = pool; this.isolate = -2; this.callbackFn = null; this.env = null; @@ -122,7 +115,8 @@ private Channel( otherOrNull != null ? otherOrNull // use other channel when provided : // otherwise allocate new and pass this reference to it - new Channel<>(otherData, this, null, id, pool); + new Channel<>(otherData, this, null, id); + this.pool = data.createPool(this); } /** @@ -138,11 +132,10 @@ private Channel( public static synchronized Channel create( JVM jvm, Class configClass) { var config = newInstance(configClass); - Persistance.Pool pool = config.pool(); var id = idCounter++; if (jvm == null) { var otherData = newInstance(configClass); - return new Channel<>(config, null, otherData, id, pool); + return new Channel<>(config, null, otherData, id); } if (!ImageInfo.inImageCode()) { @@ -167,7 +160,7 @@ public static synchronized Channel create( var handleMethod = fn.getGetStaticMethodID().call(e, channelClass, handleInC.get(), handleSigInC.get()); - var channel = new Channel<>(id, config, pool, e, channelClass, handleMethod); + var channel = new Channel<>(id, config, e, channelClass, handleMethod); var arg = StackValue.get(4, JNI.JValue.class); arg.addressOf(0).setLong(id); @@ -235,8 +228,7 @@ private static boolean createJvmPeerChannel( long id, long threadId, long callbackFn, String poolClassName) throws Throwable { var configClass = Class.forName(poolClassName); var data = (Config) newInstance(configClass); - var pool = data.pool(); - var channel = new Channel<>(id, data, pool, threadId, callbackFn); + var channel = new Channel<>(id, data, threadId, callbackFn); var prev = ID_TO_CHANNEL.put(id, channel); return prev == null; } @@ -424,8 +416,9 @@ public abstract static class Config { /** * Creates instance of pool for persisting messages. * + * @param channel the channel associated with this {@code Config} * @return the pool to use when sending messages */ - public abstract Persistance.Pool pool(); + public abstract Persistance.Pool createPool(Channel channel); } } diff --git a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java index 52780780b425..23ba70caacc8 100644 --- a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java +++ b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java @@ -18,7 +18,7 @@ public PrivateData() { } @Override - public Persistance.Pool pool() { + public Persistance.Pool createPool(Channel ignore) { return Persistables.POOL; } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index c4a5d01ab3bf..aba3afc9a71a 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -98,21 +98,14 @@ protected void writeObject(TruffleObject obj, Output out) throws IOException { if (obj instanceof OtherJvmObject other) { out.writeLong(other.id()); } else { - var id = OtherJvmPool.registerObject(obj); - out.writeLong(-id); + throw new IOException("No other subclasses of TruffleObject should get here: " + obj); } } @Override protected TruffleObject readObject(Input in) throws IOException, ClassNotFoundException { - var id = in.readLong(); - if (id < 0) { - return new OtherJvmObject(null, -id); - } else { - var cached = OtherJvmPool.findObject(id); - assert cached != null; - return cached; - } + // OtherJvmObject instance ready to be "read resolved" + return new OtherJvmObject(null, in.readLong()); } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java index 18151fe89bf9..bd6e09cf5144 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -10,18 +10,52 @@ public final class OtherJvmPool extends Channel.Config { private static final Map OBJECTS = new HashMap<>(); - static synchronized long registerObject(TruffleObject obj) { + synchronized long registerObject(TruffleObject obj) { var size = OBJECTS.size() + 1; OBJECTS.put((long) size, obj); return size; } - static synchronized TruffleObject findObject(long id) { + synchronized TruffleObject findObject(long id) { return OBJECTS.get(id); } @Override - public final Persistance.Pool pool() { - return Persistables.POOL; + @SuppressWarnings("unchecked") + public final Persistance.Pool createPool(Channel channel) { + return Persistance.Pool.withReplaceRewrite( + Persistables.POOL, + (obj) -> + switch (obj) { + case OtherJvmObject other -> { + if (other.id() < 0) { + // the other object with negative number came back + // it is our own object + var ourOwn = findObject(-other.id()); + assert ourOwn != null; + yield ourOwn; + } else { + var proxy = OtherJvmObject.bindToChannel(other, (Channel) channel); + yield proxy; + } + } + default -> obj; + }, + (obj) -> + switch (obj) { + case OtherJvmObject other -> { + assert other.id() < 0 + : "Returning back an OtherJvmObject. It is from the other JVM - e.g. it has" + + " negative number"; + yield other; + } + case TruffleObject foreign -> { + var id = registerObject(foreign); + // our own objects send to the other side should + // have negative ID + yield new OtherJvmObject(null, -id); + } + default -> obj; + }); } } diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index c1ce49b230dd..7ff8dc49c597 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -26,7 +26,7 @@ public void wrapBigDecimal() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherJvmPool.registerObject((TruffleObject) bigUnwrap); + var id = CHANNEL.getConfig().registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var otherValue = ctx.asValue(other); @@ -53,7 +53,7 @@ public void wrapArray() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherJvmPool.registerObject((TruffleObject) bigUnwrap); + var id = CHANNEL.getConfig().registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var otherValue = ctx.asValue(other); @@ -101,7 +101,7 @@ public void messageFromAnUnsupportedLibrary() { var bigUnwrap = ctx.unwrapValue(bigValue); assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - var id = OtherJvmPool.registerObject((TruffleObject) bigUnwrap); + var id = CHANNEL.getConfig().registerObject((TruffleObject) bigUnwrap); var other = new OtherJvmObject(CHANNEL, id); var noType = TypesLibrary.getUncached().hasType(other); diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java index 62aced565451..e62489e70bbb 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java +++ b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/JVMPeer.java @@ -13,7 +13,7 @@ public final class JVMPeer extends Channel.Config { public JVMPeer() {} @Override - public Persistance.Pool pool() { + public Persistance.Pool createPool(Channel ignore) { return Persistables.POOL; } From b5c973fe8b708fbc156113d1f7a13c077607ddaf Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 12:35:55 +0200 Subject: [PATCH 40/60] Map of objects shall not be global --- .../org/enso/jvm/interop/OtherJvmPool.java | 28 +++--- .../enso/jvm/interop/OtherJvmObjectTest.java | 90 +++++++++++-------- 2 files changed, 70 insertions(+), 48 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java index bd6e09cf5144..fc67e976f43a 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -8,16 +8,16 @@ /** Pool of Truffle objects associated with {@link Channel}. */ public final class OtherJvmPool extends Channel.Config { - private static final Map OBJECTS = new HashMap<>(); + private final Map objectsById = new HashMap<>(); - synchronized long registerObject(TruffleObject obj) { - var size = OBJECTS.size() + 1; - OBJECTS.put((long) size, obj); + private synchronized long registerObject(TruffleObject obj) { + var size = objectsById.size() + 1; + objectsById.put((long) size, obj); return size; } - synchronized TruffleObject findObject(long id) { - return OBJECTS.get(id); + final synchronized TruffleObject findObject(long id) { + return objectsById.get(id); } @Override @@ -35,6 +35,9 @@ public final Persistance.Pool createPool(Channel channel) { assert ourOwn != null; yield ourOwn; } else { + // real truffle object in the other JVM + // need to keep it as OtherJvmObject proxy + // just associate channel to it var proxy = OtherJvmObject.bindToChannel(other, (Channel) channel); yield proxy; } @@ -44,16 +47,15 @@ public final Persistance.Pool createPool(Channel channel) { (obj) -> switch (obj) { case OtherJvmObject other -> { - assert other.id() < 0 - : "Returning back an OtherJvmObject. It is from the other JVM - e.g. it has" - + " negative number"; - yield other; + // returning back their own OtherJvmObject - let + // them know it is theirs by using negative ID + yield new OtherJvmObject(null, -other.id()); } case TruffleObject foreign -> { var id = registerObject(foreign); - // our own objects send to the other side should - // have negative ID - yield new OtherJvmObject(null, -id); + // our own truffle objects send to the other side should + // have a positive ID + yield new OtherJvmObject(null, id); } default -> obj; }); diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 7ff8dc49c597..b8edab7b0275 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -6,11 +6,11 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import com.oracle.truffle.api.interop.TruffleObject; import java.math.BigDecimal; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.jvm.channel.Channel; import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Value; import org.hamcrest.core.StringContains; import org.junit.ClassRule; import org.junit.Test; @@ -20,15 +20,12 @@ public class OtherJvmObjectTest { private static final Channel CHANNEL = Channel.create(null, OtherJvmPool.class); @Test - public void wrapBigDecimal() { - var bigReal = new BigDecimal("432.322"); - var bigValue = ctx.asValue(bigReal); - var bigUnwrap = ctx.unwrapValue(bigValue); - assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - - var id = CHANNEL.getConfig().registerObject((TruffleObject) bigUnwrap); - var other = new OtherJvmObject(CHANNEL, id); - var otherValue = ctx.asValue(other); + public void wrapBigDecimal() throws Exception { + var testClassValue = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + assertOtherJvmObject("Represents clazz from the other JVM", testClassValue); + var bigReal = newBigDecimal("432.322"); + var otherValue = testClassValue.invokeMember("newBigDecimal", "432.322"); + assertOtherJvmObject("Represents object from the other JVM", otherValue); assertFalse("Decimal isn't array", otherValue.hasArrayElements()); assertEquals(bigReal.toPlainString(), otherValue.invokeMember("toPlainString").asString()); @@ -36,28 +33,31 @@ public void wrapBigDecimal() { var twiceReal = bigReal.add(bigReal); var twiceValue = otherValue.invokeMember("add", otherValue); assertEquals(twiceReal.toBigInteger(), twiceValue.invokeMember("toBigInteger").asBigInteger()); - assertTrue("It is OtherJvmObject", ctx.unwrapValue(twiceValue) instanceof OtherJvmObject); + assertOtherJvmObject("Also other JVM object", twiceValue); var minusValue = twiceValue.invokeMember("subtract", otherValue); assertEquals(bigReal.toString(), minusValue.invokeMember("toString").asString()); assertTrue("OtherJvmObject for minus", ctx.unwrapValue(minusValue) instanceof OtherJvmObject); } - @Test - public void wrapArray() { + public static BigDecimal newBigDecimal(String txt) { + return new BigDecimal(txt); + } + + public static Object[] otherJvmArrayWithPrimitives() { var bigReal = new Object[] { "Ahoj", 't', (byte) 1, (short) 2, (int) 3, (long) 4, (float) 5, (double) 6, true }; - var bigValue = ctx.asValue(bigReal); - var bigUnwrap = ctx.unwrapValue(bigValue); - assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); + return bigReal; + } - var id = CHANNEL.getConfig().registerObject((TruffleObject) bigUnwrap); - var other = new OtherJvmObject(CHANNEL, id); - var otherValue = ctx.asValue(other); + @Test + public void wrapArray() throws Exception { + var testClassValue = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + var otherValue = testClassValue.invokeMember("otherJvmArrayWithPrimitives"); - assertTrue("Aray is array", otherValue.hasArrayElements()); + assertTrue("Array is array", otherValue.hasArrayElements()); assertEquals("Few elements", 9, otherValue.getArraySize()); assertEquals("Ahoj", otherValue.getArrayElement(0).asString()); assertEquals("t", otherValue.getArrayElement(1).asString()); @@ -72,17 +72,22 @@ public void wrapArray() { @Test public void loadClassViaMessage() throws Exception { - var msg = new OtherJvmMessage.LoadClass("java.lang.Short"); - var shortRaw = CHANNEL.execute(OtherJvmResult.class, msg).value(); - if (shortRaw instanceof OtherJvmObject other) { - shortRaw = new OtherJvmObject(CHANNEL, other.id()); - } - var shortValue = ctx.asValue(shortRaw); - + var shortValue = loadOtherJvmClass("java.lang.Short"); var value = shortValue.invokeMember("valueOf", "32531"); assertEquals(32531, value.asInt()); } + @Test + public void loadTestClassViaMessage() throws Exception { + var testClassValue = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + var parsedValue = testClassValue.invokeMember("otherJvmValueOf", "32531"); + assertEquals(32531, parsedValue.asInt()); + } + + public static short otherJvmValueOf(String txt) { + return Short.parseShort(txt); + } + @Test public void classNotFoundError() { var msg = new OtherJvmMessage.LoadClass("java.lang.unknown.Clazz"); @@ -95,16 +100,31 @@ public void classNotFoundError() { } @Test - public void messageFromAnUnsupportedLibrary() { - var bigReal = new BigDecimal("-1.1"); - var bigValue = ctx.asValue(bigReal); - var bigUnwrap = ctx.unwrapValue(bigValue); - assertTrue("The value is represented as truffle object", bigUnwrap instanceof TruffleObject); - - var id = CHANNEL.getConfig().registerObject((TruffleObject) bigUnwrap); - var other = new OtherJvmObject(CHANNEL, id); + public void messageFromAnUnsupportedLibrary() throws Exception { + var testClassValue = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + var bigReal = testClassValue.invokeMember("newBigDecimal", "432.322"); + var other = ctx.unwrapValue(bigReal); + assertEquals("The right class", OtherJvmObject.class, other.getClass()); var noType = TypesLibrary.getUncached().hasType(other); assertFalse("Other JVM objects don't have type", noType); } + + private static Value loadOtherJvmClass(String name) throws Exception { + var msg = new OtherJvmMessage.LoadClass(name); + var shortRaw = CHANNEL.execute(OtherJvmResult.class, msg).value(); + if (shortRaw instanceof OtherJvmObject other) { + shortRaw = new OtherJvmObject(CHANNEL, other.id()); + } + var shortValue = ctx.asValue(shortRaw); + return shortValue; + } + + private static void assertOtherJvmObject(String msg, Value value) { + var unwrap = ctx.unwrapValue(value); + if (unwrap instanceof OtherJvmObject) { + return; + } + fail(msg + " but got: " + unwrap); + } } From 73a8916daa9557782ae8ff4fa9d00f4b82041c00 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 13 Jun 2025 14:52:59 +0200 Subject: [PATCH 41/60] withWriteReplace and withReadResolve instance methods form a nicer API --- .../distribution/DefaultManagers.scala | 2 +- .../test/pass/PassPersistanceTest.java | 4 +- .../test/semantic/ImportExportTest.scala | 58 +++++++------- .../enso/compiler/core/IrPersistanceTest.java | 43 ++++++----- .../enso/interpreter/caches/CacheUtils.java | 5 +- .../test/TypeMetadataPersistanceTest.java | 4 +- .../org/enso/jvm/interop/OtherJvmPool.java | 76 ++++++++++--------- .../java/org/enso/persist/Persistance.java | 33 +++++--- .../org/enso/persist/PersistanceTest.java | 30 +++----- .../TestDistributionConfiguration.scala | 2 +- 10 files changed, 138 insertions(+), 119 deletions(-) diff --git a/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala b/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala index 8105e3a66d69..a0c161617266 100644 --- a/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala +++ b/engine/launcher/src/main/scala/org/enso/launcher/distribution/DefaultManagers.scala @@ -51,7 +51,7 @@ object DefaultManagers { alwaysInstallMissing ), distributionManager, - new GraalVersionManager(distributionManager, LauncherEnvironment), + new GraalVersionManager(distributionManager), temporaryDirectoryManager, defaultResourceManager, EngineRepository.defaultEngineReleaseProvider, diff --git a/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/PassPersistanceTest.java b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/PassPersistanceTest.java index be68d097add4..7068f1464895 100644 --- a/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/PassPersistanceTest.java +++ b/engine/runtime-compiler/src/test/java/org/enso/compiler/test/pass/PassPersistanceTest.java @@ -79,11 +79,11 @@ private static T serde(Class clazz, T l, int expectedSize) throws IOExcep private static T serde(Class clazz, T l, int expectedSize, Function fn) throws IOException { - var arr = POOL.write(l, fn); + var arr = POOL.withWriteReplace(fn).write(l); if (expectedSize >= 0) { assertEquals(expectedSize, arr.length - 12); } - var ref = POOL.read(arr, null); + var ref = POOL.read(arr); return ref.get(clazz); } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala index 342fbbbb1956..da4141ea23d1 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala @@ -1038,20 +1038,21 @@ class ImportExportTest .toList .collect({ case w: Warning.DuplicatedImport => w }) warn.size shouldEqual 1 - val arr = org.enso.interpreter.caches.PersistUtils.POOL.write( - mainIr, - { - case metadata: ProcessingPass.Metadata => - metadata.prepareForSerialization( - ctx - .ensoContext() - .getCompiler - .context - .asInstanceOf[metadata.Compiler] - ); - case obj => obj - } - ); + val arr = org.enso.interpreter.caches.PersistUtils.POOL + .withWriteReplace( + { + case metadata: ProcessingPass.Metadata => + metadata.prepareForSerialization( + ctx + .ensoContext() + .getCompiler + .context + .asInstanceOf[metadata.Compiler] + ); + case obj => obj + } + ) + .write(mainIr); arr should not be empty } @@ -1079,20 +1080,21 @@ class ImportExportTest .asInstanceOf[errors.ImportExport.AmbiguousImport] ambiguousImport.symbolName shouldEqual "A_Type" try { - val arr = org.enso.interpreter.caches.PersistUtils.POOL.write( - mainIr, - { - case metadata: ProcessingPass.Metadata => - metadata.prepareForSerialization( - ctx - .ensoContext() - .getCompiler - .context - .asInstanceOf[metadata.Compiler] - ); - case obj => obj - } - ); + val arr = org.enso.interpreter.caches.PersistUtils.POOL + .withWriteReplace( + { + case metadata: ProcessingPass.Metadata => + metadata.prepareForSerialization( + ctx + .ensoContext() + .getCompiler + .context + .asInstanceOf[metadata.Compiler] + ); + case obj => obj + } + ) + .write(mainIr); fail("Shouldn't return anything when there is an error" + arr) } catch { case ex: IOException => diff --git a/engine/runtime-parser/src/test/java/org/enso/compiler/core/IrPersistanceTest.java b/engine/runtime-parser/src/test/java/org/enso/compiler/core/IrPersistanceTest.java index 0f3a6fdfdd92..29ed64ed2e2d 100644 --- a/engine/runtime-parser/src/test/java/org/enso/compiler/core/IrPersistanceTest.java +++ b/engine/runtime-parser/src/test/java/org/enso/compiler/core/IrPersistanceTest.java @@ -349,58 +349,65 @@ public void inlineReferenceIsLazy() throws Exception { @Test public void readResolve() throws Exception { var in = new Service(5); - var arr = POOL.write(in, (Function) null); + var arr = POOL.write(in); - var plain = POOL.read(arr, (Function) null); + var plain = POOL.read(arr); assertEquals("Remains five", 5, plain.get(Service.class).value()); var multiOnRead = - POOL.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + POOL.withReadResolve((obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj) + .read(arr); assertEquals("Multiplied on read", 15, multiOnRead.get(Service.class).value()); } @Test public void writeReplace() throws Exception { var in = new Service(5); - var arr = POOL.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var arr = + POOL.withWriteReplace((obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj) + .write(in); - var plain = POOL.read(arr, (Function) null); + var plain = POOL.read(arr); assertEquals("Multiplied on write", 15, plain.get(Service.class).value()); } @Test public void readResolveInline() throws Exception { var in = new ServiceSupply(new Service(5)); - var arr = POOL.write(in, (Function) null); + var arr = POOL.write(in); - var plain = POOL.read(arr, (Function) null); + var plain = POOL.read(arr); assertEquals("Remains five", 5, plain.get(ServiceSupply.class).supply().value()); var multiOnRead = - POOL.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + POOL.withReadResolve((obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj) + .read(arr); assertEquals("Multiplied on read", 15, multiOnRead.get(ServiceSupply.class).supply().value()); } @Test public void writeReplaceInline() throws Exception { var in = new ServiceSupply(new Service(5)); - var arr = POOL.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var arr = + POOL.withWriteReplace((obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj) + .write(in); - var plain = POOL.read(arr, (Function) null); + var plain = POOL.read(arr); assertEquals("Multiplied on write", 15, plain.get(ServiceSupply.class).supply().value()); } @Test public void readResolveReference() throws Exception { var in = new IntegerSupply(new Service(5)); - var arr = POOL.write(in, (Function) null); + var arr = POOL.write(in); - var plain = POOL.read(arr, (Function) null); + var plain = POOL.read(arr); assertEquals("Remains five", 5, (int) plain.get(IntegerSupply.class).supply().get()); assertEquals("Remains five 2", 5, (int) plain.get(IntegerSupply.class).supply().get()); var multiOnRead = - POOL.read(arr, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + POOL.withReadResolve((obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj) + .read(arr); assertEquals( "Multiplied on read", 15, (int) multiOnRead.get(IntegerSupply.class).supply().get()); } @@ -408,9 +415,11 @@ public void readResolveReference() throws Exception { @Test public void writeReplaceReference() throws Exception { var in = new IntegerSupply(new Service(5)); - var arr = POOL.write(in, (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); + var arr = + POOL.withWriteReplace((obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj) + .write(in); - var plain = POOL.read(arr, (Function) null); + var plain = POOL.read(arr); assertEquals("Multiplied on write", 15, (int) plain.get(IntegerSupply.class).supply().get()); } @@ -430,11 +439,11 @@ private static T serde(Class clazz, T l, int expectedSize) throws IOExcep private static T serde(Class clazz, T l, int expectedSize, Function fn) throws IOException { - var arr = POOL.write(l, fn); + var arr = POOL.withWriteReplace(fn).write(l); if (expectedSize >= 0) { assertEquals(expectedSize, arr.length - 12); } - var ref = POOL.read(arr, null); + var ref = POOL.read(arr); return ref.get(clazz); } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java index 4fb1ebec8db3..b3d2459adcac 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/CacheUtils.java @@ -24,8 +24,9 @@ private CacheUtils() {} private static int BUFFER_SIZE = 1024; static Persistance.Pool createPool(CompilerContext context, boolean keepUUIDs) { - return Persistance.Pool.withReplaceRewrite( - PersistUtils.POOL, readResolve(context), writeReplace(context, keepUUIDs)); + return PersistUtils.POOL + .withReadResolve(readResolve(context)) + .withWriteReplace(writeReplace(context, keepUUIDs)); } private static Function writeReplace(CompilerContext context, boolean keepUUIDs) { diff --git a/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java b/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java index f0f4776c0349..10b88f6ec99e 100644 --- a/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java +++ b/engine/runtime/src/test/java/org/enso/runtime/test/TypeMetadataPersistanceTest.java @@ -22,8 +22,8 @@ public class TypeMetadataPersistanceTest { org.enso.compiler.pass.analyse.types.Persistables.POOL); private static T serde(Class clazz, T l) throws IOException { - var arr = POOL.write(l, null); - var ref = POOL.read(arr, null); + var arr = POOL.write(l); + var ref = POOL.read(arr); return ref.get(clazz); } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java index fc67e976f43a..9fc343e8a6ca 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -23,41 +23,45 @@ final synchronized TruffleObject findObject(long id) { @Override @SuppressWarnings("unchecked") public final Persistance.Pool createPool(Channel channel) { - return Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - (obj) -> - switch (obj) { - case OtherJvmObject other -> { - if (other.id() < 0) { - // the other object with negative number came back - // it is our own object - var ourOwn = findObject(-other.id()); - assert ourOwn != null; - yield ourOwn; - } else { - // real truffle object in the other JVM - // need to keep it as OtherJvmObject proxy - // just associate channel to it - var proxy = OtherJvmObject.bindToChannel(other, (Channel) channel); - yield proxy; - } - } - default -> obj; - }, - (obj) -> - switch (obj) { - case OtherJvmObject other -> { - // returning back their own OtherJvmObject - let - // them know it is theirs by using negative ID - yield new OtherJvmObject(null, -other.id()); - } - case TruffleObject foreign -> { - var id = registerObject(foreign); - // our own truffle objects send to the other side should - // have a positive ID - yield new OtherJvmObject(null, id); - } - default -> obj; - }); + var withRead = + Persistables.POOL.withReadResolve( + (obj) -> + switch (obj) { + case OtherJvmObject other -> { + if (other.id() < 0) { + // the other object with negative number came back + // it is our own object + var ourOwn = findObject(-other.id()); + assert ourOwn != null; + yield ourOwn; + } else { + // real truffle object in the other JVM + // need to keep it as OtherJvmObject proxy + // just associate channel to it + var proxy = + OtherJvmObject.bindToChannel(other, (Channel) channel); + yield proxy; + } + } + default -> obj; + }); + var withReadAndWrite = + withRead.withWriteReplace( + (obj) -> + switch (obj) { + case OtherJvmObject other -> { + // returning back their own OtherJvmObject - let + // them know it is theirs by using negative ID + yield new OtherJvmObject(null, -other.id()); + } + case TruffleObject foreign -> { + var id = registerObject(foreign); + // our own truffle objects send to the other side should + // have a positive ID + yield new OtherJvmObject(null, id); + } + default -> obj; + }); + return withReadAndWrite; } } diff --git a/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java b/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java index ea6fe78dc487..8ed00b91558c 100644 --- a/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java +++ b/lib/java/persistance/src/main/java/org/enso/persist/Persistance.java @@ -228,17 +228,32 @@ public static Pool merge(Pool... pools) { } /** - * Associates write replace and/or read resolve functions with the pool. + * Associates this pool with a read resolve function. * - * @param pool pool to associate - * @param readResolve either {@code null} or function to call for each object being stored to - * provide a replacement - * @param writeReplace {@code null} or a function that allows to convert each object before - * storing it down - * @return new pool unchanged except being associated with resolve and replace functions instead - * of any previous functions associated + * @param readResolve function to call when an object is read to but before it is returned from + * {@link Input#readObject()} or {@link Input#readInline} methods to provide the object's + * replacement + * @return new pool which is internally unchanged except being associated with the provided read + * resolve function */ - public static Pool withReplaceRewrite( + public Pool withReadResolve(Function readResolve) { + return newWithResolveAndReplace(this, readResolve, this.writeReplace); + } + + /** + * Associates this pool with a write replace function. + * + * @param writeReplace function to call when an object is about to be written via {@link + * Output#writeObject} or {@link Output#writeInline} functions to provide the object's + * replacement + * @return new pool which is internally unchanged except being associated with the provided + * write replace function + */ + public Pool withWriteReplace(Function writeReplace) { + return newWithResolveAndReplace(this, this.readResolve, writeReplace); + } + + private static Pool newWithResolveAndReplace( Pool pool, Function readResolve, Function writeReplace) { return new Pool(pool.name, readResolve, writeReplace, pool.all) {}; } diff --git a/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java b/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java index 5ef369a0989b..7d8ca4a05631 100644 --- a/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java +++ b/lib/java/persistance/src/test/java/org/enso/persist/PersistanceTest.java @@ -30,10 +30,8 @@ public void testUUIDPersistance() throws Exception { @Test public void readResolve() throws Exception { var poolWith = - Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj, - null); + Persistables.POOL.withReadResolve( + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new Service(5); var arr = poolWith.write(in); @@ -48,9 +46,7 @@ public void readResolve() throws Exception { @Test public void writeReplace() throws Exception { var poolWith = - Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - null, + Persistables.POOL.withWriteReplace( (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new Service(5); var arr = poolWith.write(in); @@ -62,10 +58,8 @@ public void writeReplace() throws Exception { @Test public void readResolveInline() throws Exception { var poolWith = - Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj, - null); + Persistables.POOL.withReadResolve( + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new ServiceSupply(new Service(5)); var arr = poolWith.write(in); @@ -80,9 +74,7 @@ public void readResolveInline() throws Exception { @Test public void writeReplaceInline() throws Exception { var poolWith = - Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - null, + Persistables.POOL.withWriteReplace( (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new ServiceSupply(new Service(5)); var arr = poolWith.write(in); @@ -94,10 +86,8 @@ public void writeReplaceInline() throws Exception { @Test public void readResolveReference() throws Exception { var poolWith = - Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj, - null); + Persistables.POOL.withReadResolve( + (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new IntegerSupply(new Service(5)); var arr = poolWith.write(in); @@ -114,9 +104,7 @@ public void readResolveReference() throws Exception { @Test public void writeReplaceReference() throws Exception { var poolWith = - Persistance.Pool.withReplaceRewrite( - Persistables.POOL, - null, + Persistables.POOL.withWriteReplace( (obj) -> obj instanceof Service s ? new Service(s.value() * 3) : obj); var in = new IntegerSupply(new Service(5)); var arr = poolWith.write(in); diff --git a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala index 23767de285d9..6770a161cb40 100644 --- a/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala +++ b/lib/scala/project-manager/src/test/scala/org/enso/projectmanager/TestDistributionConfiguration.scala @@ -60,7 +60,7 @@ class TestDistributionConfiguration( lazy val distributionManager = new DistributionManager(environment) lazy val graalVersionManager = - new GraalVersionManager(distributionManager, environment) + new GraalVersionManager(distributionManager) lazy val lockManager = new TestLocalLockManager From f383a7dfc412265597979ef245ba5b60fce0369d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 12:23:57 +0200 Subject: [PATCH 42/60] These options cannot be passed to in process JVM - removing --- .jvmopts | 3 --- 1 file changed, 3 deletions(-) diff --git a/.jvmopts b/.jvmopts index 8b17718b0915..890e17024c2a 100644 --- a/.jvmopts +++ b/.jvmopts @@ -1,6 +1,3 @@ --Xss16M --Xmx4G --XX:+UseCompressedOops --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED From c47eefe458a072f22744abc64056c62fb20d07e8 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 12:24:22 +0200 Subject: [PATCH 43/60] Testing differences in propagation of exceptions --- .../enso/jvm/interop/OtherJvmObjectTest.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index b8edab7b0275..7f317068dbe5 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -3,6 +3,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -10,7 +11,10 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.jvm.channel.Channel; import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Value; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.hamcrest.core.StringContains; import org.junit.ClassRule; import org.junit.Test; @@ -88,6 +92,27 @@ public static short otherJvmValueOf(String txt) { return Short.parseShort(txt); } + @Test + public void parsingException() throws Exception { + var shortClass1 = ctx.asValue(java.lang.Short.class).getMember("static"); + try { + var value1 = shortClass1.invokeMember("valueOf", "not-a-number"); + fail("Unexpected returned value: " + value1); + } catch (PolyglotException e) { + MatcherAssert.assertThat(e.getMessage(), CoreMatchers.containsString("not-a-number")); + assertTrue("This is host exception", e.isHostException()); + assertNotNull("Host exception found", e.asHostException()); + } + var shortClass2 = loadOtherJvmClass("java.lang.Short"); + try { + var value2 = shortClass2.invokeMember("valueOf", "not-a-number"); + fail("Unexpected returned value: " + value2); + } catch (PolyglotException e) { + MatcherAssert.assertThat(e.getMessage(), CoreMatchers.containsString("not-a-number")); + assertFalse("Alas this cannot be host exception", e.isHostException()); + } + } + @Test public void classNotFoundError() { var msg = new OtherJvmMessage.LoadClass("java.lang.unknown.Clazz"); From 269d66e70837f5388817c9948f5ef056dc38cf87 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 12:25:43 +0200 Subject: [PATCH 44/60] Support for --vm.D=polyglot.enso.hostClassLoading=false experimental flag --- .../src/main/java/org/enso/common/RuntimeOptions.java | 6 ++++++ .../main/java/org/enso/interpreter/runtime/EnsoContext.java | 4 +++- .../src/main/java/org/enso/jvm/interop/OtherJvmObject.java | 3 +-- .../java/org/enso/jvm/interop/OtherJvmSymbolResolver.java | 2 +- .../main/java/org/enso/jvm/interop/TruffleClassLoader.java | 1 + 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/engine/common/src/main/java/org/enso/common/RuntimeOptions.java b/engine/common/src/main/java/org/enso/common/RuntimeOptions.java index a9c88076868b..23eb12e760bd 100644 --- a/engine/common/src/main/java/org/enso/common/RuntimeOptions.java +++ b/engine/common/src/main/java/org/enso/common/RuntimeOptions.java @@ -35,6 +35,11 @@ private RuntimeOptions() {} private static final OptionDescriptor ENABLE_STATIC_ANALYSIS_DESCRIPTOR = OptionDescriptor.newBuilder(ENABLE_STATIC_ANALYSIS_KEY, ENABLE_STATIC_ANALYSIS).build(); + public static final String HOST_CLASS_LOADING = optionName("hostClassLoading"); + public static final OptionKey HOST_CLASS_LOADING_KEY = new OptionKey<>(true); + private static final OptionDescriptor HOST_CLASS_LOADING_DESCRIPTOR = + OptionDescriptor.newBuilder(HOST_CLASS_LOADING_KEY, HOST_CLASS_LOADING).build(); + public static final String TREAT_WARNINGS_AS_ERRORS = optionName("treatWarningsAsErrors"); public static final OptionKey TREAT_WARNINGS_AS_ERRORS_KEY = new OptionKey<>(false); private static final OptionDescriptor TREAT_WARNINGS_AS_ERRORS_DESCRIPTOR = @@ -173,6 +178,7 @@ private RuntimeOptions() {} DISABLE_INLINE_CACHES_DESCRIPTOR, DISABLE_PRIVATE_CHECK_DESCRIPTOR, ENABLE_STATIC_ANALYSIS_DESCRIPTOR, + HOST_CLASS_LOADING_DESCRIPTOR, TREAT_WARNINGS_AS_ERRORS_DESCRIPTOR, ENABLE_AUTO_PARALLELISM_DESCRIPTOR, ENABLE_PROJECT_SUGGESTIONS_DESCRIPTOR, diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 88cf23163cd1..eca98d00d906 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -86,6 +86,7 @@ public final class EnsoContext { private final boolean assertionsEnabled; private final boolean isPrivateCheckDisabled; private final boolean isStaticTypeAnalysisEnabled; + private final boolean isHostClassLoading; private @CompilationFinal Compiler compiler; private final PrintStream out; private final PrintStream err; @@ -148,6 +149,7 @@ public EnsoContext( getOption(RuntimeOptions.DISABLE_IR_CACHES_KEY) || isParallelismEnabled; this.isPrivateCheckDisabled = getOption(RuntimeOptions.DISABLE_PRIVATE_CHECK_KEY); this.isStaticTypeAnalysisEnabled = getOption(RuntimeOptions.ENABLE_STATIC_ANALYSIS_KEY); + this.isHostClassLoading = getOption(RuntimeOptions.HOST_CLASS_LOADING_KEY); this.globalExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); this.assertionsEnabled = shouldAssertionsBeEnabled(); this.shouldWaitForPendingSerializationJobs = @@ -621,7 +623,7 @@ static Object lookupJavaClass( public TruffleObject lookupJavaClass(String className) { var collectedExceptions = new ArrayList(); - if (true) { // set this to false to simulate classloading via Channel + if (isHostClassLoading) { var hostSymbol = ClassLookup.lookupJavaClass( className, // name to search for diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 73a627459ce6..71550458cc9a 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -19,7 +19,6 @@ final class OtherJvmObject implements TruffleObject { private final long id; OtherJvmObject(Channel channel, long id) { - assert id > 0; this.channel = channel; this.id = id; } @@ -40,7 +39,7 @@ Object send(Message message, Object[] args) throws Exception { // proper dispatch to the other JVM var msg = new OtherJvmMessage(id, message, List.of(args)); var reply = channel.execute(OtherJvmResult.class, msg); - return bindToChannel(reply.value(), channel); + return reply.value(); } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java index e04a987c34bd..5d9388cb299f 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmSymbolResolver.java @@ -27,7 +27,7 @@ protected Object handleLoadClass(String name) throws ClassNotFoundException { try { var ch = getChannel(); var result = ch.execute(OtherJvmResult.class, new OtherJvmMessage.LoadClass(name)); - return OtherJvmObject.bindToChannel(result.value(), ch); + return result.value(); } catch (IOException | URISyntaxException ex) { throw new ClassNotFoundException(name, ex); } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index 139163fe8e67..3b440a5a27b9 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -27,6 +27,7 @@ static synchronized Context ctx() { ctx = Context.newBuilder() // no dynamic languages needed .allowHostAccess(HostAccess.ALL) // all public members + .allowExperimentalOptions(true) // to survive any -Dpolyglot options .build(); } return ctx; From f6171fbae67b6eb63ab123fde29e2c0dab013772 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 14:05:23 +0200 Subject: [PATCH 45/60] Propagating UnsupportedMessageException --- .../org/enso/jvm/interop/OtherJvmMessage.java | 17 +++++++++++----- .../org/enso/jvm/interop/OtherJvmObject.java | 5 ++++- .../enso/jvm/interop/OtherJvmObjectTest.java | 20 +++++++++++++++++++ 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index aba3afc9a71a..75fc8c82e723 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -2,12 +2,15 @@ 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.Message; import com.oracle.truffle.api.library.ReflectionLibrary; import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; import org.enso.jvm.channel.Channel; import org.enso.persist.Persistable; @@ -29,13 +32,16 @@ static ReturnValue create(T value) { @Persistable(id = 81909, allowInlining = false) record ThrowException(int kind, String msg) implements OtherJvmResult { + private static final Map, Integer> kinds; + + static { + kinds = new LinkedHashMap<>(); + kinds.put(ClassNotFoundException.class, 1); + kinds.put(UnsupportedMessageException.class, 2); + } static ThrowException create(E ex) { - var kind = - switch (ex.getClass().getName()) { - case "java.lang.ClassNotFoundException" -> 1; - default -> 0; - }; + var kind = kinds.getOrDefault(ex.getClass(), 0); return new ThrowException<>(kind, ex.getMessage()); } @@ -44,6 +50,7 @@ static ThrowException create(E ex) { public V value() throws E { switch (kind) { case 1 -> throw (E) new ClassNotFoundException(msg()); + case 2 -> throw (E) UnsupportedMessageException.create(); default -> throw new IllegalStateException(msg()); } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 71550458cc9a..98a0e03bb2fb 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -15,6 +15,9 @@ final class OtherJvmObject implements TruffleObject { /** receiver for other than InteropLibrary messages */ private static final Object POJO = new Object(); + /** special message */ + private static final Message HAS_LANGUAGE = Message.resolve(InteropLibrary.class, "hasLanguage"); + private final Channel channel; private final long id; @@ -30,7 +33,7 @@ long id() { @CompilerDirectives.TruffleBoundary @ExportMessage Object send(Message message, Object[] args) throws Exception { - if (message.getLibraryClass() != InteropLibrary.class) { + if (message.getLibraryClass() != InteropLibrary.class || HAS_LANGUAGE == message) { // we need to invoke default implementation of library // to handle the message in a proper way // hence provide POJO as a receiver diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 7f317068dbe5..df68b0feb8b9 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -113,6 +113,26 @@ public void parsingException() throws Exception { } } + @Test + public void unsupportedOperation() throws Exception { + var shortClass1 = ctx.asValue(java.lang.Short.class).getMember("static"); + try { + var value1 = shortClass1.getArrayElement(0); + fail("Unexpected returned value: " + value1); + } catch (UnsupportedOperationException e) { + MatcherAssert.assertThat( + e.getMessage(), CoreMatchers.containsString("Unsupported operation")); + } + var shortClass2 = loadOtherJvmClass("java.lang.Short"); + try { + var value2 = shortClass2.getArrayElement(0); + fail("Unexpected returned value: " + value2); + } catch (UnsupportedOperationException e) { + MatcherAssert.assertThat( + e.getMessage(), CoreMatchers.containsString("Unsupported operation")); + } + } + @Test public void classNotFoundError() { var msg = new OtherJvmMessage.LoadClass("java.lang.unknown.Clazz"); From bd95d18319babd138ea1bc981eec395509d41bab Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 15:24:34 +0200 Subject: [PATCH 46/60] Compatible identity checks --- .../org/enso/jvm/interop/OtherJvmMessage.java | 5 +++ .../org/enso/jvm/interop/OtherJvmObject.java | 24 ++++++++++-- .../org/enso/jvm/interop/OtherJvmPool.java | 2 + .../enso/jvm/interop/OtherJvmObjectTest.java | 37 +++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index 75fc8c82e723..d046f755092a 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -56,12 +56,17 @@ public V value() throws E { } } + private static final Message IS_IDENTICAL = Message.resolve(InteropLibrary.class, "isIdentical"); + @Override public OtherJvmResult apply(Channel t) { try { TruffleClassLoader.ctx().enter(); var receiver = t.getConfig().findObject(id); assert receiver instanceof TruffleObject; + if (message == IS_IDENTICAL) { + args.set(1, InteropLibrary.getUncached()); + } var res = ReflectionLibrary.getUncached().send(receiver, message, args.toArray()); return new ReturnValue<>(res); } catch (Exception ex) { diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java index 98a0e03bb2fb..154e7f476978 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmObject.java @@ -7,7 +7,7 @@ import com.oracle.truffle.api.library.ExportMessage; import com.oracle.truffle.api.library.Message; import com.oracle.truffle.api.library.ReflectionLibrary; -import java.util.List; +import java.util.Arrays; import org.enso.jvm.channel.Channel; @ExportLibrary(ReflectionLibrary.class) @@ -18,6 +18,10 @@ final class OtherJvmObject implements TruffleObject { /** special message */ private static final Message HAS_LANGUAGE = Message.resolve(InteropLibrary.class, "hasLanguage"); + private static final Message IS_IDENTICAL_OR_UNDEFINED = + Message.resolve(InteropLibrary.class, "isIdenticalOrUndefined"); + private static final Message IS_IDENTICAL = Message.resolve(InteropLibrary.class, "isIdentical"); + private final Channel channel; private final long id; @@ -33,14 +37,28 @@ long id() { @CompilerDirectives.TruffleBoundary @ExportMessage Object send(Message message, Object[] args) throws Exception { - if (message.getLibraryClass() != InteropLibrary.class || HAS_LANGUAGE == message) { + if (message == IS_IDENTICAL) { + if (args[0] instanceof OtherJvmObject other) { + if (id() == other.id()) { + return true; + } else { + // fall thru but without the library + args[1] = null; + } + } else { + return false; + } + } + if (message.getLibraryClass() != InteropLibrary.class + || HAS_LANGUAGE == message + || IS_IDENTICAL_OR_UNDEFINED == message) { // we need to invoke default implementation of library // to handle the message in a proper way // hence provide POJO as a receiver return ReflectionLibrary.getUncached().send(POJO, message, args); } else { // proper dispatch to the other JVM - var msg = new OtherJvmMessage(id, message, List.of(args)); + var msg = new OtherJvmMessage(id, message, Arrays.asList(args)); var reply = channel.execute(OtherJvmResult.class, msg); return reply.value(); } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java index 9fc343e8a6ca..af3fc370053b 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -43,6 +43,7 @@ public final Persistance.Pool createPool(Channel channel) { yield proxy; } } + case null -> null; default -> obj; }); var withReadAndWrite = @@ -60,6 +61,7 @@ public final Persistance.Pool createPool(Channel channel) { // have a positive ID yield new OtherJvmObject(null, id); } + case null -> null; default -> obj; }); return withReadAndWrite; diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index df68b0feb8b9..328e9af620ad 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -3,6 +3,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -155,6 +156,42 @@ public void messageFromAnUnsupportedLibrary() throws Exception { assertFalse("Other JVM objects don't have type", noType); } + private static final Object IDENTICAL = new Object(); + + public static Object otherJvmInstances(int kind) { + if (kind == 0) { + return IDENTICAL; + } else { + return new Object(); + } + } + + @Test + public void isIdenticalCheck() throws Exception { + var localClass = ctx.asValue(OtherJvmObjectTest.class).getMember("static"); + var local1 = localClass.invokeMember("otherJvmInstances", 0); + var local2 = localClass.invokeMember("otherJvmInstances", 0); + assertEquals(local1, local2); + + var otherClass = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + var other1 = otherClass.invokeMember("otherJvmInstances", 0); + var other2 = otherClass.invokeMember("otherJvmInstances", 0); + assertEquals(other1, other2); + } + + @Test + public void isNotIdenticalCheck() throws Exception { + var localClass = ctx.asValue(OtherJvmObjectTest.class).getMember("static"); + var local1 = localClass.invokeMember("otherJvmInstances", 1); + var local2 = localClass.invokeMember("otherJvmInstances", 1); + assertNotEquals(local1, local2); + + var otherClass = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + var other1 = otherClass.invokeMember("otherJvmInstances", 1); + var other2 = otherClass.invokeMember("otherJvmInstances", 1); + assertNotEquals(other1, other2); + } + private static Value loadOtherJvmClass(String name) throws Exception { var msg = new OtherJvmMessage.LoadClass(name); var shortRaw = CHANNEL.execute(OtherJvmResult.class, msg).value(); From 6b77ae7e7263ea344862b35db1544ebfc6e1873d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 15:39:38 +0200 Subject: [PATCH 47/60] Can callback with a String --- .../enso/jvm/interop/OtherJvmObjectTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 328e9af620ad..772d263dcf35 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -5,15 +5,18 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.math.BigDecimal; +import java.util.function.Consumer; import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.jvm.channel.Channel; import org.enso.test.utils.ContextUtils; import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Value; +import org.graalvm.polyglot.proxy.ProxyExecutable; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.hamcrest.core.StringContains; @@ -192,6 +195,39 @@ public void isNotIdenticalCheck() throws Exception { assertNotEquals(other1, other2); } + public static void callback(Consumer cb, Object value) { + cb.accept(value); + } + + @Test + public void callback() throws Exception { + class MockProxy implements ProxyExecutable { + private Value last; + + @Override + public Object execute(Value... arguments) { + assertNull("No args yet", last); + assertEquals("One arg", 1, arguments.length); + last = arguments[0]; + return arguments[0]; + } + + final void assertArgs(String msg, Value exp) { + assertEquals(msg, exp.asString(), this.last.asString()); + this.last = null; + } + } + var mock = new MockProxy(); + + var localClass = ctx.asValue(OtherJvmObjectTest.class).getMember("static"); + localClass.invokeMember("callback", mock, "Real"); + mock.assertArgs("Called with Real", ctx.asValue("Real")); + + var otherClass = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); + otherClass.invokeMember("callback", mock, "RealOther"); + mock.assertArgs("Called with Real", ctx.asValue("RealOther")); + } + private static Value loadOtherJvmClass(String name) throws Exception { var msg = new OtherJvmMessage.LoadClass(name); var shortRaw = CHANNEL.execute(OtherJvmResult.class, msg).value(); From 5d6adb2cd79c12406538189f7ad116b84e3bc2d3 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 16:50:09 +0200 Subject: [PATCH 48/60] Channel.isMaster --- .../java/org/enso/jvm/channel/Channel.java | 24 +++++++++++++++---- .../jvm/channel/ChannelInSingleJvmTest.java | 8 +++++-- .../org/enso/os/environment/jni/TestMain.java | 1 + .../os/environment/jni/LoadClassTest.java | 2 ++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 079291fd5427..46f4551710a1 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -100,13 +100,13 @@ private Channel(long id, Data data, long isolate, long callbackFn) { * Mock constructor. Creates a channel that simulates sending of the messages inside of the same * JVM. Useful for testing. */ - private Channel(Data myData, Channel otherOrNull, Data otherData, long id) { + private Channel(long isolate, Data myData, Channel otherOrNull, Data otherData, long id) { if (ImageInfo.inImageCode()) { throw new IllegalStateException("Only usable in HotSpot"); } this.id = id; this.data = myData; - this.isolate = -2; + this.isolate = isolate; this.callbackFn = null; this.env = null; this.channelClass = null; @@ -115,7 +115,7 @@ private Channel(Data myData, Channel otherOrNull, Data otherData, long id) otherOrNull != null ? otherOrNull // use other channel when provided : // otherwise allocate new and pass this reference to it - new Channel<>(otherData, this, null, id); + new Channel<>(-3, otherData, this, null, id); this.pool = data.createPool(this); } @@ -135,7 +135,7 @@ public static synchronized Channel create( var id = idCounter++; if (jvm == null) { var otherData = newInstance(configClass); - return new Channel<>(config, null, otherData, id); + return new Channel<>(-2, config, null, otherData, id); } if (!ImageInfo.inImageCode()) { @@ -188,6 +188,20 @@ public final Data getConfig() { return data; } + /** + * Master channel check. One instance of the {@code Channel} on the initializing side is marked as + * master. The other one is slave. + * + * @return is master + */ + public final boolean isMaster() { + return isolate == -1 || isolate == -2; + } + + final boolean isDirect() { + return isolate == -2 || isolate == -3; + } + /** * Executes a message in the other JVM. The message is any subclass of {@link Function} * registered for persistance via {@link Persistable @Persistable} annotation into the {@link @@ -351,7 +365,7 @@ private R executeImpl( // handles this.execute var memory = MemorySegment.ofBuffer(buffer); memory.copyFrom(MemorySegment.ofArray(bytes)); address = memory.address(); - len = isolate == -2 ? toDirectMessage(buffer) : toSubstrateMessage(memory); + len = isDirect() ? toDirectMessage(buffer) : toSubstrateMessage(memory); } if (len == -2) { // signals exception diff --git a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java index 23ba70caacc8..f7b4811cf0f8 100644 --- a/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java +++ b/lib/java/jvm-channel/src/test/java/org/enso/jvm/channel/ChannelInSingleJvmTest.java @@ -1,7 +1,9 @@ package org.enso.jvm.channel; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import java.util.function.Function; import org.enso.persist.Persistable; @@ -26,6 +28,7 @@ public Persistance.Pool createPool(Channel ignore) { @Test public void exchangeMessageThatModifiesItself() { var ch = Channel.create(null, PrivateData.class); + assertTrue("The created channel is a master", ch.isMaster()); var msg = new Increment(10); @@ -54,7 +57,7 @@ public void exchangeMessageThatModifiesPrivateData() { } @Persistable(id = 8341) - static final class Increment implements Function { + static final class Increment implements Function, Increment> { int valueToIncrement; Increment(int valueToIncrement) { @@ -66,8 +69,9 @@ int valueToIncrement() { } @Override - public Increment apply(Object ignore) { + public Increment apply(Channel channel) { valueToIncrement++; + assertFalse("We are processed in the slave", channel.isMaster()); return this; } } diff --git a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java index 2b220884580f..756569a88e47 100644 --- a/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java +++ b/lib/java/os-environment/src/main/java/org/enso/os/environment/jni/TestMain.java @@ -37,6 +37,7 @@ static BigInteger factorial(long n) { record RequestFactorial(long n) implements Function, Void> { @Override public Void apply(Channel channel) { + assert !channel.isMaster() : "Requesting factorial is handled in the slave only"; var res = factorial(n).toString(); channel.execute(Void.class, new ReportResult(n, res)); return null; diff --git a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java index 9b851316af6b..d17b35aba2a6 100644 --- a/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java +++ b/lib/java/os-environment/src/test/java/org/enso/os/environment/jni/LoadClassTest.java @@ -1,6 +1,7 @@ package org.enso.os.environment.jni; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; @@ -45,6 +46,7 @@ private static JVM jvm() { @Before public void initializeChannel() throws Exception { channel = Channel.create(jvm(), JVMPeer.class); + assertTrue("Created channel is master", channel.isMaster()); } @Test From e745a10c7c48ac4bba9a25204af940eef5ab514e Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 16 Jun 2025 17:47:55 +0200 Subject: [PATCH 49/60] Only enter the TruffleClassLoader.ctx() when on slave --- .../org/enso/jvm/interop/OtherJvmMessage.java | 12 +++++++++--- .../enso/jvm/interop/TruffleClassLoader.java | 12 +++++++++++- .../enso/jvm/interop/OtherJvmObjectTest.java | 18 +++++++++++++++--- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index d046f755092a..c2ac972b76d6 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -60,8 +60,15 @@ public V value() throws E { @Override public OtherJvmResult apply(Channel t) { + if (t.isMaster()) { + return handle(t); + } else { + return TruffleClassLoader.withCtx(() -> handle(t)); + } + } + + private OtherJvmResult handle(Channel t) { try { - TruffleClassLoader.ctx().enter(); var receiver = t.getConfig().findObject(id); assert receiver instanceof TruffleObject; if (message == IS_IDENTICAL) { @@ -71,8 +78,6 @@ public V value() throws E { return new ReturnValue<>(res); } catch (Exception ex) { return ThrowException.create(ex); - } finally { - TruffleClassLoader.ctx().leave(); } } @@ -81,6 +86,7 @@ record LoadClass(String name) implements Function> { @Override public OtherJvmResult apply(Channel t) { + assert !t.isMaster() : "Class loading only works on the slave side!"; try { var clazzRaw = TruffleClassLoader.loadClassObject(name); return ReturnValue.create(clazzRaw); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index 3b440a5a27b9..c0e297125591 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -9,6 +9,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.util.function.Supplier; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.HostAccess; @@ -22,7 +23,16 @@ private TruffleClassLoader() { super(new URL[0]); } - static synchronized Context ctx() { + static D withCtx(Supplier action) { + ctx().enter(); + try { + return action.get(); + } finally { + ctx().leave(); + } + } + + private static synchronized Context ctx() { if (ctx == null) { ctx = Context.newBuilder() // no dynamic languages needed diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 772d263dcf35..5ea21c2b90fb 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -14,18 +14,26 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary; import org.enso.jvm.channel.Channel; import org.enso.test.utils.ContextUtils; +import org.graalvm.polyglot.Context; import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Value; import org.graalvm.polyglot.proxy.ProxyExecutable; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.hamcrest.core.StringContains; +import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; public class OtherJvmObjectTest { @ClassRule public static final ContextUtils ctx = ContextUtils.newBuilder("js").build(); - private static final Channel CHANNEL = Channel.create(null, OtherJvmPool.class); + + private static Channel CHANNEL; + + @BeforeClass + public static void initializeChannel() { + CHANNEL = Channel.create(null, OtherJvmPool.class); + } @Test public void wrapBigDecimal() throws Exception { @@ -209,6 +217,9 @@ public Object execute(Value... arguments) { assertNull("No args yet", last); assertEquals("One arg", 1, arguments.length); last = arguments[0]; + + var myCtx = Context.getCurrent(); + assertEquals("The right context", ctx.context(), myCtx); return arguments[0]; } @@ -218,13 +229,14 @@ final void assertArgs(String msg, Value exp) { } } var mock = new MockProxy(); + var mockValue = ctx.asValue(mock); var localClass = ctx.asValue(OtherJvmObjectTest.class).getMember("static"); - localClass.invokeMember("callback", mock, "Real"); + localClass.invokeMember("callback", mockValue, "Real"); mock.assertArgs("Called with Real", ctx.asValue("Real")); var otherClass = loadOtherJvmClass(OtherJvmObjectTest.class.getName()); - otherClass.invokeMember("callback", mock, "RealOther"); + otherClass.invokeMember("callback", mockValue, "RealOther"); mock.assertArgs("Called with Real", ctx.asValue("RealOther")); } From 4f373cd68cd8485915ae9810d30fd879e584edc7 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 08:35:05 +0200 Subject: [PATCH 50/60] Encapsulate general problems with other JVM into Truffle exception --- .../java/org/enso/jvm/interop/OtherJvmException.java | 9 +++++++++ .../main/java/org/enso/jvm/interop/OtherJvmMessage.java | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java new file mode 100644 index 000000000000..e334c77186e1 --- /dev/null +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java @@ -0,0 +1,9 @@ +package org.enso.jvm.interop; + +import com.oracle.truffle.api.exception.AbstractTruffleException; + +final class OtherJvmException extends AbstractTruffleException { + OtherJvmException(String message) { + super(message); + } +} diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index c2ac972b76d6..61cdae701df5 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -51,7 +51,7 @@ public V value() throws E { switch (kind) { case 1 -> throw (E) new ClassNotFoundException(msg()); case 2 -> throw (E) UnsupportedMessageException.create(); - default -> throw new IllegalStateException(msg()); + default -> throw new OtherJvmException(msg()); } } } From fdc89fb5d7b44148af0524981349441fd5e73f6d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 10:02:05 +0200 Subject: [PATCH 51/60] Opt-in via --vm.D=polyglot.enso.classLoading to enable other JVM classloading --- .../enso/common/PolyglotSymbolResolver.java | 14 ++++++- .../java/org/enso/common/RuntimeOptions.java | 10 +++-- .../enso/interpreter/runtime/EnsoContext.java | 41 +++++++++++++------ .../enso/jvm/interop/OtherJvmException.java | 6 +-- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java index 2c8bd8e2222b..b9727c3b7131 100644 --- a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java +++ b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java @@ -6,7 +6,19 @@ import java.util.Collections; import java.util.ServiceLoader; -/** Generic support for loading Java polyglot symbols. */ +/** + * Generic support for loading Java polyglot symbols. The resolver provides two kinds of interfaces: + * + *
    + *
  • the client API - represented by all the public static methods + *
  • the SPI - e.g. service provider interface - those are the protected abstract methods + *
+ * + * Those who tend to extend the capabilities of loading Java classes into Enso runtime shall + * register their own implementation visible via {@link ServiceLoader}. + * + * @see RuntimeOptions#HOST_CLASS_LOADING + */ public abstract class PolyglotSymbolResolver { private static final Collection ALL; diff --git a/engine/common/src/main/java/org/enso/common/RuntimeOptions.java b/engine/common/src/main/java/org/enso/common/RuntimeOptions.java index 23eb12e760bd..66c2d9675bb6 100644 --- a/engine/common/src/main/java/org/enso/common/RuntimeOptions.java +++ b/engine/common/src/main/java/org/enso/common/RuntimeOptions.java @@ -35,10 +35,14 @@ private RuntimeOptions() {} private static final OptionDescriptor ENABLE_STATIC_ANALYSIS_DESCRIPTOR = OptionDescriptor.newBuilder(ENABLE_STATIC_ANALYSIS_KEY, ENABLE_STATIC_ANALYSIS).build(); - public static final String HOST_CLASS_LOADING = optionName("hostClassLoading"); - public static final OptionKey HOST_CLASS_LOADING_KEY = new OptionKey<>(true); + public static final String HOST_CLASS_LOADING = optionName("classLoading"); + public static final OptionKey HOST_CLASS_LOADING_KEY = new OptionKey<>("hosted"); private static final OptionDescriptor HOST_CLASS_LOADING_DESCRIPTOR = - OptionDescriptor.newBuilder(HOST_CLASS_LOADING_KEY, HOST_CLASS_LOADING).build(); + OptionDescriptor.newBuilder(HOST_CLASS_LOADING_KEY, HOST_CLASS_LOADING) + .help("Controls the way Enso runtime resolves polyglot java import statements") + .usageSyntax("Possible values are ") + .category(OptionCategory.INTERNAL) + .build(); public static final String TREAT_WARNINGS_AS_ERRORS = optionName("treatWarningsAsErrors"); public static final OptionKey TREAT_WARNINGS_AS_ERRORS_KEY = new OptionKey<>(false); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index eca98d00d906..66b171090010 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -87,6 +87,7 @@ public final class EnsoContext { private final boolean isPrivateCheckDisabled; private final boolean isStaticTypeAnalysisEnabled; private final boolean isHostClassLoading; + private final boolean isResolverClassLoading; private @CompilationFinal Compiler compiler; private final PrintStream out; private final PrintStream err; @@ -149,7 +150,20 @@ public EnsoContext( getOption(RuntimeOptions.DISABLE_IR_CACHES_KEY) || isParallelismEnabled; this.isPrivateCheckDisabled = getOption(RuntimeOptions.DISABLE_PRIVATE_CHECK_KEY); this.isStaticTypeAnalysisEnabled = getOption(RuntimeOptions.ENABLE_STATIC_ANALYSIS_KEY); - this.isHostClassLoading = getOption(RuntimeOptions.HOST_CLASS_LOADING_KEY); + { + var classLoading = getOption(RuntimeOptions.HOST_CLASS_LOADING_KEY); + this.isHostClassLoading = + switch (classLoading) { + case "hosted", "all" -> true; + case "service" -> false; + case null, default -> throw new IllegalStateException(classLoading); + }; + this.isResolverClassLoading = switch (classLoading) { + case "service", "all" -> true; + case "hosted" -> false; + case null, default -> throw new IllegalStateException(classLoading); + }; + } this.globalExecutionEnvironment = getOption(EnsoLanguage.EXECUTION_ENVIRONMENT); this.assertionsEnabled = shouldAssertionsBeEnabled(); this.shouldWaitForPendingSerializationJobs = @@ -634,19 +648,20 @@ public TruffleObject lookupJavaClass(String className) { return (TruffleObject) hostSymbol; } } - var javaHome = System.getProperty("java.home"); - logger.info( - () -> String.format("Class %s not found, trying to turn on JVM %s", className, javaHome)); - var hostSymbol = - ClassLookup.lookupJavaClass( - className, // name to search for - PolyglotSymbolResolver::loadClass, // pluggable polyglot searches - collectedExceptions // collect exceptions - ); - if (hostSymbol instanceof TruffleObject) { - return (TruffleObject) hostSymbol; + if (isResolverClassLoading) { + var javaHome = System.getProperty("java.home"); + logger.info( + () -> String.format("Class %s not found, trying to turn on JVM %s", className, javaHome)); + var hostSymbol = + ClassLookup.lookupJavaClass( + className, // name to search for + PolyglotSymbolResolver::loadClass, // pluggable polyglot searches + collectedExceptions // collect exceptions + ); + if (hostSymbol instanceof TruffleObject) { + return (TruffleObject) hostSymbol; + } } - var level = Level.WARNING; for (var ex : collectedExceptions) { logger.log(level, ex.getMessage()); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java index e334c77186e1..61bc7939c1df 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmException.java @@ -3,7 +3,7 @@ import com.oracle.truffle.api.exception.AbstractTruffleException; final class OtherJvmException extends AbstractTruffleException { - OtherJvmException(String message) { - super(message); - } + OtherJvmException(String message) { + super(message); + } } From dc4db1f3b726ff7310c1eccecb43b300d8ed652d Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 10:08:21 +0200 Subject: [PATCH 52/60] Initialize with null --- .../main/java/org/enso/common/PolyglotSymbolResolver.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java index b9727c3b7131..a5430d51bfe5 100644 --- a/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java +++ b/engine/common/src/main/java/org/enso/common/PolyglotSymbolResolver.java @@ -38,7 +38,7 @@ public abstract class PolyglotSymbolResolver { * @throws java.lang.ClassNotFoundException if no name was found */ public static Object loadClass(String name) throws ClassNotFoundException { - var ex = new ClassNotFoundException(); + ClassNotFoundException ex = null; for (var p : ALL) { try { var found = p.handleLoadClass(name); @@ -48,7 +48,11 @@ public static Object loadClass(String name) throws ClassNotFoundException { ex = cnfe; } } - throw ex; + if (ex == null) { + throw new ClassNotFoundException(name); + } else { + throw ex; + } } public static void addToClassPath(URL url) { From 6018f38758031e32de780773a3deaeb40794b93f Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 10:09:38 +0200 Subject: [PATCH 53/60] Removing non-javadoc documentation --- .../src/main/java/org/enso/jvm/channel/Channel.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 46f4551710a1..53cfd0500481 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -343,11 +343,10 @@ private void checkForException(JNI.JNIEnv e) { } } - private R executeImpl( // handles this.execute - Persistance.Pool pool, // the pool with persitance - Class replyType, // requested return type - Function, ? extends R> msg // function to serde to the other JVM - ) { + private R executeImpl( + Persistance.Pool pool, + Class replyType, + Function, ? extends R> msg) { var address = 0L; try { var bytes = pool.write(msg); From 9ea85e32e5cc8d5ae3d9bfb928f04e0951104436 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 10:10:39 +0200 Subject: [PATCH 54/60] Removing commented out requires statements --- lib/java/jvm-interop/src/main/java/module-info.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/module-info.java b/lib/java/jvm-interop/src/main/java/module-info.java index 9c3aa429a703..adeb052d5a89 100644 --- a/lib/java/jvm-interop/src/main/java/module-info.java +++ b/lib/java/jvm-interop/src/main/java/module-info.java @@ -2,10 +2,6 @@ import org.enso.jvm.interop.OtherJvmSymbolResolver; module org.enso.jvm.interop { - // requires org.slf4j; - // requires org.graalvm.nativeimage; - // requires org.graalvm.word; - requires org.enso.engine.common; provides PolyglotSymbolResolver with From 22f170cb2b736d4c6a1946cb8643fc3e10af5c11 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 10:13:46 +0200 Subject: [PATCH 55/60] A javadoc --- .../src/main/java/org/enso/jvm/interop/OtherJvmMessage.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index 61cdae701df5..e147cba6914f 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -16,10 +16,9 @@ import org.enso.persist.Persistable; import org.enso.persist.Persistance; +/** Sends a message to the other side with ReflectionLibrary-like arguments. */ @Persistable(id = 81901) -record OtherJvmMessage( // sends a message to the other side - long id, Message message, List args // with ReflectionLibrary-like arguments - ) +record OtherJvmMessage(long id, Message message, List args) implements Function< Channel, OtherJvmResult> { @Persistable(id = 81908, allowInlining = false) From e765579d125c23b5634e207a25ac8bcfcc713a93 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 10:19:04 +0200 Subject: [PATCH 56/60] Using name constants to identify special isolates --- .../main/java/org/enso/jvm/channel/Channel.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java index 53cfd0500481..b89c4371ccf7 100644 --- a/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java +++ b/lib/java/jvm-channel/src/main/java/org/enso/jvm/channel/Channel.java @@ -33,6 +33,10 @@ * @param internal data of the channel */ public final class Channel implements AutoCloseable { + private static final long ISOLATE_SVM = -1; + private static final long ISOLATE_MOCK_MASTER = -2; + private static final long ISOLATE_MOCK_SLAVE = -3; + /** * @GuardedBy("Channel.class") */ @@ -63,7 +67,7 @@ private Channel( this.id = id; this.data = data; this.env = env; - this.isolate = -1; + this.isolate = ISOLATE_SVM; this.callbackFn = null; this.channelClass = handleClass; this.channelHandle = handleFn; @@ -115,7 +119,7 @@ private Channel(long isolate, Data myData, Channel otherOrNull, Data other otherOrNull != null ? otherOrNull // use other channel when provided : // otherwise allocate new and pass this reference to it - new Channel<>(-3, otherData, this, null, id); + new Channel<>(ISOLATE_MOCK_SLAVE, otherData, this, null, id); this.pool = data.createPool(this); } @@ -135,7 +139,7 @@ public static synchronized Channel create( var id = idCounter++; if (jvm == null) { var otherData = newInstance(configClass); - return new Channel<>(-2, config, null, otherData, id); + return new Channel<>(ISOLATE_MOCK_MASTER, config, null, otherData, id); } if (!ImageInfo.inImageCode()) { @@ -195,11 +199,11 @@ public final Data getConfig() { * @return is master */ public final boolean isMaster() { - return isolate == -1 || isolate == -2; + return isolate == ISOLATE_SVM || isolate == ISOLATE_MOCK_MASTER; } final boolean isDirect() { - return isolate == -2 || isolate == -3; + return isolate == ISOLATE_MOCK_MASTER || isolate == ISOLATE_MOCK_SLAVE; } /** From cda39805f01a7e55f8c5148897858d1d186215c9 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 14:16:35 +0200 Subject: [PATCH 57/60] Associate Channel.isMaster with a Context when running OtherJvmObjectTest --- .../org/enso/jvm/interop/OtherJvmMessage.java | 20 +++++----- .../org/enso/jvm/interop/OtherJvmPool.java | 3 ++ .../enso/jvm/interop/TruffleClassLoader.java | 38 ++++++++++--------- .../enso/jvm/interop/OtherJvmObjectTest.java | 1 + 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java index e147cba6914f..255db85a4444 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmMessage.java @@ -59,11 +59,8 @@ public V value() throws E { @Override public OtherJvmResult apply(Channel t) { - if (t.isMaster()) { - return handle(t); - } else { - return TruffleClassLoader.withCtx(() -> handle(t)); - } + var res = t.getConfig().loader.withCtx(() -> handle(t)); + return res; } private OtherJvmResult handle(Channel t) { @@ -82,12 +79,13 @@ public V value() throws E { @Persistable(id = 81905) record LoadClass(String name) - implements Function> { + implements Function< + Channel, OtherJvmResult> { @Override - public OtherJvmResult apply(Channel t) { + public OtherJvmResult apply(Channel t) { assert !t.isMaster() : "Class loading only works on the slave side!"; try { - var clazzRaw = TruffleClassLoader.loadClassObject(name); + var clazzRaw = t.getConfig().loader.loadClassObject(name); return ReturnValue.create(clazzRaw); } catch (ClassNotFoundException ex) { return ThrowException.create(ex); @@ -96,10 +94,10 @@ public OtherJvmResult apply(Channel t) { } @Persistable(id = 81906) - record AddToClassPath(String url) implements Function, Void> { + record AddToClassPath(String url) implements Function, Void> { @Override - public Void apply(Channel t) { - TruffleClassLoader.addToClassPath(url); + public Void apply(Channel t) { + t.getConfig().loader.addToClassPath(url); return null; } } diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java index af3fc370053b..fb45e4051f47 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/OtherJvmPool.java @@ -10,6 +10,9 @@ public final class OtherJvmPool extends Channel.Config { private final Map objectsById = new HashMap<>(); + /** context to use when entering tests */ + final TruffleClassLoader loader = new TruffleClassLoader(); + private synchronized long registerObject(TruffleObject obj) { var size = objectsById.size() + 1; objectsById.put((long) size, obj); diff --git a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java index c0e297125591..49652579d1ec 100644 --- a/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java +++ b/lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/TruffleClassLoader.java @@ -15,24 +15,19 @@ @ExportLibrary(value = InteropLibrary.class) final class TruffleClassLoader extends URLClassLoader implements TruffleObject { - private static final TruffleClassLoader DEFAULT = new TruffleClassLoader(); - private static Context ctx; + private Context ctx; private Object value; - private TruffleClassLoader() { + TruffleClassLoader() { super(new URL[0]); } - static D withCtx(Supplier action) { - ctx().enter(); - try { - return action.get(); - } finally { - ctx().leave(); - } + final synchronized void assignCtx(Context ctx) { + assert this.ctx == null; + this.ctx = ctx; } - private static synchronized Context ctx() { + private synchronized Context ctx() { if (ctx == null) { ctx = Context.newBuilder() // no dynamic languages needed @@ -43,20 +38,29 @@ private static synchronized Context ctx() { return ctx; } - static void addToClassPath(String url) { + final D withCtx(Supplier action) { + ctx().enter(); + try { + return action.get(); + } finally { + ctx().leave(); + } + } + + void addToClassPath(String url) { try { - DEFAULT.addURL(new URI(url).toURL()); + addURL(new URI(url).toURL()); } catch (MalformedURLException | URISyntaxException ex) { ex.printStackTrace(); } } - static TruffleObject loadClassObject(String className) throws ClassNotFoundException { - var clazz = DEFAULT.loadClass(className); + final TruffleObject loadClassObject(String className) throws ClassNotFoundException { + var clazz = loadClass(className); var clazzValue1 = ctx().asValue(clazz); var clazzValue2 = clazzValue1.getMember("static"); - ctx().asValue(DEFAULT).execute(clazzValue2); - return (TruffleObject) DEFAULT.value; + ctx().asValue(this).execute(clazzValue2); + return (TruffleObject) value; } @ExportMessage diff --git a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java index 5ea21c2b90fb..3228eb1f9104 100644 --- a/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java +++ b/lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/OtherJvmObjectTest.java @@ -33,6 +33,7 @@ public class OtherJvmObjectTest { @BeforeClass public static void initializeChannel() { CHANNEL = Channel.create(null, OtherJvmPool.class); + CHANNEL.getConfig().loader.assignCtx(ctx.context()); } @Test From 66a20452840313a77d92ee4c1bb8a362f79805a3 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 18:28:27 +0200 Subject: [PATCH 58/60] Avoid PolyglotSymbolResolver.addToClassPath until it is turned on --- .../main/java/org/enso/interpreter/runtime/EnsoContext.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java index 66b171090010..4a10862cdcea 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/EnsoContext.java @@ -519,7 +519,9 @@ public void addToClassPath(TruffleFile file) { try { var url = file.toUri().toURL(); hostClassLoader.add(url); - PolyglotSymbolResolver.addToClassPath(url); + if (isResolverClassLoading) { + PolyglotSymbolResolver.addToClassPath(url); + } } catch (MalformedURLException ex) { throw new IllegalStateException(ex); } From f4b374ee67aea8fe6aa1a5ac1dc451282a205a1b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 17 Jun 2025 18:44:24 +0200 Subject: [PATCH 59/60] windows-2019 image is being deprecated --- .github/workflows/enso4igv.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/enso4igv.yml b/.github/workflows/enso4igv.yml index 6d13793fda82..c2d4b17be78d 100644 --- a/.github/workflows/enso4igv.yml +++ b/.github/workflows/enso4igv.yml @@ -79,7 +79,7 @@ jobs: target/lib/** build_windows_parser: - runs-on: windows-2019 + runs-on: windows-2022 steps: - uses: actions/checkout@v4 From d77b80bfd7911dd85e2bef41519bb6d3f523f651 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Wed, 18 Jun 2025 04:32:58 +0200 Subject: [PATCH 60/60] Backout of f383a7dfc412265597979ef245ba5b60fce0369d --- .jvmopts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.jvmopts b/.jvmopts index 890e17024c2a..8b17718b0915 100644 --- a/.jvmopts +++ b/.jvmopts @@ -1,3 +1,6 @@ +-Xss16M +-Xmx4G +-XX:+UseCompressedOops --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED