Skip to content

Commit 02889ed

Browse files
committed
[GR-49386] [GR-38404] Convert guest byte buffers to host byte array or ByteSequence.
PullRequest: graal/15821
2 parents b3d20a4 + 8d4128d commit 02889ed

File tree

30 files changed

+1372
-48
lines changed

30 files changed

+1372
-48
lines changed

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/ffi/TruffleByteBuffer.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,21 @@ byte readBufferByte(long byteOffset, @Shared("error") @Cached BranchProfile erro
167167
}
168168
}
169169

170+
@ExportMessage
171+
void readBuffer(long byteOffset, byte[] destination, int destinationOffset, int length, @Shared("error") @Cached BranchProfile error) throws InvalidBufferOffsetException {
172+
if (length < 0) {
173+
error.enter();
174+
throw InvalidBufferOffsetException.create(byteOffset, length);
175+
}
176+
try {
177+
int index = Math.toIntExact(byteOffset);
178+
readBytes(this.byteBuffer, index, destination, destinationOffset, length);
179+
} catch (ArithmeticException | IndexOutOfBoundsException e) {
180+
error.enter();
181+
throw InvalidBufferOffsetException.create(byteOffset, length);
182+
}
183+
}
184+
170185
@ExportMessage
171186
void writeBufferByte(long byteOffset, byte value, @Shared("error") @Cached BranchProfile error) throws UnsupportedMessageException, InvalidBufferOffsetException {
172187
try {
@@ -311,6 +326,11 @@ private static byte readByte(ByteBuffer byteBuffer, int index) {
311326
return byteBuffer.get(index);
312327
}
313328

329+
@TruffleBoundary(allowInlining = true)
330+
private static void readBytes(ByteBuffer byteBuffer, int index, byte[] destination, int destinationOffset, int length) {
331+
byteBuffer.get(index, destination, destinationOffset, length);
332+
}
333+
314334
@TruffleBoundary(allowInlining = true)
315335
private static short readShort(ByteBuffer byteBuffer, ByteOrder order, int index) {
316336
return byteBuffer.order(order).asShortBuffer().get(index);

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/messages/InteropMessage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public enum Message {
125125
IsBufferWritable,
126126
GetBufferSize,
127127
ReadBufferByte,
128+
ReadBuffer,
128129
WriteBufferByte,
129130
ReadBufferShort,
130131
WriteBufferShort,

espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/runtime/dispatch/staticobject/SharedInterop.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1358,6 +1358,23 @@ public static byte readBufferByte(StaticObject receiver, long byteOffset,
13581358
throw unsupported();
13591359
}
13601360

1361+
@ExportMessage
1362+
public static void readBuffer(StaticObject receiver, long byteOffset, byte[] destination, int destinationOffset, int length,
1363+
@Cached IndirectCallNode callNode,
1364+
@Cached CallSharedInteropMessage sharedCallNode) throws UnsupportedMessageException {
1365+
int dispatchId = receiver.getKlass().getDispatchId();
1366+
InteropMessage.Message message = InteropMessage.Message.ReadBuffer;
1367+
if (InteropMessageFactories.isShareable(dispatchId, message)) {
1368+
dispatchId = InteropMessageFactories.sourceDispatch(dispatchId, message);
1369+
sharedCallNode.call(dispatchId, message, receiver, byteOffset, destination, destinationOffset, length);
1370+
}
1371+
CallTarget target = getTarget(receiver, InteropMessage.Message.ReadBuffer);
1372+
if (target != null) {
1373+
callNode.call(target, receiver, byteOffset, destination, destinationOffset, length);
1374+
}
1375+
throw unsupported();
1376+
}
1377+
13611378
@ExportMessage
13621379
public static void writeBufferByte(StaticObject receiver, long byteOffset, byte value,
13631380
@Cached IndirectCallNode callNode,

sdk/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ This changelog summarizes major changes between GraalVM SDK versions. The main f
44

55
## Version 24.0.0
66
* (GR-49334) Deprecated the `FileSystems#allowLanguageHomeAccess()` method and introduced `FileSystem#allowInternalResourceAccess()` as a replacement. To ensure compatibility, both methods now provide support for language homes and internal resources.
7+
* (GR-49386) Added `Value#readBuffer(long, byte[], int, int)` to enable bulk reads of buffers into byte arrays.
8+
* (GR-49386) Added the ability to use `Value#as(ByteSequence.class)` to map guest language byte buffers (`Value#hasBufferElements()`) to the read-only `ByteSequence` interface in order to access the bytes without copying the guest language buffer.
9+
* (GR-49386) Custom implementations of `ByteSequence`, like the values returned by `ByteSequence.create(byte[])`, are now interpreted by guest languages as buffers.
10+
* (GR-38404) Added the ability to use `Value#as(Collection.class)` to map guest language arrays (`Value#hasArrayElements()`) to the `Collection` interface in order to access the array elements without copying the guest language array.
711

812
## Version 23.1.0
913
* (GR-43819) The GraalVM SDK was split into several more fine-grained modules. The use of the graalvm-sdk module is now deprecated. Please update your Maven and module dependencies accordingly. Note that all APIs remain compatible. The following new modules are available:

sdk/src/org.graalvm.polyglot/snapshot.sigtest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@ meth public static org.graalvm.polyglot.Value asValue(java.lang.Object)
634634
meth public void pin()
635635
meth public void putHashEntry(java.lang.Object,java.lang.Object)
636636
meth public void putMember(java.lang.String,java.lang.Object)
637+
meth public void readBuffer(long,byte[],int,int)
637638
meth public void setArrayElement(long,java.lang.Object)
638639
meth public void writeBufferByte(long,byte)
639640
meth public void writeBufferDouble(java.nio.ByteOrder,long,double)

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Engine.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,21 @@ public ByteSequence asByteSequence(Object origin) {
11611161
return (ByteSequence) origin;
11621162
}
11631163

1164+
@Override
1165+
public Object toByteSequence(Object origin) {
1166+
return Engine.getImpl().asByteSequence(origin);
1167+
}
1168+
1169+
@Override
1170+
public int byteSequenceLength(Object origin) {
1171+
return ((ByteSequence) origin).length();
1172+
}
1173+
1174+
@Override
1175+
public byte byteSequenceByteAt(Object origin, int index) {
1176+
return ((ByteSequence) origin).byteAt(index);
1177+
}
1178+
11641179
@Override
11651180
public boolean isInstrument(Object instrument) {
11661181
return instrument instanceof Instrument;
@@ -1606,6 +1621,11 @@ public Class<?> getPolyglotExceptionClass() {
16061621
return PolyglotException.class;
16071622
}
16081623

1624+
@Override
1625+
public Class<?> getByteSequenceClass() {
1626+
return ByteSequence.class;
1627+
}
1628+
16091629
@Override
16101630
public Object callContextAsValue(Object current, Object classOverrides) {
16111631
return ((Context) current).asValue(classOverrides);
@@ -2115,6 +2135,11 @@ public FileSystem newNIOFileSystem(java.nio.file.FileSystem fileSystem) {
21152135
throw noPolyglotImplementationFound();
21162136
}
21172137

2138+
@Override
2139+
public ByteSequence asByteSequence(Object object) {
2140+
throw noPolyglotImplementationFound();
2141+
}
2142+
21182143
@Override
21192144
public ProcessHandler newDefaultProcessHandler() {
21202145
throw noPolyglotImplementationFound();

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/HostAccess.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ public final class HostAccess {
301301
public enum MutableTargetMapping {
302302
/**
303303
* Enables default mapping of guest object arrays to host object {@link java.util.List}.
304+
* This mapping is also applied when the target host object is {@link java.util.Collection}.
304305
*/
305306
ARRAY_TO_JAVA_LIST,
306307
/**

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/Value.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,55 @@ public byte readBufferByte(long byteOffset) throws UnsupportedOperationException
464464
return dispatch.readBufferByte(this.context, receiver, byteOffset);
465465
}
466466

467+
/**
468+
* Reads bytes from the receiver object into the specified byte array.
469+
* <p>
470+
* The access is <em>not</em> guaranteed to be atomic. Therefore, this message is <em>not</em>
471+
* thread-safe.
472+
* <p>
473+
* Invoking this message does not cause any observable side-effects.
474+
* <p>
475+
* <b>Example</b> reading into an output stream using a 4k auxiliary byte array:
476+
*
477+
* <pre>
478+
* Value val = ...
479+
* assert val.hasBufferElements();
480+
* try (OutputStream out = ...) {
481+
* byte[] aux = new byte[4096];
482+
* long bufferSize = val.getBufferSize();
483+
* for (long offset = 0; offset < bufferSize; offset += aux.length) {
484+
* int bytesToRead = (int) Math.min(bufferSize - offset, aux.length);
485+
* val.readBuffer(offset, aux, 0, bytesToRead);
486+
* out.write(aux, 0, bytesToRead);
487+
* }
488+
* }
489+
* </pre>
490+
*
491+
* In case the goal is to read the whole contents into a single byte array, the easiest way is
492+
* to do that through {@link org.graalvm.polyglot.io.ByteSequence}:
493+
*
494+
* <pre>
495+
* byte[] byteArray = val.as(ByteSequence.class).toByteArray();
496+
* </pre>
497+
*
498+
* @param byteOffset offset in the buffer to start reading from.
499+
* @param destination byte array to write the read bytes into.
500+
* @param destinationOffset offset in the destination array to start writing from.
501+
* @param length number of bytes to read.
502+
* @throws IndexOutOfBoundsException if and only if
503+
* <code>byteOffset < 0 || length < 0 || byteOffset + length > </code>{@link #getBufferSize()}<code> || destinationOffset < 0 || destinationOffset + length > destination.length</code>
504+
* @throws UnsupportedOperationException if the value does not have {@link #hasBufferElements
505+
* buffer elements}.
506+
* @throws IllegalStateException if the context is already closed.
507+
* @throws PolyglotException if a guest language error occurred during execution.
508+
* @since 24.0
509+
*/
510+
public void readBuffer(long byteOffset, byte[] destination, int destinationOffset, int length) throws UnsupportedOperationException, IndexOutOfBoundsException {
511+
Objects.requireNonNull(destination, "destination");
512+
Objects.checkFromIndexSize(destinationOffset, length, destination.length);
513+
dispatch.readBuffer(this.context, receiver, byteOffset, destination, destinationOffset, length);
514+
}
515+
467516
/**
468517
* Writes the given byte at the given byte offset from the start of the buffer.
469518
* <p>

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/AbstractPolyglotImpl.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,16 @@ protected APIAccess() {
286286

287287
public abstract boolean isByteSequence(Object origin);
288288

289+
public abstract Class<?> getByteSequenceClass();
290+
289291
public abstract ByteSequence asByteSequence(Object origin);
290292

293+
public abstract Object toByteSequence(Object origin);
294+
295+
public abstract int byteSequenceLength(Object origin);
296+
297+
public abstract byte byteSequenceByteAt(Object origin, int index);
298+
291299
public abstract boolean isProxyArray(Object proxy);
292300

293301
public abstract boolean isProxyDate(Object proxy);
@@ -953,6 +961,8 @@ protected AbstractHostAccess(AbstractPolyglotImpl impl) {
953961

954962
public abstract <T> List<T> toList(Object internalContext, Object guestValue, boolean implementFunction, Class<T> elementClass, Type elementType);
955963

964+
public abstract Object toByteSequence(Object internalContext, Object guestValue);
965+
956966
public abstract <K, V> Map<K, V> toMap(Object internalContext, Object foreignObject, boolean implementsFunction, Class<K> keyClass, Type keyType, Class<V> valueClass, Type valueType);
957967

958968
public abstract <K, V> Map.Entry<K, V> toMapEntry(Object internalContext, Object foreignObject, boolean implementsFunction,
@@ -1112,6 +1122,8 @@ public boolean hasBufferElements(Object context, Object receiver) {
11121122

11131123
public abstract byte readBufferByte(Object context, Object receiver, long byteOffset);
11141124

1125+
public abstract void readBuffer(Object context, Object receiver, long byteOffset, byte[] destination, int destinationOffset, int length);
1126+
11151127
public abstract void writeBufferByte(Object context, Object receiver, long byteOffset, byte value);
11161128

11171129
public abstract short readBufferShort(Object context, Object receiver, ByteOrder order, long byteOffset);
@@ -1401,6 +1413,10 @@ public FileSystem newNIOFileSystem(java.nio.file.FileSystem fileSystem) {
14011413
return getNext().newNIOFileSystem(fileSystem);
14021414
}
14031415

1416+
public ByteSequence asByteSequence(Object object) {
1417+
return getNext().asByteSequence(object);
1418+
}
1419+
14041420
public ProcessHandler newDefaultProcessHandler() {
14051421
return getNext().newDefaultProcessHandler();
14061422
}

sdk/src/org.graalvm.polyglot/src/org/graalvm/polyglot/impl/UnnamedToModuleBridge.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,13 @@ static FileSystem fromFileSystem(Object value) {
418418
return new UnnamedToModuleFileSystemGen(value);
419419
}
420420

421+
static ByteSequence fromByteSequence(Object value) {
422+
if (value == null) {
423+
return null;
424+
}
425+
return new UnnamedToModuleByteSequenceGen(value);
426+
}
427+
421428
static ThreadScope fromThreadScope(Object value) {
422429
if (value == null) {
423430
return null;

0 commit comments

Comments
 (0)