Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4174,7 +4174,7 @@ lazy val `jvm-channel` =
.in(file("lib/java/jvm-channel"))
.enablePlugins(JPMSPlugin)
.settings(
customFrgaalJavaCompilerSettings("24"),
customFrgaalJavaCompilerSettings(targetJdk = "24"),
autoScalaLibrary := false,
(Test / fork) := true,
commands += WithDebugCommand.withDebug,
Expand Down Expand Up @@ -4202,7 +4202,7 @@ lazy val `jvm-interop` =
.in(file("lib/java/jvm-interop"))
.enablePlugins(JPMSPlugin)
.settings(
frgaalJavaCompilerSetting,
customFrgaalJavaCompilerSettings("24"),
// jvm-interop/test has to run with -ea disabled form Truffle.
// Otherwise Truffle library performs a lot of additional
// checks and they skew the message counts. Thus enabling -ea
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ final class OtherInteropType {

private static final int MASK_ARRAY = 0x0400;
private static final int MASK_HASH = 0x0800;
private static final int MASK_BUFFER = 0x1000;

private OtherInteropType() {}

Expand All @@ -54,6 +55,9 @@ static short findType(TruffleObject obj) {
if (iop.hasHashEntries(obj)) {
one |= MASK_HASH;
}
if (iop.hasBufferElements(obj)) {
one |= MASK_BUFFER;
}
return one;
}

Expand Down Expand Up @@ -185,6 +189,10 @@ static boolean hasHashEntries(int v) {
return (v & MASK_HASH) == MASK_HASH;
}

static boolean hasBufferElements(int v) {
return (v & MASK_BUFFER) == MASK_BUFFER;
}

@Persistable(id = 1)
static final class PersistTruffleObject extends Persistance<TruffleObject> {
PersistTruffleObject() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,55 @@
import com.oracle.truffle.api.library.Message;
import com.oracle.truffle.api.library.ReflectionLibrary;
import com.oracle.truffle.api.nodes.Node;
import java.lang.foreign.MemorySegment;
import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import org.enso.jvm.channel.Channel;
import org.enso.persist.Persistable;
import org.graalvm.polyglot.Value;

/** Sends a message to the other side with ReflectionLibrary-like arguments. */
@Persistable(id = 81901)
public record OtherJvmMessage(long id, Message message, List<Object> args)
implements Function<
Channel<OtherJvmPool>, OtherJvmResult<? extends Object, ? extends Exception>> {
private static final Message IS_IDENTICAL = Message.resolve(InteropLibrary.class, "isIdentical");
private static final Message IS_POINTER = Message.resolve(InteropLibrary.class, "isPointer");
private static final Message AS_POINTER = Message.resolve(InteropLibrary.class, "asPointer");

@Override
public OtherJvmResult<? extends Object, ? extends Exception> apply(Channel<OtherJvmPool> t) {
var node = ReflectionLibrary.getUncached();
var prev = t.getConfig().enter(t.isMaster(), node);
var lib = ReflectionLibrary.getUncached();
var prev = t.getConfig().enter(t.isMaster(), lib);
try {
var receiver = t.getConfig().findObject(id());
if (receiver == null) {
throw new NullPointerException(
"No object for " + id() + " message: " + message() + " args: " + args());
}
var iop = InteropLibrary.getUncached();
if (message == IS_IDENTICAL) {
args.set(1, InteropLibrary.getUncached());
args.set(1, iop);
}
var res = node.send(receiver, message, args.toArray());
if (message == IS_POINTER && iop.hasBufferElements(receiver)) {
var buf = Value.asValue(receiver).as(ByteBuffer.class);
return new ReturnValue<>(buf.isDirect());
}
if (message == AS_POINTER && iop.hasBufferElements(receiver)) {
var buf = Value.asValue(receiver).as(ByteBuffer.class);
var seg = MemorySegment.ofBuffer(buf);
return new ReturnValue<>(seg.address());
}
var res = lib.send(receiver, message, args.toArray());
return new ReturnValue<>(res);
} catch (Exception ex) {
return ThrowException.create(ex);
} finally {
t.getConfig().leave(t.isMaster(), node, prev);
t.getConfig().leave(t.isMaster(), lib, prev);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ final class OtherJvmObject implements TruffleObject {
Message.resolve(InteropLibrary.class, "hasArrayElements");
private static final Message HAS_HASH_ENTRIES =
Message.resolve(InteropLibrary.class, "hasHashEntries");
private static final Message HAS_BUFFER_ELEMENTS =
Message.resolve(InteropLibrary.class, "hasBufferElements");

private static final Message IS_DATE = Message.resolve(InteropLibrary.class, "isDate");
private static final Message IS_TIME = Message.resolve(InteropLibrary.class, "isTime");
Expand Down Expand Up @@ -159,8 +161,8 @@ final Object send(Message message, Object[] args, @Bind Node self) throws Except
if (message == HAS_HASH_ENTRIES) {
return OtherInteropType.hasHashEntries(mask);
}
if (message == HAS_ARRAY_ELEMENTS) {
return OtherInteropType.hasArrayElements(mask);
if (message == HAS_BUFFER_ELEMENTS) {
return OtherInteropType.hasBufferElements(mask);
}
if (message == IS_TIME) {
return OtherInteropType.isTime(mask);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import java.lang.foreign.MemorySegment;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.function.Consumer;
import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
Expand All @@ -23,6 +25,7 @@
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.ByteSequence;
import org.graalvm.polyglot.proxy.ProxyExecutable;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
Expand Down Expand Up @@ -456,6 +459,86 @@ public void metaObjectEgClassesAreImmutableInJVM() throws Exception {
});
}

public static final class WithABuffer {
public final ByteBuffer buf;

WithABuffer(ByteBuffer buf) {
this.buf = buf;
}

public String toText() {
var arr = new byte[buf.limit()];
buf.get(arr);
return new String(arr);
}
}

public static WithABuffer withBuffer(int type, int size) {
var buf =
switch (type) {
case 0 -> ByteBuffer.allocateDirect(size);
default -> throw new IllegalArgumentException();
};
buf.put("Hello".getBytes());
buf.flip();
return new WithABuffer(buf);
}

@Test
public void directByteBufferHostInterop() throws Exception {
var otherClass = ctx.asValue(OtherJvmObjectTest.class).getMember("static");
checkDirectByteBuffer(otherClass);
}

@Test
public void directByteBufferGuestInterop() throws Exception {
var otherClass = loadOtherJvmClass(OtherJvmObjectTest.class.getName());
checkDirectByteBuffer(otherClass);
}

private void checkDirectByteBuffer(Value otherClass) throws Exception {
var withBuffer = otherClass.invokeMember("withBuffer", 0, 10);
var bufValue = withBuffer.getMember("buf");
assertTrue("It is a byte buffer", bufValue.hasBufferElements());
var seq = bufValue.as(ByteSequence.class);
assertEquals('H', seq.byteAt(0));
assertEquals('e', seq.byteAt(1));
assertEquals('l', seq.byteAt(2));
assertEquals('l', seq.byteAt(3));
assertEquals('o', seq.byteAt(4));

var buf = asByteBuffer(bufValue);
assertEquals('H', buf.get(0));
assertEquals('e', buf.get(1));
assertEquals('l', buf.get(2));
assertEquals('l', buf.get(3));
assertEquals('o', buf.get(4));
buf.put(0, "Ahoj!".getBytes());

assertEquals("Ahoj!", withBuffer.invokeMember("toText").asString());
}

/**
* Converting a buffer-like value to {@link ByteBuffer} is tricky. Simple {@link
* Value#as(java.lang.Class)} works only for {@code HostObject}. To convert "guest value" we need
* to do something special. Let's rely on special <em>native pointer</em> support provided by the
* other JVM for direct {@link ByteBuffer}.
*
* @param value the value to convert to {@link ByteBuffer}
* @return instance of {@link ByteBuffer} to use in this JVM
*/
private static ByteBuffer asByteBuffer(Value value) throws Exception {
assertTrue("The value is buffer-like", value.hasBufferElements());
try {
return value.as(ByteBuffer.class);
} catch (ClassCastException ex) {
assertTrue("Direct buffer should support native address", value.isNativePointer());
var address = value.asNativePointer();
var seg = MemorySegment.ofAddress(address).reinterpret(value.getBufferSize());
return seg.asByteBuffer();
}
}

private static Value loadOtherJvmClass(String name) throws Exception {
var msg = new OtherJvmMessage.LoadClass(name);
var raw = CHANNEL.execute(OtherJvmResult.class, msg).value(null);
Expand Down
Loading