8317837: Leftover FFM implementation-only changes

Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org>
Co-authored-by: Per Minborg <pminborg@openjdk.org>
Reviewed-by: mcimadamore
This commit is contained in:
Jorn Vernee 2023-10-13 19:05:47 +00:00
parent 605c976729
commit b12c471a99
22 changed files with 1432 additions and 115 deletions

@ -108,6 +108,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \
--add-exports java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED \
--add-exports java.base/jdk.internal.vm=ALL-UNNAMED \
--add-exports java.base/jdk.internal.event=ALL-UNNAMED \
--add-exports java.base/jdk.internal.foreign=ALL-UNNAMED \
--enable-preview \
-processor org.openjdk.jmh.generators.BenchmarkProcessor, \
JAVA_FLAGS := --add-modules jdk.unsupported --limit-modules java.management \

@ -28,6 +28,8 @@ package java.lang;
import java.io.ObjectStreamField;
import java.io.UnsupportedEncodingException;
import java.lang.annotation.Native;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandles;
import java.lang.constant.Constable;
import java.lang.constant.ConstantDesc;
@ -1836,6 +1838,21 @@ public final class String
return encode(Charset.defaultCharset(), coder(), value);
}
boolean bytesCompatible(Charset charset) {
if (isLatin1()) {
if (charset == ISO_8859_1.INSTANCE) {
return true; // ok, same encoding
} else if (charset == UTF_8.INSTANCE || charset == US_ASCII.INSTANCE) {
return !StringCoding.hasNegatives(value, 0, value.length); // ok, if ASCII-compatible
}
}
return false;
}
void copyToSegmentRaw(MemorySegment segment, long offset) {
MemorySegment.copy(value, 0, segment, ValueLayout.JAVA_BYTE, offset, value.length);
}
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code

@ -35,6 +35,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.invoke.StringConcatFactory;
@ -88,7 +89,6 @@ import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationScope;
import jdk.internal.vm.StackableScope;
import jdk.internal.vm.ThreadContainer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
import jdk.internal.vm.annotation.Stable;
import sun.nio.fs.DefaultFileSystemProvider;
@ -2669,6 +2669,16 @@ public final class System {
public String getLoaderNameID(ClassLoader loader) {
return loader.nameAndId();
}
@Override
public void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
string.copyToSegmentRaw(segment, offset);
}
@Override
public boolean bytesCompatible(String string, Charset charset) {
return string.bytesCompatible(charset);
}
});
}
}

@ -1076,7 +1076,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
* such that {@code isAccessibleBy(T) == false}.
*/
default String getString(long offset) {
return getString(offset, StandardCharsets.UTF_8);
return getString(offset, sun.nio.cs.UTF_8.INSTANCE);
}
/**
@ -1132,7 +1132,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl {
*/
default void setString(long offset, String str) {
Objects.requireNonNull(str);
setString(offset, str, StandardCharsets.UTF_8);
setString(offset, str, sun.nio.cs.UTF_8.INSTANCE);
}
/**

@ -89,7 +89,7 @@ public interface SegmentAllocator {
@ForceInline
default MemorySegment allocateFrom(String str) {
Objects.requireNonNull(str);
return allocateFrom(str, StandardCharsets.UTF_8);
return allocateFrom(str, sun.nio.cs.UTF_8.INSTANCE);
}
/**
@ -124,11 +124,20 @@ public interface SegmentAllocator {
Objects.requireNonNull(charset);
Objects.requireNonNull(str);
int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize();
byte[] bytes = str.getBytes(charset);
MemorySegment segment = allocateNoInit(bytes.length + termCharSize);
MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length);
MemorySegment segment;
int length;
if (StringSupport.bytesCompatible(str, charset)) {
length = str.length();
segment = allocateNoInit((long) length + termCharSize);
StringSupport.copyToSegmentRaw(str, segment, 0);
} else {
byte[] bytes = str.getBytes(charset);
length = bytes.length;
segment = allocateNoInit((long) bytes.length + termCharSize);
MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length);
}
for (int i = 0 ; i < termCharSize ; i++) {
segment.set(ValueLayout.JAVA_BYTE, bytes.length + i, (byte)0);
segment.set(ValueLayout.JAVA_BYTE, length + i, (byte)0);
}
return segment;
}

@ -25,6 +25,8 @@
package java.lang.invoke;
import jdk.internal.foreign.Utils;
/**
* Base class for memory segment var handle view implementations.
*/
@ -54,7 +56,7 @@ abstract sealed class VarHandleSegmentViewBase extends VarHandle permits
}
static IllegalArgumentException newIllegalArgumentExceptionForMisalignedAccess(long address) {
return new IllegalArgumentException("Misaligned access at address: " + address);
return new IllegalArgumentException("Misaligned access at address: " + Utils.toHexString(address));
}
static UnsupportedOperationException newUnsupportedAccessModeForAlignment(long alignment) {

@ -27,6 +27,7 @@ package jdk.internal.access;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.module.ModuleDescriptor;
@ -574,4 +575,14 @@ public interface JavaLangAccess {
* explicitly set otherwise <qualified-class-name> @<id>
*/
String getLoaderNameID(ClassLoader loader);
/**
* Copy the string bytes to an existing segment, avoiding intermediate copies.
*/
void copyToSegmentRaw(String string, MemorySegment segment, long offset);
/**
* Are the string bytes compatible with the given charset?
*/
boolean bytesCompatible(String string, Charset charset);
}

@ -26,6 +26,7 @@
package jdk.internal.foreign;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import java.lang.foreign.AddressLayout;
import java.lang.foreign.GroupLayout;
@ -40,9 +41,13 @@ import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.util.stream.Collectors.joining;
/**
* This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout)},
@ -89,7 +94,6 @@ public class LayoutPath {
private final long offset;
private final LayoutPath enclosing;
private final long[] strides;
private final long[] bounds;
private final MethodHandle[] derefAdapters;
@ -105,15 +109,13 @@ public class LayoutPath {
// Layout path selector methods
public LayoutPath sequenceElement() {
check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
SequenceLayout seq = (SequenceLayout)layout;
SequenceLayout seq = requireSequenceLayout();
MemoryLayout elem = seq.elementLayout();
return LayoutPath.nestedPath(elem, offset, addStride(elem.byteSize()), addBound(seq.elementCount()), derefAdapters, this);
}
public LayoutPath sequenceElement(long start, long step) {
check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
SequenceLayout seq = (SequenceLayout)layout;
SequenceLayout seq = requireSequenceLayout();
checkSequenceBounds(seq, start);
MemoryLayout elem = seq.elementLayout();
long elemSize = elem.byteSize();
@ -122,21 +124,19 @@ public class LayoutPath {
start + 1;
long maxIndex = Math.ceilDiv(nelems, Math.abs(step));
return LayoutPath.nestedPath(elem, offset + (start * elemSize),
addStride(elemSize * step), addBound(maxIndex), derefAdapters, this);
addStride(elemSize * step), addBound(maxIndex), derefAdapters, this);
}
public LayoutPath sequenceElement(long index) {
check(SequenceLayout.class, "attempting to select a sequence element from a non-sequence layout");
SequenceLayout seq = (SequenceLayout)layout;
SequenceLayout seq = requireSequenceLayout();
checkSequenceBounds(seq, index);
long elemSize = seq.elementLayout().byteSize();
long elemOffset = elemSize * index;
return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters,this);
return LayoutPath.nestedPath(seq.elementLayout(), offset + elemOffset, strides, bounds, derefAdapters, this);
}
public LayoutPath groupElement(String name) {
check(GroupLayout.class, "attempting to select a group element from a non-group layout");
GroupLayout g = (GroupLayout)layout;
GroupLayout g = requireGroupLayout();
long offset = 0;
MemoryLayout elem = null;
for (int i = 0; i < g.memberLayouts().size(); i++) {
@ -150,20 +150,21 @@ public class LayoutPath {
}
}
if (elem == null) {
throw badLayoutPath("cannot resolve '" + name + "' in layout " + layout);
throw badLayoutPath(
String.format("cannot resolve '%s' in layout %s", name, breadcrumbs()));
}
return LayoutPath.nestedPath(elem, this.offset + offset, strides, bounds, derefAdapters, this);
}
public LayoutPath groupElement(long index) {
check(GroupLayout.class, "attempting to select a group element from a non-group layout");
GroupLayout g = (GroupLayout)layout;
GroupLayout g = requireGroupLayout();
long elemSize = g.memberLayouts().size();
long offset = 0;
MemoryLayout elem = null;
for (int i = 0; i <= index; i++) {
if (i == elemSize) {
throw badLayoutPath("cannot resolve element " + index + " in layout " + layout);
throw badLayoutPath(
String.format("cannot resolve element %d in layout: %s", index, breadcrumbs()));
}
elem = g.memberLayouts().get(i);
if (g instanceof StructLayout && i < index) {
@ -176,7 +177,8 @@ public class LayoutPath {
public LayoutPath derefElement() {
if (!(layout instanceof AddressLayout addressLayout) ||
addressLayout.targetLayout().isEmpty()) {
throw badLayoutPath("Cannot dereference layout: " + layout);
throw badLayoutPath(
String.format("Cannot dereference layout: %s", breadcrumbs()));
}
MemoryLayout derefLayout = addressLayout.targetLayout().get();
MethodHandle handle = dereferenceHandle(false).toMethodHandle(VarHandle.AccessMode.GET);
@ -201,7 +203,8 @@ public class LayoutPath {
public VarHandle dereferenceHandle(boolean adapt) {
if (!(layout instanceof ValueLayout valueLayout)) {
throw new IllegalArgumentException("Path does not select a value layout");
throw new IllegalArgumentException(
String.format("Path does not select a value layout: %s", breadcrumbs()));
}
// If we have an enclosing layout, drop the alignment check for the accessed element,
@ -288,7 +291,9 @@ public class LayoutPath {
private static void checkAlign(MemorySegment segment, long offset, MemoryLayout constraint) {
if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(offset, constraint)) {
throw new IllegalArgumentException("Target offset incompatible with alignment constraints: " + constraint.byteAlignment());
throw new IllegalArgumentException(String.format(
"Target offset %d is incompatible with alignment constraint %d (of %s) for segment %s"
, offset, constraint.byteAlignment(), constraint, segment));
}
}
@ -314,15 +319,27 @@ public class LayoutPath {
// Helper methods
private void check(Class<?> layoutClass, String msg) {
private SequenceLayout requireSequenceLayout() {
return requireLayoutType(SequenceLayout.class, "sequence");
}
private GroupLayout requireGroupLayout() {
return requireLayoutType(GroupLayout.class, "group");
}
private <T extends MemoryLayout> T requireLayoutType(Class<T> layoutClass, String name) {
if (!layoutClass.isAssignableFrom(layout.getClass())) {
throw badLayoutPath(msg);
throw badLayoutPath(
String.format("attempting to select a %s element from a non-%s layout: %s",
name, name, breadcrumbs()));
}
return layoutClass.cast(layout);
}
private void checkSequenceBounds(SequenceLayout seq, long index) {
if (index >= seq.elementCount()) {
throw badLayoutPath(String.format("Sequence index out of bound; found: %d, size: %d", index, seq.elementCount()));
throw badLayoutPath(String.format("sequence index out of bounds; index: %d, elementCount is %d for layout %s",
index, seq.elementCount(), breadcrumbs()));
}
}
@ -342,6 +359,13 @@ public class LayoutPath {
return newBounds;
}
private String breadcrumbs() {
return Stream.iterate(this, Objects::nonNull, lp -> lp.enclosing)
.map(LayoutPath::layout)
.map(Object::toString)
.collect(joining(", selected from: "));
}
/**
* This class provides an immutable implementation for the {@code PathElement} interface. A path element implementation
* is simply a pointer to one of the selector methods provided by the {@code LayoutPath} class.

@ -25,103 +25,250 @@
package jdk.internal.foreign;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.util.ArraysSupport;
import sun.security.action.GetPropertyAction;
import java.lang.foreign.MemorySegment;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_SHORT;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.*;
/**
* Miscellaneous functions to read and write strings, in various charsets.
*/
public class StringSupport {
static final JavaLangAccess JAVA_LANG_ACCESS = SharedSecrets.getJavaLangAccess();
private StringSupport() {}
public static String read(MemorySegment segment, long offset, Charset charset) {
return switch (CharsetKind.of(charset)) {
case SINGLE_BYTE -> readFast_byte(segment, offset, charset);
case DOUBLE_BYTE -> readFast_short(segment, offset, charset);
case QUAD_BYTE -> readFast_int(segment, offset, charset);
case SINGLE_BYTE -> readByte(segment, offset, charset);
case DOUBLE_BYTE -> readShort(segment, offset, charset);
case QUAD_BYTE -> readInt(segment, offset, charset);
};
}
public static void write(MemorySegment segment, long offset, Charset charset, String string) {
switch (CharsetKind.of(charset)) {
case SINGLE_BYTE -> writeFast_byte(segment, offset, charset, string);
case DOUBLE_BYTE -> writeFast_short(segment, offset, charset, string);
case QUAD_BYTE -> writeFast_int(segment, offset, charset, string);
case SINGLE_BYTE -> writeByte(segment, offset, charset, string);
case DOUBLE_BYTE -> writeShort(segment, offset, charset, string);
case QUAD_BYTE -> writeInt(segment, offset, charset, string);
}
}
private static String readFast_byte(MemorySegment segment, long offset, Charset charset) {
long len = strlen_byte(segment, offset);
private static String readByte(MemorySegment segment, long offset, Charset charset) {
long len = chunkedStrlenByte(segment, offset);
byte[] bytes = new byte[(int)len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int)len);
return new String(bytes, charset);
}
private static void writeFast_byte(MemorySegment segment, long offset, Charset charset, String string) {
byte[] bytes = string.getBytes(charset);
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
segment.set(JAVA_BYTE, offset + bytes.length, (byte)0);
private static void writeByte(MemorySegment segment, long offset, Charset charset, String string) {
int bytes = copyBytes(string, segment, charset, offset);
segment.set(JAVA_BYTE, offset + bytes, (byte)0);
}
private static String readFast_short(MemorySegment segment, long offset, Charset charset) {
long len = strlen_short(segment, offset);
private static String readShort(MemorySegment segment, long offset, Charset charset) {
long len = chunkedStrlenShort(segment, offset);
byte[] bytes = new byte[(int)len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int)len);
return new String(bytes, charset);
}
private static void writeFast_short(MemorySegment segment, long offset, Charset charset, String string) {
byte[] bytes = string.getBytes(charset);
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
segment.set(JAVA_SHORT, offset + bytes.length, (short)0);
private static void writeShort(MemorySegment segment, long offset, Charset charset, String string) {
int bytes = copyBytes(string, segment, charset, offset);
segment.set(JAVA_SHORT, offset + bytes, (short)0);
}
private static String readFast_int(MemorySegment segment, long offset, Charset charset) {
long len = strlen_int(segment, offset);
private static String readInt(MemorySegment segment, long offset, Charset charset) {
long len = strlenInt(segment, offset);
byte[] bytes = new byte[(int)len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int)len);
return new String(bytes, charset);
}
private static void writeFast_int(MemorySegment segment, long offset, Charset charset, String string) {
byte[] bytes = string.getBytes(charset);
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
segment.set(JAVA_INT, offset + bytes.length, 0);
private static void writeInt(MemorySegment segment, long offset, Charset charset, String string) {
int bytes = copyBytes(string, segment, charset, offset);
segment.set(JAVA_INT, offset + bytes, 0);
}
private static int strlen_byte(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset++) {
/**
* {@return the shortest distance beginning at the provided {@code start}
* to the encountering of a zero byte in the provided {@code segment}}
* <p>
* The method divides the region of interest into three distinct regions:
* <ul>
* <li>head (access made on a byte-by-byte basis) (if any)</li>
* <li>body (access made with eight bytes at a time at physically 64-bit-aligned memory) (if any)</li>
* <li>tail (access made on a byte-by-byte basis) (if any)</li>
* </ul>
* <p>
* The body is using a heuristic method to determine if a long word
* contains a zero byte. The method might have false positives but
* never false negatives.
* <p>
* This method is inspired by the `glibc/string/strlen.c` implementation
*
* @param segment to examine
* @param start from where examination shall begin
* @throws IllegalArgumentException if the examined region contains no zero bytes
* within a length that can be accepted by a String
*/
public static int chunkedStrlenByte(MemorySegment segment, long start) {
// Handle the first unaligned "head" bytes separately
int headCount = (int)SharedUtils.remainsToAlignment(segment.address() + start, Long.BYTES);
int offset = 0;
for (; offset < headCount; offset++) {
byte curr = segment.get(JAVA_BYTE, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
// We are now on a long-aligned boundary so this is the "body"
int bodyCount = bodyCount(segment.byteSize() - start - headCount);
for (; offset < bodyCount; offset += Long.BYTES) {
// We know we are `long` aligned so, we can save on alignment checking here
long curr = segment.get(JAVA_LONG_UNALIGNED, start + offset);
// Is this a candidate?
if (mightContainZeroByte(curr)) {
for (int j = 0; j < 8; j++) {
if (segment.get(JAVA_BYTE, start + offset + j) == 0) {
return offset + j;
}
}
}
}
// Handle the "tail"
return requireWithinArraySize((long) offset + strlenByte(segment, start + offset));
}
private static int strlen_short(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset += 2) {
/* Bits 63 and N * 8 (N = 1..7) of this number are zero. Call these bits
the "holes". Note that there is a hole just to the left of
each byte, with an extra at the end:
bits: 01111110 11111110 11111110 11111110 11111110 11111110 11111110 11111111
bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG HHHHHHHH
The 1-bits make sure that carries propagate to the next 0-bit.
The 0-bits provide holes for carries to fall into.
*/
private static final long HIMAGIC_FOR_BYTES = 0x8080_8080_8080_8080L;
private static final long LOMAGIC_FOR_BYTES = 0x0101_0101_0101_0101L;
static boolean mightContainZeroByte(long l) {
return ((l - LOMAGIC_FOR_BYTES) & (~l) & HIMAGIC_FOR_BYTES) != 0;
}
private static final long HIMAGIC_FOR_SHORTS = 0x8000_8000_8000_8000L;
private static final long LOMAGIC_FOR_SHORTS = 0x0001_0001_0001_0001L;
static boolean mightContainZeroShort(long l) {
return ((l - LOMAGIC_FOR_SHORTS) & (~l) & HIMAGIC_FOR_SHORTS) != 0;
}
static int requireWithinArraySize(long size) {
if (size > ArraysSupport.SOFT_MAX_ARRAY_LENGTH) {
throw newIaeStringTooLarge();
}
return (int) size;
}
static int bodyCount(long remaining) {
return (int) Math.min(
// Make sure we do not wrap around
Integer.MAX_VALUE - Long.BYTES,
// Remaining bytes to consider
remaining)
& -Long.BYTES; // Mask 0xFFFFFFF8
}
private static int strlenByte(MemorySegment segment, long start) {
for (int offset = 0; offset < ArraysSupport.SOFT_MAX_ARRAY_LENGTH; offset += 1) {
byte curr = segment.get(JAVA_BYTE, start + offset);
if (curr == 0) {
return offset;
}
}
throw newIaeStringTooLarge();
}
/**
* {@return the shortest distance beginning at the provided {@code start}
* to the encountering of a zero short in the provided {@code segment}}
* <p>
* Note: The inspected region must be short aligned.
*
* @see #chunkedStrlenByte(MemorySegment, long) for more information
*
* @param segment to examine
* @param start from where examination shall begin
* @throws IllegalArgumentException if the examined region contains no zero shorts
* within a length that can be accepted by a String
*/
public static int chunkedStrlenShort(MemorySegment segment, long start) {
// Handle the first unaligned "head" bytes separately
int headCount = (int)SharedUtils.remainsToAlignment(segment.address() + start, Long.BYTES);
int offset = 0;
for (; offset < headCount; offset += Short.BYTES) {
short curr = segment.get(JAVA_SHORT, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
// We are now on a long-aligned boundary so this is the "body"
int bodyCount = bodyCount(segment.byteSize() - start - headCount);
for (; offset < bodyCount; offset += Long.BYTES) {
// We know we are `long` aligned so, we can save on alignment checking here
long curr = segment.get(JAVA_LONG_UNALIGNED, start + offset);
// Is this a candidate?
if (mightContainZeroShort(curr)) {
for (int j = 0; j < Long.BYTES; j += Short.BYTES) {
if (segment.get(JAVA_SHORT_UNALIGNED, start + offset + j) == 0) {
return offset + j;
}
}
}
}
// Handle the "tail"
return requireWithinArraySize((long) offset + strlenShort(segment, start + offset));
}
private static int strlen_int(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset += 4) {
int curr = segment.get(JAVA_INT, start + offset);
private static int strlenShort(MemorySegment segment, long start) {
for (int offset = 0; offset < ArraysSupport.SOFT_MAX_ARRAY_LENGTH; offset += Short.BYTES) {
short curr = segment.get(JAVA_SHORT_UNALIGNED, start + offset);
if (curr == (short)0) {
return offset;
}
}
throw newIaeStringTooLarge();
}
// The gain of using `long` wide operations for `int` is lower than for the two other `byte` and `short` variants
// so, there is only one method for ints.
public static int strlenInt(MemorySegment segment, long start) {
for (int offset = 0; offset < ArraysSupport.SOFT_MAX_ARRAY_LENGTH; offset += Integer.BYTES) {
// We are guaranteed to be aligned here so, we can use unaligned access.
int curr = segment.get(JAVA_INT_UNALIGNED, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
throw newIaeStringTooLarge();
}
public enum CharsetKind {
@ -140,15 +287,46 @@ public class StringSupport {
}
public static CharsetKind of(Charset charset) {
if (charset == StandardCharsets.UTF_8 || charset == StandardCharsets.ISO_8859_1 || charset == StandardCharsets.US_ASCII) {
return CharsetKind.SINGLE_BYTE;
} else if (charset == StandardCharsets.UTF_16LE || charset == StandardCharsets.UTF_16BE || charset == StandardCharsets.UTF_16) {
return CharsetKind.DOUBLE_BYTE;
} else if (charset == StandardCharsets.UTF_32LE || charset == StandardCharsets.UTF_32BE || charset == StandardCharsets.UTF_32) {
return CharsetKind.QUAD_BYTE;
// Comparing the charset to specific internal implementations avoids loading the class `StandardCharsets`
if (charset == sun.nio.cs.UTF_8.INSTANCE ||
charset == sun.nio.cs.ISO_8859_1.INSTANCE ||
charset == sun.nio.cs.US_ASCII.INSTANCE) {
return SINGLE_BYTE;
} else if (charset instanceof sun.nio.cs.UTF_16LE ||
charset instanceof sun.nio.cs.UTF_16BE ||
charset instanceof sun.nio.cs.UTF_16) {
return DOUBLE_BYTE;
} else if (charset instanceof sun.nio.cs.UTF_32LE ||
charset instanceof sun.nio.cs.UTF_32BE ||
charset instanceof sun.nio.cs.UTF_32) {
return QUAD_BYTE;
} else {
throw new IllegalArgumentException("Unsupported charset: " + charset);
}
}
}
public static boolean bytesCompatible(String string, Charset charset) {
return JAVA_LANG_ACCESS.bytesCompatible(string, charset);
}
public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset) {
if (bytesCompatible(string, charset)) {
copyToSegmentRaw(string, segment, offset);
return string.length();
} else {
byte[] bytes = string.getBytes(charset);
MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length);
return bytes.length;
}
}
public static void copyToSegmentRaw(String string, MemorySegment segment, long offset) {
JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset);
}
private static IllegalArgumentException newIaeStringTooLarge() {
return new IllegalArgumentException("String too large");
}
}

@ -146,7 +146,7 @@ public final class Utils {
@ForceInline
public static MemorySegment longToAddress(long addr, long size, long align) {
if (!isAligned(addr, align)) {
throw new IllegalArgumentException("Invalid alignment constraint for address: " + addr);
throw new IllegalArgumentException("Invalid alignment constraint for address: " + toHexString(addr));
}
return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(addr, size);
}
@ -154,7 +154,7 @@ public final class Utils {
@ForceInline
public static MemorySegment longToAddress(long addr, long size, long align, MemorySessionImpl scope) {
if (!isAligned(addr, align)) {
throw new IllegalArgumentException("Invalid alignment constraint for address: " + addr);
throw new IllegalArgumentException("Invalid alignment constraint for address: " + toHexString(addr));
}
return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(addr, size, scope);
}

@ -54,10 +54,8 @@ import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.lang.ref.Reference;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@ -129,6 +127,10 @@ public final class SharedUtils {
return ((addr - 1) | (alignment - 1)) + 1;
}
public static long remainsToAlignment(long addr, long alignment) {
return alignUp(addr, alignment) - addr;
}
/**
* Takes a MethodHandle that takes an input buffer as a first argument (a MemorySegment), and returns nothing,
* and adapts it to return a MemorySegment, by allocating a MemorySegment for the input

@ -28,6 +28,7 @@ import java.lang.foreign.MemorySegment;
import java.lang.foreign.Arena;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.Utils;
public final class UpcallStubs {
@ -36,7 +37,7 @@ public final class UpcallStubs {
private static void freeUpcallStub(long stubAddress) {
if (!freeUpcallStub0(stubAddress)) {
throw new IllegalStateException("Not a stub address: " + stubAddress);
throw new IllegalStateException("Not a stub address: " + Utils.toHexString(stubAddress));
}
}

@ -372,7 +372,7 @@ public final class ValueLayouts {
* <li>{@link ValueLayout.OfFloat}, for {@code float.class}</li>
* <li>{@link ValueLayout.OfLong}, for {@code long.class}</li>
* <li>{@link ValueLayout.OfDouble}, for {@code double.class}</li>
* <li>{@link ValueLayout.OfAddress}, for {@code MemorySegment.class}</li>
* <li>{@link AddressLayout}, for {@code MemorySegment.class}</li>
* </ul>
* @param carrier the value layout carrier.
* @param order the value layout's byte order.

@ -34,6 +34,7 @@ import org.testng.annotations.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;
import java.util.function.IntFunction;
@ -136,7 +137,7 @@ public class TestLayoutPaths {
}
@Test
public void testBadAlignmentOfRoot() throws Throwable {
public void testBadAlignmentOfRoot() {
MemoryLayout struct = MemoryLayout.structLayout(
JAVA_INT,
JAVA_SHORT.withName("x"));
@ -147,22 +148,58 @@ public class TestLayoutPaths {
assertEquals(seg.address() % JAVA_SHORT.byteAlignment(), 0); // should be aligned
assertNotEquals(seg.address() % struct.byteAlignment(), 0); // should not be aligned
String expectedMessage = "Target offset incompatible with alignment constraints: " + struct.byteAlignment();
String expectedMessage = "Target offset 0 is incompatible with alignment constraint " + struct.byteAlignment() + " (of [i4s2(x)]) for segment MemorySegment";
VarHandle vhX = struct.varHandle(groupElement("x"));
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> {
vhX.set(seg, 0L, (short) 42);
});
assertEquals(iae.getMessage(), expectedMessage);
assertTrue(iae.getMessage().startsWith(expectedMessage));
MethodHandle sliceX = struct.sliceHandle(groupElement("x"));
iae = expectThrows(IllegalArgumentException.class, () -> {
MemorySegment slice = (MemorySegment) sliceX.invokeExact(seg, 0L);
});
assertEquals(iae.getMessage(), expectedMessage);
assertTrue(iae.getMessage().startsWith(expectedMessage));
}
}
@Test
public void testWrongTypeRoot() {
MemoryLayout struct = MemoryLayout.structLayout(
JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN),
JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN)
);
var expectedMessage = "Bad layout path: attempting to select a sequence element from a non-sequence layout: [i4i4]";
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
struct.select(PathElement.sequenceElement()));
assertEquals(iae.getMessage(), expectedMessage);
}
@Test
public void testWrongTypeEnclosing() {
MemoryLayout struct = MemoryLayout.structLayout(
MemoryLayout.sequenceLayout(2, MemoryLayout.structLayout(
JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN).withName("3a"),
JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN).withName("3b")
).withName("2")
).withName("1")
).withName("0");
var expectedMessage = "Bad layout path: attempting to select a sequence element from a non-sequence layout: " +
"[i4(3a)i4(3b)](2), selected from: " +
"[2:[i4(3a)i4(3b)](2)](1), selected from: " +
"[[2:[i4(3a)i4(3b)](2)](1)](0)";
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () ->
struct.select(PathElement.groupElement("1"),
PathElement.sequenceElement(),
PathElement.sequenceElement()));
assertEquals(iae.getMessage(), expectedMessage);
}
@Test
public void testBadSequencePathInOffset() {
SequenceLayout seq = MemoryLayout.sequenceLayout(10, JAVA_INT);

@ -22,46 +22,69 @@
*
*/
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import jdk.internal.foreign.StringSupport;
import org.testng.annotations.*;
import static java.lang.foreign.ValueLayout.*;
import static org.testng.Assert.*;
/*
* @test
* @modules java.base/jdk.internal.foreign
* @run testng TestStringEncoding
*/
public class TestStringEncoding {
@Test(dataProvider = "strings")
public void testStrings(String testString) throws ReflectiveOperationException {
public void testStrings(String testString) {
for (Charset charset : Charset.availableCharsets().values()) {
if (isStandard(charset)) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment text = arena.allocateFrom(testString, charset);
for (Arena arena : arenas()) {
try (arena) {
MemorySegment text = arena.allocateFrom(testString, charset);
int terminatorSize = "\0".getBytes(charset).length;
if (charset == StandardCharsets.UTF_16) {
terminatorSize -= 2; // drop BOM
}
// Note that the JDK's UTF_32 encoder doesn't add a BOM.
// This is legal under the Unicode standard, and means the byte order is BE.
// See: https://unicode.org/faq/utf_bom.html#gen7
int terminatorSize = "\0".getBytes(charset).length;
if (charset == StandardCharsets.UTF_16) {
terminatorSize -= 2; // drop BOM
}
// Note that the JDK's UTF_32 encoder doesn't add a BOM.
// This is legal under the Unicode standard, and means the byte order is BE.
// See: https://unicode.org/faq/utf_bom.html#gen7
int expectedByteLength =
testString.getBytes(charset).length +
terminatorSize;
int expectedByteLength =
testString.getBytes(charset).length +
terminatorSize;
assertEquals(text.byteSize(), expectedByteLength);
assertEquals(text.byteSize(), expectedByteLength);
String roundTrip = text.getString(0, charset);
if (charset.newEncoder().canEncode(testString)) {
assertEquals(roundTrip, testString);
String roundTrip = text.getString(0, charset);
if (charset.newEncoder().canEncode(testString)) {
assertEquals(roundTrip, testString);
}
}
}
} else {
@ -70,25 +93,367 @@ public class TestStringEncoding {
}
}
@Test(dataProvider = "strings")
public void testStringsHeap(String testString) {
for (Charset charset : singleByteCharsets()) {
for (var arena : arenas()) {
try (arena) {
MemorySegment text = arena.allocateFrom(testString, charset);
text = toHeapSegment(text);
int expectedByteLength =
testString.getBytes(charset).length + 1;
assertEquals(text.byteSize(), expectedByteLength);
String roundTrip = text.getString(0, charset);
if (charset.newEncoder().canEncode(testString)) {
assertEquals(roundTrip, testString);
}
}
}
}
}
MemorySegment toHeapSegment(MemorySegment segment) {
var heapArray = segment.toArray(JAVA_BYTE);
return MemorySegment.ofArray(heapArray);
}
@Test(dataProvider = "strings")
public void unboundedSegment(String testString) {
testModifyingSegment(testString,
standardCharsets(),
s -> s.reinterpret(Long.MAX_VALUE),
UnaryOperator.identity());
}
@Test(dataProvider = "strings")
public void unalignedSegmentSingleByte(String testString) {
testModifyingSegment(testString,
singleByteCharsets(),
s -> s.byteSize() > 1 ? s.asSlice(1) : s,
s -> s.length() > 0 ? s.substring(1) : s);
}
@Test(dataProvider = "strings")
public void expandedSegment(String testString) {
try (var arena = Arena.ofConfined()) {
for (int i = 0; i < Long.BYTES; i++) {
int extra = i;
testModifyingSegment(testString,
// Single byte charsets
standardCharsets(),
s -> {
var s2 = arena.allocate(s.byteSize() + extra);
MemorySegment.copy(s, 0, s2, 0, s.byteSize());
return s2;
},
UnaryOperator.identity());
}
}
}
public void testModifyingSegment(String testString,
List<Charset> charsets,
UnaryOperator<MemorySegment> segmentMapper,
UnaryOperator<String> stringMapper) {
for (var charset : charsets) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment text = arena.allocateFrom(testString, charset);
text = segmentMapper.apply(text);
String roundTrip = text.getString(0, charset);
String expected = stringMapper.apply(testString);
if (charset.newEncoder().canEncode(testString)) {
assertEquals(roundTrip, expected);
}
}
}
}
@Test()
public void testPeculiarContentSingleByte() {
Random random = new Random(42);
for (int len = 7; len < 71; len++) {
for (var arena : arenas()) {
try (arena) {
var segment = arena.allocate(len, 1);
var arr = new byte[len];
random.nextBytes(arr);
segment.copyFrom(MemorySegment.ofArray(arr));
int terminatorIndex = random.nextInt(len);
segment.set(ValueLayout.JAVA_BYTE, terminatorIndex, (byte) 0);
for (Charset charset : singleByteCharsets()) {
var s = segment.getString(0, charset);
var ref = referenceImpl(segment, 0, charset);
assertEquals(s, ref);
}
}
}
}
}
@Test(dataProvider = "strings")
public void testOffset(String testString) {
if (testString.length() < 3 || !containsOnlyRegularCharacters(testString)) {
return;
}
for (var charset : singleByteCharsets()) {
for (var arena: arenas()) {
try (arena) {
MemorySegment inSegment = arena.allocateFrom(testString, charset);
for (int i = 0; i < 3; i++) {
String actual = inSegment.getString(i, charset);
assertEquals(actual, testString.substring(i));
}
}
}
}
}
private static final MemoryLayout CHAR_POINTER = ADDRESS
.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE));
private static final Linker LINKER = Linker.nativeLinker();
private static final MethodHandle STRCAT = LINKER.downcallHandle(
LINKER.defaultLookup().find("strcat").orElseThrow(),
FunctionDescriptor.of(CHAR_POINTER, CHAR_POINTER, CHAR_POINTER));
@Test(dataProvider = "strings")
public void nativeSegFromNativeCall(String testString) {
String addition = "123";
try (var arena = Arena.ofConfined()) {
try {
var testStringSegment = arena.allocateFrom(testString);
var additionSegment = arena.allocateFrom(addition);
var destination = arena.allocate(testStringSegment.byteSize() + additionSegment.byteSize() - 1);
destination.copyFrom(testStringSegment);
MemorySegment concatenation = (MemorySegment) STRCAT.invokeExact(destination, arena.allocateFrom(addition));
var actual = concatenation.getString(0);
assertEquals(actual, testString + addition);
} catch (Throwable t) {
throw new AssertionError(t);
}
}
}
@Test
public void segmentationFault() {
for (int i = 1; i < 18; i++) {
var size = 1 << i;
try (var arena = Arena.ofConfined()) {
var seg = arena.allocate(size, size);
seg.fill((byte) 1);
try {
var s = seg.getString(0);
System.out.println("s.length() = " + s.length());
} catch (IndexOutOfBoundsException e) {
// we will end up here if strlen finds a zero outside the MS
}
}
}
}
private static final int TEST_LENGTH_MAX = 277;
private Random deterministicRandom() {
return new Random(42);
}
@Test
public void chunked_strlen_byte() {
Random random = deterministicRandom();
for (int skew = 0; skew < Long.BYTES; skew++) {
for (int len = 0; len < TEST_LENGTH_MAX; len++) {
try (var arena = Arena.ofConfined()) {
var segment = arena.allocate(len + 1 + skew)
.asSlice(skew);
for (int i = 0; i < len; i++) {
byte value;
while ((value = (byte) random.nextInt()) == 0) {
}
segment.setAtIndex(JAVA_BYTE, i, value);
}
segment.setAtIndex(JAVA_BYTE, len, (byte) 0);
for (int j = 0; j < len; j++) {
int actual = StringSupport.chunkedStrlenByte(segment, j);
assertEquals(actual, len - j);
}
}
}
}
}
@Test
public void chunked_strlen_short() {
Random random = deterministicRandom();
for (int skew = 0; skew < Long.BYTES; skew += Short.BYTES) {
for (int len = 0; len < TEST_LENGTH_MAX; len++) {
try (var arena = Arena.ofConfined()) {
var segment = arena.allocate((len + 1) * Short.BYTES + skew, JAVA_SHORT.byteAlignment())
.asSlice(skew);
for (int i = 0; i < len; i++) {
short value;
while ((value = (short) random.nextInt()) == 0) {
}
segment.setAtIndex(JAVA_SHORT, i, value);
}
segment.setAtIndex(JAVA_SHORT, len, (short) 0);
for (int j = 0; j < len; j++) {
int actual = StringSupport.chunkedStrlenShort(segment, j * Short.BYTES);
assertEquals(actual, (len - j) * Short.BYTES);
}
}
}
}
}
@Test
public void strlen_int() {
Random random = deterministicRandom();
for (int skew = 0; skew < Long.BYTES; skew += Integer.BYTES) {
for (int len = 0; len < TEST_LENGTH_MAX; len++) {
try (var arena = Arena.ofConfined()) {
var segment = arena.allocate((len + 1) * Integer.BYTES + skew, JAVA_INT.byteAlignment())
.asSlice(skew);
for (int i = 0; i < len; i++) {
int value;
while ((value = random.nextInt()) == 0) {
}
segment.setAtIndex(JAVA_INT, i, value);
}
segment.setAtIndex(JAVA_INT, len, 0);
for (int j = 0; j < len; j++) {
int actual = StringSupport.strlenInt(segment, j * Integer.BYTES);
assertEquals(actual, (len - j) * Integer.BYTES);
}
}
}
}
}
@DataProvider
public static Object[][] strings() {
return new Object[][] {
{ "testing" },
{ "" },
{ "X" },
{ "12345" },
{ "yen \u00A5" },
{ "snowman \u26C4" },
{ "rainbow \uD83C\uDF08" }
return new Object[][]{
{"testing"},
{""},
{"X"},
{"12345"},
{"yen \u00A5"},
{"snowman \u26C4"},
{"rainbow \uD83C\uDF08"},
{"0"},
{"01"},
{"012"},
{"0123"},
{"01234"},
{"012345"},
{"0123456"},
{"01234567"},
{"012345678"},
{"0123456789"}
};
}
boolean isStandard(Charset charset) throws ReflectiveOperationException {
public static boolean containsOnlyRegularCharacters(String s) {
return s.chars()
.allMatch(c -> Character.isLetterOrDigit((char) c));
}
boolean isStandard(Charset charset) {
for (Field standardCharset : StandardCharsets.class.getDeclaredFields()) {
if (standardCharset.get(null) == charset) {
return true;
try {
if (standardCharset.get(null) == charset) {
return true;
}
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
return false;
}
List<Charset> standardCharsets() {
return Charset.availableCharsets().values().stream()
.filter(this::isStandard)
.toList();
}
List<Charset> singleByteCharsets() {
return Arrays.asList(StandardCharsets.UTF_8, StandardCharsets.ISO_8859_1, StandardCharsets.US_ASCII);
}
static String referenceImpl(MemorySegment segment, long offset, Charset charset) {
long len = strlen_byte(segment, offset);
byte[] bytes = new byte[(int) len];
MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, (int) len);
return new String(bytes, charset);
}
// Reference implementation
private static int strlen_byte(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset++) {
byte curr = segment.get(JAVA_BYTE, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
}
private static List<Arena> arenas() {
return Arrays.asList(
Arena.ofConfined(), // Native memory
new HeapArena(byte.class), // Heap memory backed by a byte array
new HeapArena(short.class), // Heap memory backed by a short array
new HeapArena(int.class), // Heap memory backed by an int array
new HeapArena(long.class)); // Heap memory backed by a long array
}
private static final class HeapArena implements Arena {
private static final int ELEMENT_SIZE = 1_000;
private final MemorySegment backingSegment;
private final SegmentAllocator allocator;
public HeapArena(Class<?> type) {
backingSegment = switch (type) {
case Class<?> c when byte.class.equals(c) -> MemorySegment.ofArray(new byte[ELEMENT_SIZE]);
case Class<?> c when short.class.equals(c) ->
MemorySegment.ofArray(new short[ELEMENT_SIZE]);
case Class<?> c when int.class.equals(c) ->
MemorySegment.ofArray(new int[ELEMENT_SIZE]);
case Class<?> c when long.class.equals(c) ->
MemorySegment.ofArray(new long[ELEMENT_SIZE]);
default -> throw new IllegalArgumentException(type.toString());
};
allocator = SegmentAllocator.slicingAllocator(backingSegment);
}
@Override
public MemorySegment allocate(long byteSize, long byteAlignment) {
return allocator.allocate(byteSize, byteAlignment);
}
@Override
public MemorySegment.Scope scope() {
return backingSegment.scope();
}
@Override
public void close() {
// Do nothing
}
@Override
public String toString() {
return "HeapArena{" +
"type=" + backingSegment.heapBase().orElseThrow().getClass().getName() +
'}';
}
}
}

@ -0,0 +1,102 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import org.testng.annotations.*;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import static java.lang.foreign.ValueLayout.*;
import static org.testng.Assert.*;
/*
* @test
* @modules java.base/jdk.internal.foreign
* @requires sun.arch.data.model == "64"
* @requires vm.flavor != "zero"
*
* @run testng/othervm -Xmx6G TestStringEncodingJumbo
*/
public class TestStringEncodingJumbo {
@Test()
public void testJumboSegment() {
testWithJumboSegment("testJumboSegment", segment -> {
segment.fill((byte) 1);
segment.set(JAVA_BYTE, Integer.MAX_VALUE + 10L, (byte) 0);
String big = segment.getString(100);
assertEquals(big.length(), Integer.MAX_VALUE - (100 - 10));
});
}
@Test()
public void testStringLargerThanMaxInt() {
testWithJumboSegment("testStringLargerThanMaxInt", segment -> {
segment.fill((byte) 1);
segment.set(JAVA_BYTE, Integer.MAX_VALUE + 10L, (byte) 0);
assertThrows(IllegalArgumentException.class, () -> {
segment.getString(0);
});
});
}
private static void testWithJumboSegment(String testName, Consumer<MemorySegment> tester) {
Path path = Paths.get("mapped_file");
try {
// Relly try to make sure the file is deleted after use
path.toFile().deleteOnExit();
deleteIfExistsOrThrow(path);
try (RandomAccessFile raf = new RandomAccessFile(path.toFile(), "rw")) {
FileChannel fc = raf.getChannel();
try (Arena arena = Arena.ofConfined()) {
var segment = fc.map(FileChannel.MapMode.READ_WRITE, 0L, (long) Integer.MAX_VALUE + 100, arena);
tester.accept(segment);
}
}
} catch (Exception e) {
throw new AssertionError(e);
} catch (OutOfMemoryError oome) {
// Unfortunately, we run out of memory and cannot run this test in this configuration
System.out.println("Skipping test because of insufficient memory: " + testName);
} finally {
deleteIfExistsOrThrow(path);
}
}
private static void deleteIfExistsOrThrow(Path file) {
try {
Files.deleteIfExists(file);
} catch (IOException ioe) {
throw new AssertionError("Unable to delete mapped file: " + file);
}
}
}

@ -0,0 +1,145 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.foreign;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySegment.Scope;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(org.openjdk.jmh.annotations.Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" })
public class AllocTest extends CLayouts {
Arena arena = Arena.ofConfined();
@Param({"5", "20", "100", "500", "1000"})
public int size;
@TearDown
public void tearDown() {
arena.close();
}
@Benchmark
public MemorySegment alloc_confined() {
Arena arena = Arena.ofConfined();
MemorySegment segment = arena.allocate(size);
arena.close();
return segment;
}
@Benchmark
public long alloc_calloc_arena() {
CallocArena arena = new CallocArena();
MemorySegment segment = arena.allocate(size);
arena.close();
return segment.address();
}
@Benchmark
public long alloc_unsafe_arena() {
UnsafeArena arena = new UnsafeArena();
MemorySegment segment = arena.allocate(size);
arena.close();
return segment.address();
}
public static class CallocArena implements Arena {
static final MethodHandle CALLOC = Linker.nativeLinker()
.downcallHandle(
Linker.nativeLinker().defaultLookup().find("calloc").get(),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG));
static MemorySegment calloc(long size) {
try {
return (MemorySegment)CALLOC.invokeExact(size, 1L);
} catch (Throwable ex) {
throw new IllegalStateException(ex);
}
}
final Arena arena = Arena.ofConfined();
@Override
public Scope scope() {
return arena.scope();
}
@Override
public void close() {
arena.close();
}
@Override
public MemorySegment allocate(long byteSize, long byteAlignment) {
return calloc(byteSize)
.reinterpret(byteSize, arena, CLayouts::freeMemory);
}
}
public static class UnsafeArena implements Arena {
final Arena arena = Arena.ofConfined();
@Override
public Scope scope() {
return arena.scope();
}
@Override
public void close() {
arena.close();
}
@Override
public MemorySegment allocate(long byteSize, long byteAlignment) {
MemorySegment segment = MemorySegment.ofAddress(Utils.unsafe.allocateMemory(byteSize));
Utils.unsafe.setMemory(segment.address(), byteSize, (byte)0);
return segment.reinterpret(byteSize, arena, ms -> Utils.unsafe.freeMemory(segment.address()));
}
}
}

@ -0,0 +1,161 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.foreign;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.lang.foreign.ValueLayout.*;
import static jdk.internal.foreign.StringSupport.*;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 3, jvmArgsAppend = {"--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED", "--enable-native-access=ALL-UNNAMED", "--enable-preview"})
public class InternalStrLen {
private MemorySegment singleByteSegment;
private MemorySegment singleByteSegmentMisaligned;
private MemorySegment doubleByteSegment;
private MemorySegment quadByteSegment;
@Param({"1", "4", "16", "251", "1024"})
int size;
@Setup
public void setup() {
var arena = Arena.ofAuto();
singleByteSegment = arena.allocate((size + 1L) * Byte.BYTES);
singleByteSegmentMisaligned = arena.allocate((size + 1L) * Byte.BYTES);
doubleByteSegment = arena.allocate((size + 1L) * Short.BYTES);
quadByteSegment = arena.allocate((size + 1L) * Integer.BYTES);
Stream.of(singleByteSegment, doubleByteSegment, quadByteSegment)
.forEach(s -> IntStream.range(0, (int) s.byteSize() - 1)
.forEach(i -> s.set(
ValueLayout.JAVA_BYTE,
i,
(byte) ThreadLocalRandom.current().nextInt(1, 254)
)));
singleByteSegment.set(ValueLayout.JAVA_BYTE, singleByteSegment.byteSize() - Byte.BYTES, (byte) 0);
doubleByteSegment.set(ValueLayout.JAVA_SHORT, doubleByteSegment.byteSize() - Short.BYTES, (short) 0);
quadByteSegment.set(ValueLayout.JAVA_INT, quadByteSegment.byteSize() - Integer.BYTES, 0);
singleByteSegmentMisaligned = arena.allocate(singleByteSegment.byteSize() + 1).
asSlice(1);
MemorySegment.copy(singleByteSegment, 0, singleByteSegmentMisaligned, 0, singleByteSegment.byteSize());
}
@Benchmark
public int elementSingle() {
return legacy_strlen_byte(singleByteSegment, 0);
}
@Benchmark
public int elementByteMisaligned() {
return legacy_strlen_byte(singleByteSegmentMisaligned, 0);
}
@Benchmark
public int elementDouble() {
return legacy_strlen_short(doubleByteSegment, 0);
}
@Benchmark
public int elementQuad() {
return legacy_strlen_int(quadByteSegment, 0);
}
@Benchmark
public int chunkedSingle() {
return chunkedStrlenByte(singleByteSegment, 0);
}
@Benchmark
public int chunkedSingleMisaligned() {
return chunkedStrlenByte(singleByteSegmentMisaligned, 0);
}
@Benchmark
public int chunkedDouble() {
return chunkedStrlenShort(doubleByteSegment, 0);
}
@Benchmark
public int changedElementQuad() {
return strlenInt(quadByteSegment, 0);
}
// These are the legacy methods
private static int legacy_strlen_byte(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset++) {
byte curr = segment.get(JAVA_BYTE, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
}
private static int legacy_strlen_short(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset += 2) {
short curr = segment.get(JAVA_SHORT, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
}
private static int legacy_strlen_int(MemorySegment segment, long start) {
// iterate until overflow (String can only hold a byte[], whose length can be expressed as an int)
for (int offset = 0; offset >= 0; offset += 4) {
int curr = segment.get(JAVA_INT, start + offset);
if (curr == 0) {
return offset;
}
}
throw new IllegalArgumentException("String too large");
}
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.foreign;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySegment.Scope;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.MethodHandle;
import java.util.concurrent.TimeUnit;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(org.openjdk.jmh.annotations.Scope.Thread)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" })
public class ToCStringTest extends CLayouts {
@Param({"5", "20", "100", "200"})
public int size;
public String str;
static {
System.loadLibrary("ToCString");
}
static final MethodHandle STRLEN;
static {
Linker abi = Linker.nativeLinker();
STRLEN = abi.downcallHandle(abi.defaultLookup().find("strlen").get(),
FunctionDescriptor.of(C_INT, C_POINTER));
}
@Setup
public void setup() {
str = makeString(size);
}
@Benchmark
public long jni_writeString() throws Throwable {
return writeString(str);
}
@Benchmark
public MemorySegment panama_writeString() throws Throwable {
Arena arena = Arena.ofConfined();
MemorySegment segment = arena.allocateFrom(str);
arena.close();
return segment;
}
static native long writeString(String str);
static String makeString(int size) {
String lorem = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
""";
return lorem.substring(0, size);
}
}

@ -0,0 +1,84 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.foreign;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 3, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED", "--enable-preview"})
public class ToJavaStringTest {
private MemorySegment strSegment;
@Param({"5", "20", "100", "200"})
int size;
static {
System.loadLibrary("ToJavaString");
}
@Setup
public void setup() {
var arena = Arena.ofAuto();
strSegment = arena.allocateFrom(LOREM.substring(0, size));
}
@Benchmark
public String panama_readString() {
return strSegment.getString(0);
}
@Benchmark
public String jni_readString() {
return readString(strSegment.address());
}
static native String readString(long addr);
static String LOREM = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
""";
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <jni.h>
#include <stdlib.h>
#include <string.h>
JNIEXPORT jlong JNICALL Java_org_openjdk_bench_java_lang_foreign_ToCStringTest_writeString(JNIEnv *const env, const jclass cls, const jstring text) {
const char *str = (*env)->GetStringUTFChars(env, text, NULL);
jlong addr = (jlong)(void*)str;
(*env)->ReleaseStringUTFChars(env, text, str);
return addr;
}

@ -0,0 +1,32 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <jni.h>
#include <stdlib.h>
#include <string.h>
JNIEXPORT jstring JNICALL Java_org_openjdk_bench_java_lang_foreign_ToJavaStringTest_readString(JNIEnv *const env, const jclass cls, jlong addr) {
return (*env)->NewStringUTF(env, (char*)(void*)addr);
}