Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8317837: Leftover FFM implementation-only changes #16128

Closed
wants to merge 15 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions make/test/BuildMicrobenchmark.gmk
Original file line number Diff line number Diff line change
@@ -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 \
17 changes: 17 additions & 0 deletions src/java.base/share/classes/java/lang/String.java
Original file line number Diff line number Diff line change
@@ -28,6 +28,8 @@
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 byte[] getBytes() {
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
12 changes: 11 additions & 1 deletion src/java.base/share/classes/java/lang/System.java
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@
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.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 StackWalker newStackWalkerInstance(Set<StackWalker.Option> options,
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);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1076,7 +1076,7 @@ default long mismatch(MemorySegment other) {
* 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 @@ default String getString(long offset, Charset charset) {
*/
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);
}

/**
Original file line number Diff line number Diff line change
@@ -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 @@ default MemorySegment allocateFrom(String str, Charset charset) {
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;
}
Original file line number Diff line number Diff line change
@@ -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) {
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@

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 @@ StackWalker newStackWalkerInstance(Set<StackWalker.Option> options,
* 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);
}
66 changes: 45 additions & 21 deletions src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java
Original file line number Diff line number Diff line change
@@ -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.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 @@ private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] boun
// 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 LayoutPath sequenceElement(long start, long step) {
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 LayoutPath groupElement(String name) {
}
}
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 LayoutPath groupElement(long index) {
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 VarHandle dereferenceHandle() {

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 MethodHandle sliceHandle() {

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 @@ private static LayoutPath derefPath(MemoryLayout layout, MethodHandle handle, La

// 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 @@ private long[] addBound(long maxIndex) {
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.
272 changes: 225 additions & 47 deletions src/java.base/share/classes/jdk/internal/foreign/StringSupport.java

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/java.base/share/classes/jdk/internal/foreign/Utils.java
Original file line number Diff line number Diff line change
@@ -146,15 +146,15 @@ private static byte booleanToByte(boolean b) {
@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);
}

@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);
}
Original file line number Diff line number Diff line change
@@ -54,10 +54,8 @@
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 static long alignUp(long addr, long alignment) {
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
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@
import java.lang.foreign.Arena;

import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.Utils;

public final class UpcallStubs {

@@ -36,7 +37,7 @@ private 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));
}
}

Original file line number Diff line number Diff line change
@@ -372,7 +372,7 @@ public String toString() {
* <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.
45 changes: 41 additions & 4 deletions test/jdk/java/foreign/TestLayoutPaths.java
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@

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 void testByteOffsetHandleBadRange() {
}

@Test
public void testBadAlignmentOfRoot() throws Throwable {
public void testBadAlignmentOfRoot() {
MemoryLayout struct = MemoryLayout.structLayout(
JAVA_INT,
JAVA_SHORT.withName("x"));
@@ -147,22 +148,58 @@ public void testBadAlignmentOfRoot() throws Throwable {
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);
413 changes: 389 additions & 24 deletions test/jdk/java/foreign/TestStringEncoding.java

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions test/jdk/java/foreign/TestStringEncodingJumbo.java
Original file line number Diff line number Diff line change
@@ -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 changes: 145 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java
Original file line number Diff line number Diff line change
@@ -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()));
}
}
}
161 changes: 161 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/foreign/InternalStrLen.java
Original file line number Diff line number Diff line change
@@ -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");
}

}
103 changes: 103 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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.
""";

}
33 changes: 33 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/foreign/libToCString.c
Original file line number Diff line number Diff line change
@@ -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;
}
32 changes: 32 additions & 0 deletions test/micro/org/openjdk/bench/java/lang/foreign/libToJavaString.c
Original file line number Diff line number Diff line change
@@ -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);
}