diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 0a6a56f2a2e..d6caf33e875 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -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 \ diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 9b19d7e2ac1..befd04ad916 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -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 diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index da4fd4f358d..e7ed153c620 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -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); + } }); } } diff --git a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java index 11beb471695..3bea712e221 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -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); } /** diff --git a/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java b/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java index 8d411cdc4ca..fc74c78436b 100644 --- a/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java +++ b/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java @@ -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; } diff --git a/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java b/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java index ffc2af1ec51..5cb71cf0424 100644 --- a/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java @@ -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) { diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 29c64f2a1b6..f026209aba4 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -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 @ */ 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); } diff --git a/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java b/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java index f52c5e2ee64..f87bbf306c3 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java +++ b/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java @@ -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 requireLayoutType(Class 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. diff --git a/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java b/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java index 4571a7c55c5..eaa028cae39 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java +++ b/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java @@ -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}} + *

+ * The method divides the region of interest into three distinct regions: + *

    + *
  • head (access made on a byte-by-byte basis) (if any)
  • + *
  • body (access made with eight bytes at a time at physically 64-bit-aligned memory) (if any)
  • + *
  • tail (access made on a byte-by-byte basis) (if any)
  • + *
+ *

+ * 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. + *

+ * 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}} + *

+ * 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"); + } + } diff --git a/src/java.base/share/classes/jdk/internal/foreign/Utils.java b/src/java.base/share/classes/jdk/internal/foreign/Utils.java index 7cbb07d0c10..7bbfc705b80 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/Utils.java +++ b/src/java.base/share/classes/jdk/internal/foreign/Utils.java @@ -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); } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java index 4fbb246e77c..90c3efbfe67 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java @@ -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 diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java b/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java index 854e5643dc9..79adbe51542 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java @@ -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)); } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java b/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java index ed11e0b28b9..2567227abf6 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java +++ b/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java @@ -372,7 +372,7 @@ public final class ValueLayouts { *

  • {@link ValueLayout.OfFloat}, for {@code float.class}
  • *
  • {@link ValueLayout.OfLong}, for {@code long.class}
  • *
  • {@link ValueLayout.OfDouble}, for {@code double.class}
  • - *
  • {@link ValueLayout.OfAddress}, for {@code MemorySegment.class}
  • + *
  • {@link AddressLayout}, for {@code MemorySegment.class}
  • * * @param carrier the value layout carrier. * @param order the value layout's byte order. diff --git a/test/jdk/java/foreign/TestLayoutPaths.java b/test/jdk/java/foreign/TestLayoutPaths.java index f9d254a5983..c3692b1fb9d 100644 --- a/test/jdk/java/foreign/TestLayoutPaths.java +++ b/test/jdk/java/foreign/TestLayoutPaths.java @@ -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); diff --git a/test/jdk/java/foreign/TestStringEncoding.java b/test/jdk/java/foreign/TestStringEncoding.java index d7d27cdc452..c4eeae8ea3e 100644 --- a/test/jdk/java/foreign/TestStringEncoding.java +++ b/test/jdk/java/foreign/TestStringEncoding.java @@ -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 charsets, + UnaryOperator segmentMapper, + UnaryOperator 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 standardCharsets() { + return Charset.availableCharsets().values().stream() + .filter(this::isStandard) + .toList(); + } + + List 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 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() + + '}'; + } + } + } diff --git a/test/jdk/java/foreign/TestStringEncodingJumbo.java b/test/jdk/java/foreign/TestStringEncodingJumbo.java new file mode 100644 index 00000000000..8ef86e72efc --- /dev/null +++ b/test/jdk/java/foreign/TestStringEncodingJumbo.java @@ -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 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); + } + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java new file mode 100644 index 00000000000..b9b5a3f30da --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java @@ -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())); + } + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/InternalStrLen.java b/test/micro/org/openjdk/bench/java/lang/foreign/InternalStrLen.java new file mode 100644 index 00000000000..81ed675c7d9 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/InternalStrLen.java @@ -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"); + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java new file mode 100644 index 00000000000..7194dec9b23 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java @@ -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); + } +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/ToJavaStringTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/ToJavaStringTest.java new file mode 100644 index 00000000000..47b279679a3 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/ToJavaStringTest.java @@ -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. + """; + +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/libToCString.c b/test/micro/org/openjdk/bench/java/lang/foreign/libToCString.c new file mode 100644 index 00000000000..24c7e6f959b --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/libToCString.c @@ -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 +#include +#include + +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; +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/libToJavaString.c b/test/micro/org/openjdk/bench/java/lang/foreign/libToJavaString.c new file mode 100644 index 00000000000..dff129dedf8 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/libToJavaString.c @@ -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 +#include +#include + +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); +}