Skip to content

Commit 0008bcf

Browse files
Testing and enhancing JavaScript Dual JVM interop (#14780)
- extracted from #14438 - this is an independent fix improving JavaScript interop - when JavaScript strings are crossing _dual JVM_ boundary
1 parent 8a12ee6 commit 0008bcf

File tree

4 files changed

+149
-5
lines changed

4 files changed

+149
-5
lines changed

build.sbt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4298,11 +4298,12 @@ lazy val `jvm-interop` =
42984298
(Test / fork) := true,
42994299
commands += WithDebugCommand.withDebug,
43004300
libraryDependencies ++= Seq(
4301-
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
4302-
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
4303-
"org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test,
4304-
"junit" % "junit" % junitVersion % Test,
4305-
"com.github.sbt" % "junit-interface" % junitIfVersion % Test
4301+
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion % "provided",
4302+
"org.graalvm.truffle" % "truffle-dsl-processor" % graalMavenPackagesVersion % "provided",
4303+
"org.graalvm.sdk" % "graal-sdk" % graalMavenPackagesVersion % Test,
4304+
"junit" % "junit" % junitVersion % Test,
4305+
"com.github.sbt" % "junit-interface" % junitIfVersion % Test,
4306+
"org.graalvm.polyglot" % "js-community" % graalMavenPackagesVersion % Test
43064307
),
43074308
Compile / moduleDependencies ++= Seq(
43084309
"org.graalvm.truffle" % "truffle-api" % graalMavenPackagesVersion,

lib/java/jvm-interop/src/main/java/org/enso/jvm/interop/impl/OtherInteropType.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.oracle.truffle.api.interop.InteropLibrary;
55
import com.oracle.truffle.api.interop.TruffleObject;
66
import com.oracle.truffle.api.library.Message;
7+
import com.oracle.truffle.api.strings.TruffleString;
78
import java.io.IOException;
89
import java.math.BigInteger;
910
import java.nio.charset.StandardCharsets;
@@ -600,4 +601,29 @@ protected Duration readObject(Persistance.Input in) throws IOException, ClassNot
600601
return Duration.ofSeconds(s, n);
601602
}
602603
}
604+
605+
@Persistable(id = 127)
606+
static final class PersistTruffleString extends Persistance<TruffleString> {
607+
608+
public PersistTruffleString() {
609+
super(TruffleString.class, true, 127);
610+
}
611+
612+
@Override
613+
protected void writeObject(TruffleString obj, Persistance.Output out) throws IOException {
614+
var s = obj.toJavaStringUncached();
615+
var bytes = s.getBytes(StandardCharsets.UTF_8);
616+
out.writeInt(bytes.length);
617+
out.write(bytes);
618+
}
619+
620+
@Override
621+
protected TruffleString readObject(Persistance.Input in)
622+
throws IOException, ClassNotFoundException {
623+
var len = in.readInt();
624+
var bytes = new byte[len];
625+
in.readFully(bytes);
626+
return TruffleString.fromByteArrayUncached(bytes, TruffleString.Encoding.UTF_8, false);
627+
}
628+
}
603629
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package org.enso.jvm.interop.impl;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertTrue;
5+
import static org.junit.Assert.fail;
6+
7+
import com.oracle.truffle.api.TruffleLanguage;
8+
import org.enso.jvm.channel.Channel;
9+
import org.enso.test.utils.ContextUtils;
10+
import org.graalvm.polyglot.Context;
11+
import org.graalvm.polyglot.Value;
12+
import org.junit.BeforeClass;
13+
import org.junit.ClassRule;
14+
import org.junit.Test;
15+
16+
public class OtherJvmJavaScriptTest {
17+
@ClassRule
18+
public static final ContextUtils ctx = ContextUtils.newBuilder("host").assertGC(false).build();
19+
20+
private static Channel<OtherJvmPool> CHANNEL;
21+
22+
@BeforeClass
23+
public static void initializeChannel() {
24+
System.setProperty("org.enso.jvm.interop.limit", "" + Integer.MAX_VALUE);
25+
CHANNEL = Channel.create(null, OtherJvmPool.class);
26+
CHANNEL
27+
.getConfig()
28+
.onEnterLeave(
29+
FakeLanguage.class,
30+
null,
31+
(__) -> {
32+
ctx.context().enter();
33+
return null;
34+
},
35+
(__, ___) -> {
36+
ctx.context().leave();
37+
});
38+
}
39+
40+
@Test
41+
public void wrapTruffleString() throws Exception {
42+
var testClassValue = loadOtherJvmClass(OtherJvmJavaScriptTest.class.getName());
43+
assertOtherJvmObject("Represents clazz from the other JVM", testClassValue);
44+
45+
var result =
46+
new ResultCallbacks() {
47+
private Object value;
48+
49+
@Override
50+
public void onMessage(Object o) {
51+
this.value = o;
52+
}
53+
};
54+
55+
var returnedResult = testClassValue.invokeMember("multiString", "Hello", 3, result);
56+
assertTrue("Represents a string", returnedResult.isString());
57+
58+
assertEquals("HelloHelloHello", returnedResult.asString());
59+
assertEquals("HelloHelloHello", result.value.toString());
60+
}
61+
62+
public static String multiString(String txt, int count, ResultCallbacks onResult) {
63+
try (var jsCtx = Context.newBuilder("js").build()) {
64+
var fn =
65+
jsCtx.eval(
66+
"js",
67+
"""
68+
(function(txt, count, onResult) {
69+
let sb = "";
70+
for (let i = 0; i < count; i++) {
71+
sb = sb + txt;
72+
}
73+
onResult.onMessage(sb);
74+
return sb;
75+
})
76+
""");
77+
var res = fn.execute(txt, count, onResult).asString();
78+
return res;
79+
}
80+
}
81+
82+
private static Value loadOtherJvmClass(String name) throws Exception {
83+
var msg = new OtherJvmMessage.LoadClass(name);
84+
var raw = CHANNEL.execute(OtherJvmResult.class, msg).value(null);
85+
if (raw instanceof OtherJvmObject other) {
86+
assertTrue(other.assertChannel(CHANNEL));
87+
}
88+
var value = ctx.asValue(raw);
89+
return value;
90+
}
91+
92+
private static void assertOtherJvmObject(String msg, Value value) {
93+
var unwrap = ctx.unwrapValue(value);
94+
if (unwrap instanceof OtherJvmObject) {
95+
return;
96+
}
97+
fail(msg + " but got: " + unwrap);
98+
}
99+
100+
public static interface ResultCallbacks {
101+
public void onMessage(Object o);
102+
}
103+
104+
private abstract static class FakeLanguage extends TruffleLanguage<Object> {}
105+
}

lib/java/jvm-interop/src/test/java/org/enso/jvm/interop/impl/OtherJvmObjectTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ public static Object otherJvmInstances(int kind) {
280280
case 3 -> new int[20];
281281
case 4 -> "Hello";
282282
case 5 -> "Hello".repeat(100000);
283+
case 6 -> wrap("Hello");
284+
case 7 -> wrap("Hello".repeat(100000));
283285
default -> null;
284286
};
285287
}
@@ -350,6 +352,16 @@ public void isStringLong() throws Exception {
350352
checkString(5);
351353
}
352354

355+
@Test
356+
public void isTruffleStringShort() throws Exception {
357+
checkString(6);
358+
}
359+
360+
@Test
361+
public void isTruffleStringLong() throws Exception {
362+
checkString(7);
363+
}
364+
353365
private void checkString(int kind) throws Exception {
354366
var localClass = ctx.asValue(OtherJvmObjectTest.class).getMember("static");
355367
var local1 = localClass.invokeMember("otherJvmInstances", kind);

0 commit comments

Comments
 (0)