Skip to content

Commit

Permalink
8308858: FFM API and strings
Browse files Browse the repository at this point in the history
Reviewed-by: jvernee
  • Loading branch information
mcimadamore committed Jun 14, 2023
1 parent a34b06a commit c5efc43
Show file tree
Hide file tree
Showing 14 changed files with 337 additions and 107 deletions.
22 changes: 11 additions & 11 deletions doc/panama_ffi.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ public class Examples {
);

try (Arena arena = Arena.ofConfined()) {
MemorySegment hello = arena.allocateUtf8String("Hello");
MemorySegment hello = arena.allocateString("Hello");
long len = (long) strlen.invokeExact(hello); // 5
System.out.println(len);
}
Expand All @@ -306,10 +306,10 @@ public class Examples {
);

try (Arena arena = Arena.ofConfined()) {
MemorySegment hello = arena.allocateUtf8String("Hello");
MemorySegment hello = arena.allocateString("Hello");
long len = (long) strlen_virtual.invokeExact(
STDLIB.find("strlen").get(),
hello); // 5
STDLIB.find("strlen").get(),
hello); // 5
System.out.println(len);
}
}
Expand All @@ -326,15 +326,15 @@ public class Examples {
FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS)
);
FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT,
ADDRESS.withTargetLayout(JAVA_INT),
ADDRESS.withTargetLayout(JAVA_INT));
ADDRESS.withTargetLayout(JAVA_INT),
ADDRESS.withTargetLayout(JAVA_INT));
MethodHandle comparHandle = MethodHandles.lookup()
.findStatic(Qsort.class, "qsortCompare",
comparDesc.toMethodType());
.findStatic(Qsort.class, "qsortCompare",
comparDesc.toMethodType());

try (Arena arena = Arena.ofConfined()) {
MemorySegment comparFunc = LINKER.upcallStub(
comparHandle, comparDesc, arena);
comparHandle, comparDesc, arena);

MemorySegment array = arena.allocateArray(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
qsort.invokeExact(array, 10L, 4L, comparFunc);
Expand All @@ -350,8 +350,8 @@ public class Examples {
Linker.Option.firstVariadicArg(1) // first int is variadic
);
try (Arena arena = Arena.ofConfined()) {
MemorySegment s = arena.allocateUtf8String("%d plus %d equals %d\n");
int res = (int)printf.invokeExact(s, 2, 2, 4);
MemorySegment s = arena.allocateString("%d plus %d equals %d\n");
int res = (int) printf.invokeExact(s, 2, 2, 4);
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/java.base/share/classes/java/lang/foreign/Linker.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@
*
* {@snippet lang = java:
* try (Arena arena = Arena.ofConfined()) {
* MemorySegment str = arena.allocateUtf8String("Hello");
* MemorySegment str = arena.allocateString("Hello");
* long len = (long) strlen.invokeExact(str); // 5
* }
* }
*}
* <h3 id="describing-c-sigs">Describing C signatures</h3>
*
* When interacting with the native linker, clients must provide a platform-dependent description of the signature
Expand Down Expand Up @@ -460,9 +460,9 @@
*
* {@snippet lang = java:
* try (Arena arena = Arena.ofConfined()) {
* int res = (int)printf.invokeExact(arena.allocateUtf8String("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
* }
* int res = (int)printf.invokeExact(arena.allocateString("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
* }
*}
*
* <h2 id="safety">Safety considerations</h2>
*
Expand Down
109 changes: 91 additions & 18 deletions src/java.base/share/classes/java/lang/foreign/MemorySegment.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
Expand All @@ -47,6 +48,7 @@
import jdk.internal.foreign.HeapMemorySegmentImpl;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.StringSupport;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.foreign.layout.ValueLayouts;
Expand Down Expand Up @@ -1069,52 +1071,123 @@ default long mismatch(MemorySegment other) {
double[] toArray(ValueLayout.OfDouble elementLayout);

/**
* Reads a UTF-8 encoded, null-terminated string from this segment at the given offset.
* Reads a null-terminated string from this segment at the given offset, using the
* {@linkplain StandardCharsets#UTF_8 UTF-8} charset.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
* Calling this method is equivalent to the following code:
* {@snippet lang = java:
* getString(offset, StandardCharsets.UTF_8);
*}
*
* @param offset offset in bytes (relative to this segment address) at which this access operation will occur.
* @return a Java string constructed from the bytes read from the given starting address up to (but not including)
* the first {@code '\0'} terminator character (assuming one is found).
* @throws IllegalArgumentException if the size of the UTF-8 string is greater than the largest string supported by the platform.
* @throws IndexOutOfBoundsException if {@code offset < 0} or {@code offset > byteSize() - S}, where {@code S} is the size of the UTF-8
* string (including the terminator character).
* @throws IllegalArgumentException if the size of the string is greater than the largest string supported by the platform.
* @throws IndexOutOfBoundsException if {@code offset < 0}.
* @throws IndexOutOfBoundsException if {@code offset} > byteSize() - (B + 1)}, where {@code B} is the size,
* in bytes, of the string encoded using UTF-8 charset {@code str.getBytes(StandardCharsets.UTF_8).length}).
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not
* {@linkplain Scope#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code isAccessibleBy(T) == false}.
*/
default String getUtf8String(long offset) {
return SharedUtils.toJavaStringInternal(this, offset);
default String getString(long offset) {
return getString(offset, StandardCharsets.UTF_8);
}

/**
* Writes the given string into this segment at the given offset, converting it to a null-terminated byte sequence using UTF-8 encoding.
* Reads a null-terminated string from this segment at the given offset, using the provided charset.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
*
* @param offset offset in bytes (relative to this segment address) at which this access operation will occur.
* @param charset the charset used to {@linkplain Charset#newDecoder() decode} the string bytes.
* @return a Java string constructed from the bytes read from the given starting address up to (but not including)
* the first {@code '\0'} terminator character (assuming one is found).
* @throws IllegalArgumentException if the size of the string is greater than the largest string supported by the platform.
* @throws IndexOutOfBoundsException if {@code offset < 0}.
* @throws IndexOutOfBoundsException if {@code offset} > byteSize() - (B + N)}, where:
* <ul>
* <li>{@code B} is the size, in bytes, of the string encoded using the provided charset
* (e.g. {@code str.getBytes(charset).length});</li>
* <li>{@code N} is the size (in bytes) of the terminator char according to the provided charset. For instance,
* this is 1 for {@link StandardCharsets#US_ASCII} and 2 for {@link StandardCharsets#UTF_16}.</li>
* </ul>
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not
* {@linkplain Scope#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code isAccessibleBy(T) == false}.
* @throws UnsupportedOperationException if {@code charset} is not a {@linkplain StandardCharsets standard charset}.
*/
default String getString(long offset, Charset charset) {
Objects.requireNonNull(charset);
return StringSupport.read(this, offset, charset);
}

/**
* Writes the given string into this segment at the given offset, converting it to a null-terminated byte sequence
* using the {@linkplain StandardCharsets#UTF_8 UTF-8} charset.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getUtf8String(long)}, the string
* will appear truncated when read again.
* Calling this method is equivalent to the following code:
* {@snippet lang = java:
* setString(offset, str, StandardCharsets.UTF_8);
*}
* @param offset offset in bytes (relative to this segment address) at which this access operation will occur.
* the final address of this write operation can be expressed as {@code address() + offset}.
* @param str the Java string to be written into this segment.
* @throws IndexOutOfBoundsException if {@code offset < 0} or {@code offset > byteSize() - str.getBytes().length() + 1}.
* @throws IndexOutOfBoundsException if {@code offset < 0}.
* @throws IndexOutOfBoundsException if {@code offset} > byteSize() - (B + 1}, where {@code B} is the size,
* in bytes, of the string encoded using UTF-8 charset {@code str.getBytes(StandardCharsets.UTF_8).length}).
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not
* {@linkplain Scope#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code isAccessibleBy(T) == false}.
*/
default void setUtf8String(long offset, String str) {
Utils.toCString(str.getBytes(StandardCharsets.UTF_8), SegmentAllocator.prefixAllocator(asSlice(offset)));
default void setString(long offset, String str) {
Objects.requireNonNull(str);
setString(offset, str, StandardCharsets.UTF_8);
}

/**
* Writes the given string into this segment at the given offset, converting it to a null-terminated byte sequence
* using the provided charset.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement string. The {@link
* java.nio.charset.CharsetDecoder} class should be used when more control
* over the decoding process is required.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again.
*
* @param offset offset in bytes (relative to this segment address) at which this access operation will occur.
* the final address of this write operation can be expressed as {@code address() + offset}.
* @param str the Java string to be written into this segment.
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the string bytes.
* @throws IndexOutOfBoundsException if {@code offset < 0}.
* @throws IndexOutOfBoundsException if {@code offset} > byteSize() - (B + N)}, where:
* <ul>
* <li>{@code B} is the size, in bytes, of the string encoded using the provided charset
* (e.g. {@code str.getBytes(charset).length});</li>
* <li>{@code N} is the size (in bytes) of the terminator char according to the provided charset. For instance,
* this is 1 for {@link StandardCharsets#US_ASCII} and 2 for {@link StandardCharsets#UTF_16}.</li>
* </ul>
* @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not
* {@linkplain Scope#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread {@code T},
* such that {@code isAccessibleBy(T) == false}.
* @throws UnsupportedOperationException if {@code charset} is not a {@linkplain StandardCharsets standard charset}.
*/
default void setString(long offset, String str, Charset charset) {
Objects.requireNonNull(charset);
Objects.requireNonNull(str);
StringSupport.write(this, offset, charset, str);
}

/**
* Creates a memory segment that is backed by the same region of memory that backs the given {@link Buffer} instance.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
import java.lang.invoke.VarHandle;
import java.lang.reflect.Array;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.function.Function;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.SlicingAllocator;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.StringSupport;
import jdk.internal.javac.PreviewFeature;
import jdk.internal.vm.annotation.ForceInline;

/**
* An object that may be used to allocate {@linkplain MemorySegment memory segments}. Clients implementing this interface
Expand Down Expand Up @@ -75,27 +77,59 @@
public interface SegmentAllocator {

/**
* Converts a Java string into a UTF-8 encoded, null-terminated C string,
* Converts a Java string into a null-terminated C string using the {@linkplain StandardCharsets#UTF_8 UTF-8} charset,
* storing the result into a memory segment.
* <p>
* Calling this method is equivalent to the following code:
* {@snippet lang = java:
* allocateString(str, StandardCharsets.UTF_8);
*}
*
* @param str the Java string to be converted into a C string.
* @return a new native segment containing the converted C string.
*/
@ForceInline
default MemorySegment allocateString(String str) {
Objects.requireNonNull(str);
return allocateString(str, StandardCharsets.UTF_8);
}

/**
* Converts a Java string into a null-terminated C string using the provided charset,
* and storing the result into a memory segment.
* <p>
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
* {@link java.nio.charset.CharsetEncoder} class should be used when more
* control over the encoding process is required.
* <p>
* If the given string contains any {@code '\0'} characters, they will be
* copied as well. This means that, depending on the method used to read
* the string, such as {@link MemorySegment#getUtf8String(long)}, the string
* the string, such as {@link MemorySegment#getString(long)}, the string
* will appear truncated when read again.
*
* @implSpec the default implementation for this method copies the contents of the provided Java string
* into a new memory segment obtained by calling {@code this.allocate(str.length() + 1)}.
* @param str the Java string to be converted into a C string.
* @param str the Java string to be converted into a C string.
* @param charset the charset used to {@linkplain Charset#newEncoder() encode} the string bytes.
* @return a new native segment containing the converted C string.
* @throws UnsupportedOperationException if {@code charset} is not a {@linkplain StandardCharsets standard charset}.
* @implSpec the default implementation for this method copies the contents of the provided Java string
* into a new memory segment obtained by calling {@code this.allocate(B + N)}, where:
* <ul>
* <li>{@code B} is the size, in bytes, of the string encoded using the provided charset
* (e.g. {@code str.getBytes(charset).length});</li>
* <li>{@code N} is the size (in bytes) of the terminator char according to the provided charset. For instance,
* this is 1 for {@link StandardCharsets#US_ASCII} and 2 for {@link StandardCharsets#UTF_16}.</li>
* </ul>
*/
default MemorySegment allocateUtf8String(String str) {
@ForceInline
default MemorySegment allocateString(String str, Charset charset) {
Objects.requireNonNull(charset);
Objects.requireNonNull(str);
return Utils.toCString(str.getBytes(StandardCharsets.UTF_8), this);
int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize();
byte[] bytes = str.getBytes(charset);
MemorySegment segment = allocate(bytes.length + termCharSize);
MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length);
return segment;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
* );
*
* try (Arena arena = Arena.ofConfined()) {
* MemorySegment cString = arena.allocateUtf8String("Hello");
* MemorySegment cString = arena.allocateString("Hello");
* long len = (long)strlen.invokeExact(cString); // 5
* }
*}
Expand All @@ -110,7 +110,7 @@
* into a foreign function call, according to the rules specified by the ABI of the underlying platform.
* The {@link java.lang.foreign.Arena} class also provides many useful methods for
* interacting with foreign code, such as
* {@linkplain java.lang.foreign.SegmentAllocator#allocateUtf8String(java.lang.String) converting} Java strings into
* {@linkplain java.lang.foreign.SegmentAllocator#allocateString(java.lang.String) converting} Java strings into
* zero-terminated, UTF-8 strings, as demonstrated in the above example.
*
* <h2 id="restricted">Restricted methods</h2>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ void downcall() throws Throwable {
);

try (Arena arena = Arena.ofConfined()) {
MemorySegment str = arena.allocateUtf8String("Hello");
MemorySegment str = arena.allocateString("Hello");
long len = (long) strlen.invokeExact(str); // 5
}

Expand Down Expand Up @@ -287,7 +287,7 @@ void variadicFunc() throws Throwable {
);

try (Arena arena = Arena.ofConfined()) {
int res = (int) printf.invokeExact(arena.allocateUtf8String("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
int res = (int) printf.invokeExact(arena.allocateString("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
}

}
Expand Down Expand Up @@ -569,7 +569,7 @@ void header() throws Throwable {
);

try (Arena arena = Arena.ofConfined()) {
MemorySegment cString = arena.allocateUtf8String("Hello");
MemorySegment cString = arena.allocateString("Hello");
long len = (long) strlen.invokeExact(cString); // 5
}

Expand Down
Loading

0 comments on commit c5efc43

Please sign in to comment.