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:
parent
605c976729
commit
b12c471a99
make/test
src/java.base/share/classes
test
jdk/java/foreign
micro/org/openjdk/bench/java/lang/foreign
@ -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() +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
102
test/jdk/java/foreign/TestStringEncodingJumbo.java
Normal file
102
test/jdk/java/foreign/TestStringEncodingJumbo.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
145
test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java
Normal file
145
test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java
Normal 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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user