When working with computation intensive applications or working with low latency applications it would be interesting to allow passing Buffers between Java and Graaljs. I've learned that Nashorn (since Java 9) allows this using the following snippet:
// usually you would get it from the java context but for simplification
// i've created a byte buffer in js
var ByteBuffer = Java.type('java.nio.ByteBuffer');
var bb = ByteBuffer.allocateDirect(12);
// the official ArrayBuffer takes 1 argument (the length)
// but nashorn has a small trick, if the argument is of type
// ByteBuffer, it uses the ByteBuffer instead of allocating a new one
var ab = new ArrayBuffer(bb);
// now we can use it with the JS specific TypedArrays views,
// for example...
var ia = new Int8Array(ab);
// in the end the java code could just inspect the byte buffer above either
// by keeping a reference to it or other ways...
Now this allows us for example to create a buffer in java, do some work, pass the buffer to js and let js do some work in a "simpler language" easier to experiment with, small dev time, etc... and in the end use the result and continue in java.
We can do something similar to this but requires that the buffer gets copied to a byte[] when passing back from js to java:
// no need to pass a buffer will rely on the Int8Array
var ia = new Int8Array(12);
// do some work
return Java.to(ia, 'byte[]');
I guess that this approach puts more pressure in the garbage collector, CPU, and probably on the java side it will be wrapped again into a buffer for further processing...
Also note that this approach does not take into account if the buffer already comes from java, in that case we need to make another slow copy into a buffer to feed to the typed array... which I guess will require a loop instead of a fast memory copy operation.
So my request would be to allow ArrayBuffer to diverge from the ECMA spec and allow this one extra constructor argument like nashorn >=9.
@pmlopes should the new ArrayBuffer be using the passed ByteBuffer directly or can it be a copy?
I think to be compatible with nashorn and to make it "useful". By useful i mean the following use case:
We shouldn't copy but just take a reference.
Hi @pmlopes ,
we recently introduced some initial support for this feature. These tests should give you an overview of what is currently supported.
Please let us know if you have any feedback!
Cheers,
Daniele
Hi @eleinadani,
Thanks for the update this seems a great feature, I just have one question: on Value::as(ByteBuffer.class) what types will it work with? On the tests it's always a java.nio.ByteBuffer either created on the script or provided in the bindings, but will it also work if the script object is of type JavaScript ArrayBuffer ? or any of the TypedArrays https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray ?
Hi @pmlopes,
Currently we support automatic conversion via .as(ByteBuffer.class) only for Value objects that _are_ ByteBuffer instances, as described in the tests. Supporting conversion from ArrayBuffer/TypedArray to ByteBuffer would require some changes to our interop mechanisms. This might be possible in the future, but we don't have concrete plans fort that at the moment.
Ok great, one last question, is is possible to access the ByteBuffer from a js ArrayBuffer so we don't have to pass the byte buffer reference all the way the call stack where it might need to interop with java?
Say a non enumerable property? That can be undefined if no backing byte buffer was provided.
Hi @pmlopes,
this sounds like a feature that could be implemented in pure JS code (e.g., by storing the ByteBuffer storage as a field of the new ArrayBuffer, or by patching the ArrayBuffer constructor). Do you have a specific example/scenario where this would not be possible?
It could but since the extension (constructor with j.n.ByteBuffer) is on graal I thing an getter on a non enumerable property could be too.
I can patch on my side but I think this would benefit all users not just me. Regardless of the outcome thanks for adding this!
@eleinadani, I guess this should work right?
const ByteBuffer = Java.type('java.nio.ByteBuffer');
// we need to redefine the constructor of arraybuffer to allow us to access the
// underlying j.n.ByteBuffer if present
class EArrayBuffer extends ArrayBuffer {
constructor(...args) {
// no change in behaviour
super(...args);
// keep a non enumerable reference to the buffer if present
if (args[0] instanceof ByteBuffer) {
Object.defineProperty(this, "buffer", {
value: args[0]
});
}
}
}
// replace the default impl with the extended one
global.ArrayBuffer = EArrayBuffer;
Yes, that looks like a reasonable approach. The following:
var bb = new ArrayBuffer(ByteBuffer.allocateDirect(32))
console.log(bb.buffer)
should then print something like:
java.nio.DirectByteBuffer[pos=0 lim=32 cap=32]
This feature is working as described on the last could of releases.
Most helpful comment
Hi @pmlopes ,
we recently introduced some initial support for this feature. These tests should give you an overview of what is currently supported.
Please let us know if you have any feedback!
Cheers,
Daniele