diff a/.jcheck/conf b/.jcheck/conf --- a/.jcheck/conf +++ b/.jcheck/conf @@ -1,12 +1,12 @@ [general] -project=jdk +project=panama jbs=JDK version=22 [checks] -error=author,committer,reviewers,merge,issues,executable,symlink,message,hg-tag,whitespace,problemlists +error=author,committer,reviewers,merge,executable,symlink,message,hg-tag,whitespace,problemlists [repository] tags=(?:jdk-(?:[1-9]([0-9]*)(?:\.(?:0|[1-9][0-9]*)){0,4})(?:\+(?:(?:[0-9]+))|(?:-ga)))|(?:jdk[4-9](?:u\d{1,3})?-(?:(?:b\d{2,3})|(?:ga)))|(?:hs\d\d(?:\.\d{1,2})?-b\d\d) branches= @@ -17,19 +17,15 @@ [checks "whitespace"] files=.*\.cpp|.*\.hpp|.*\.c|.*\.h|.*\.java|.*\.cc|.*\.hh|.*\.m|.*\.mm|.*\.md|.*\.gmk|.*\.m4|.*\.ac|Makefile ignore-tabs=.*\.gmk|Makefile [checks "merge"] -message=Merge +message=Merge .* [checks "reviewers"] -reviewers=1 -ignore=duke +committers=1 [checks "committer"] role=committer -[checks "issues"] -pattern=^([124-8][0-9]{6}): (\S.*)$ - [checks "problemlists"] dirs=test/jdk|test/langtools|test/lib-test|test/hotspot/jtreg|test/jaxp diff a/README.md b/README.md --- a/README.md +++ b/README.md @@ -8,5 +8,19 @@ - [doc/building.md](doc/building.md) (markdown version) See for more information about the OpenJDK Community and the JDK and see for JDK issue tracking. + +--- +Foreign Function & Memory API + +This repository contains changes which aim at improving the interoperability between the Java programming language and native libraries, which is one of the main goals of [Project Panama](https://openjdk.java.net/projects/panama/). This is done by introducing a new Java API, the Foreign Function & Memory API, which can be used to: + +* interact with different kinds of memory resources, including so-called off-heap or native memory, as shown [here](doc/panama_memaccess.md); +* find native functions in a .dll/.so/.dylib and invoke them using method handles, as shown [here](doc/panama_ffi.md). + +This API has been delivered, as incubating/preview APIs, in official JDK releases, see [JEP 412](https://openjdk.java.net/jeps/412), [JEP 419](https://openjdk.java.net/jeps/419) and [JEP 424](https://openjdk.java.net/jeps/424) for more details. + +The Foreign Function & Memory API is best used in combination with a tool called `jextract`, which can be used to generate Java bindings to access functions and/or structs in a native library described by a given header file. The tool is available in a standalone [repository](https://github.com/openjdk/jextract) which contains several [examples](https://github.com/openjdk/jextract/tree/master/samples) which should help you getting started. + +Early acccess (EA) binary snapshots of this repository can be found at: http://jdk.java.net/panama/ diff a/doc/panama_ffi.html b/doc/panama_ffi.html --- /dev/null +++ b/doc/panama_ffi.html @@ -0,0 +1,693 @@ + + + + + +panama_ffi + +
+

State of foreign function support

January 2023

Maurizio Cimadamore

The Foreign Function & Memory API (FFM API in short) provides access to foreign functions through the Linker interface, which has been available as an incubating API since Java 16. A linker allows clients to construct downcall method handles — that is, method handles whose invocation targets a native function defined in some native library. In other words, FFM API's foreign function support is completely expressed in terms of Java code and no intermediate native code is required.

Zero-length memory segments

Before we dive into the specifics of the foreign function support, it would be useful to briefly recap some of the main concepts we have learned when exploring the foreign memory access support. The Foreign Memory Access API allows client to create and manipulate memory segments. A memory segment is a view over a memory source (either on- or off-heap) which is spatially bounded, temporally bounded and thread-confined. The guarantees ensure that dereferencing a segment that has been created by Java code is always safe, and can never result in a VM crash, or, worse, in silent memory corruption.

Now, in the case of memory segments, the above properties (spatial bounds, temporal bounds and confinement) can be known in full when the segment is created. But when we interact with native libraries we often receive raw pointers; such pointers have no spatial bounds (does a char* in C refer to one char, or a char array of a given size?), no notion of temporal bounds, nor thread-confinement. Raw addresses in the FFM API are modelled using zero-length memory segments.

If clients want to dereference a zero-length memory segment, they can do so unsafely in two ways. First, the client can create a new memory segment from the zero-length memory segment unsafely, using the MemorySegment::ofAddress factory. This method is restricted and will generate runtime warnings if called without specifying the --enable-native-access command-line flag. By calling MemorySegment::ofAddress a client inject extra knowledge about spatial bounds which might be available in the native library the client is interacting with:

Alternatively, clients can obtain an unbounded address value layout. This is done using the ValueLayout.OfAddress::asUnbounded method (which is also a restricted method). When an access operation uses an unbounded address value layouts, the runtime will wrap any corresponding raw addresses with native segments with maximal size (i.e. Long.MAX_VALUE). As such, these segments can be accessed directly, as follows:

Which approach is taken largely depends on the information that a client has available when obtaining a memory segment wrapping a native pointer. For instance, if such pointer points to a C struct, the client might prefer to resize the segment unsafely, to match the size of the struct (so that out-of-bounds access will be detected by the API). In other instances, however, there will be no, or little information as to what spatial and/or temporal bounds should be associated with a given native pointer. In these cases using an unbounded address layout might be preferable.

Segment allocators

Idiomatic C code implicitly relies on stack allocation to allow for concise variable declarations; consider this example:

A variable initializer such as the one above can be implemented as follows, using the Foreign Memory Access API:

There are a number of issues with the above code snippet:

  • compared to the C code, it is more verbose — the native array has to be initialized element by element
  • allocation is very slow compared to C; allocating the arr variable now takes a full malloc, while in C the variable was simply stack-allocated
  • when having multiple declarations like the one above, it might become increasingly harder to manage the lifecycle of the various segments

To address these problems, the FFM API provides a SegmentAllocator abstraction, a functional interface which provides methods to allocate commonly used values. Since Arena implements the SegmentAllocator interface, the above code can be rewritten conveniently as follows:

In the above code, the arena acts as a native allocator (that is, an allocator built on top of MemorySegment::allocateNative). The arena is then used to create a native array, initialized to the values 0, 1, 2, 3, 4. The array initialization is more efficient, compared to the previous snippet, as the Java array is copied in bulk into the memory region associated with the newly allocated memory segment. The returned segment is associated with the scope of the arena which performed the allocation, meaning that the segment will no longer be accessible after the try-with-resource construct.

Custom segment allocators are also critical to achieve optimal allocation performance; for this reason, a number of predefined allocators are available via factories in the SegmentAllocator interface. For example, the following code creates a slicing allocator and uses it to allocate a segment whose content is initialized from a Java int array:

This code creates a native segment whose size is 1024 bytes. The segment is then used to create a slicing allocator, which responds to allocation requests by returning slices of that pre-allocated segment. If the current segment does not have sufficient space to accommodate an allocation request, an exception is thrown. All of the memory associated with the segments created by the allocator (i.e., in the body of the for loop) is deallocated atomically when the arena is closed. This technique combines the advantages of deterministic deallocation, provided by the Arena abstraction, with a more flexible and scalable allocation scheme. It can be very useful when writing code which manages a large number of off-heap segments.

All the methods in the FFM API which produce memory segments (see VaList::nextVarg and downcall method handles), allow for an allocator parameter to be provided — this is key in ensuring that an application using the FFM API achieves optimal allocation performances, especially in non-trivial use cases.

Symbol lookups

The first ingredient of any foreign function support is a mechanism to lookup symbols in native libraries. In traditional Java/JNI, this is done via the System::loadLibrary and System::load methods. Unfortunately, these methods do not provide a way for clients to obtain the address associated with a given library symbol. For this reason, the Foreign Linker API introduces a new abstraction, namely SymbolLookup (similar in spirit to a method handle lookup), which provides capabilities to lookup named symbols; we can obtain a symbol lookup in 3 different ways:

  • SymbolLookup::libraryLookup(String, SegmentScope) — creates a symbol lookup which can be used to search symbol in a library with the given name. The provided segment scope parameter controls the library lifecycle: that is, when the scope is not longer alive, the library referred to by the lookup will also be closed;
  • SymbolLookup::loaderLookup — creates a symbol lookup which can be used to search symbols in all the libraries loaded by the caller's classloader (e.g. using System::loadLibrary or System::load)
  • Linker::defaultLookup — returns the default symbol lookup associated with a Linker instance. For instance, the default lookup of the native linker (see Linker::nativeLinker) can be used to look up platform-specific symbols in the standard C library (such as strlen, or getpid).

Once a lookup has been obtained, a client can use it to retrieve handles to library symbols (either global variables or functions) using the find(String) method, which returns an Optional<MemorySegment>. The memory segments returned by the lookup are zero-length segments, whose base address is the address of the function or variable in the library.

For instance, the following code can be used to look up the clang_getClangVersion function provided by the clang library; it does so by creating a library lookup whose lifecycle is associated to that of a confined arena.

Linker

At the core of the FFM API's foreign function support we find the Linker abstraction. This abstraction plays a dual role: first, for downcalls, it allows modelling foreign function calls as plain MethodHandle calls (see Linker::downcallHandle); second, for upcalls, it allows to convert an existing MethodHandle (which might point to some Java method) into a MemorySegment which could then be passed to foreign functions as a function pointer (see Linker::upcallStub):

Both functions take a FunctionDescriptor instance — essentially an aggregate of memory layouts which is used to describe the argument and return types of a foreign function in full. Supported layouts are value layouts (for scalars and pointers) and group layouts (for structs/unions). Each layout in a function descriptor is associated with a carrier Java type (see table below); together, all the carrier types associated with layouts in a function descriptor will determine a unique Java MethodType — that is, the Java signature that clients will be using when interacting with said downcall handles, or upcall stubs.

The Linker::nativeLinker factory is used to obtain a Linker implementation for the ABI associated with the OS and processor where the Java runtime is currently executing. As such, the native linker can be used to call C functions. The following table shows the mapping between C types, layouts and Java carriers under the Linux/macOS native linker implementation; note that the mappings can be platform dependent: on Windows/x64, the C type long is 32-bit, so the JAVA_INT layout (and the Java carrier int.class) would have to be used instead:

C typeLayoutJava carrier
boolJAVA_BOOLEANbyte
charJAVA_BYTEbyte
shortJAVA_SHORTshort, char
intJAVA_INTint
longJAVA_LONGlong
long longJAVA_LONGlong
floatJAVA_FLOATfloat
doubleJAVA_DOUBLEdouble
char*
int**
...
ADDRESSMemorySegment
struct Point { int x; int y; };
union Choice { float a; int b; };
...
MemoryLayout.structLayout(...)
MemoryLayout.unionLayout(...)
MemorySegment

Both C structs/unions and pointers are modelled using the MemorySegment carrier type. However, C structs/unions are modelled in function descriptors with memory layouts of type GroupLayout, whereas pointers are modelled using the ADDRESS value layout constant (whose size is platform-specific). Moreover, the behavior of a downcall method handle returning a struct/union type is radically different from that of a downcall method handle returning a C pointer:

  • downcall method handles returning C pointers will wrap the pointer address into a fresh zero-length memory segment (unless an unbounded address layout is specified);
  • downcall method handles returning a C struct/union type will return a new segment, of given size (the size of the struct/union). The segment is allocated using a user-provided SegmentAllocator, which is provided using an additional prefix parameter inserted in the downcall method handle signature.

A tool, such as jextract, will generate all the required C layouts (for scalars and structs/unions) automatically, so that clients do not have to worry about platform-dependent details such as sizes, alignment constraints and padding.

Downcalls

We will now look at how foreign functions can be called from Java using the native linker. Assume we wanted to call the following function from the standard C library:

In order to do that, we have to:

  • lookup the strlen symbol
  • describe the signature of the C function using a function descriptor
  • create a downcall native method handle with the above information, using the native linker

Here's an example of how we might want to do that (a full listing of all the examples in this and subsequent sections will be provided in the appendix):

Note that, since the function strlen is part of the standard C library, which is loaded with the VM, we can just use the default lookup of the native linker to look it up. The rest is pretty straightforward — the only tricky detail is how to model size_t: typically this type has the size of a pointer, so we can use JAVA_LONG both Linux and Windows. On the Java side, we model the size_t using a long and the pointer is modelled using an Addressable parameter.

Once we have obtained the downcall method handle, we can just use it as any other method handle1:

Here we are using a confined arena to convert a Java string into an off-heap memory segment which contains a NULL terminated C string. We then pass that segment to the method handle and retrieve our result in a Java long. Note how all this is possible without any piece of intervening native code — all the interop code can be expressed in (low level) Java. Note also how we use an arena to control the lifecycle of the allocated C string, which ensures timely deallocation of the memory segment holding the native string.

The Linker interface also supports linking of native functions without an address known at link time; when that happens, an address (of type MemorySegment) must be provided when the method handle returned by the linker is invoked — this is very useful to support virtual calls. For instance, the above code can be rewritten as follows:

It is important to note that, albeit the interop code is written in Java, the above code can not be considered 100% safe. There are many arbitrary decisions to be made when setting up downcall method handles such as the one above, some of which might be obvious to us (e.g. how many parameters does the function take), but which cannot ultimately be verified by the Java runtime. After all, a symbol in a dynamic library is nothing but a numeric offset and, unless we are using a shared library with debugging information, no type information is attached to a given library symbol. This means that the Java runtime has to trust the function descriptor passed in2; for this reason, the Linker::nativeLinker factory is also a restricted method.

When working with shared arenas, it is always possible for the arena associated with a memory segment passed by reference to a native function to be closed (by another thread) while the native function is executing. When this happens, the native code is at risk of dereferencing already-freed memory, which might trigger a JVM crash, or even result in silent memory corruption. For this reason, the Linker API provides some basic temporal safety guarantees: any MemorySegment instance passed by reference to a downcall method handle will be kept alive for the entire duration of the call. In other words, it's as if the call to the downcall method handle occurred inside an invisible call to SegmentScope::whileAlive.

Performance-wise, the reader might ask how efficient calling a foreign function using a native method handle is; the answer is very. The JVM comes with some special support for native method handles, so that, if a give method handle is invoked many times (e.g, inside a hot loop), the JIT compiler might decide to generate a snippet of assembly code required to call the native function, and execute that directly. In most cases, invoking native function this way is as efficient as doing so through JNI.

Upcalls

Sometimes, it is useful to pass Java code as a function pointer to some native function; we can achieve that by using foreign linker support for upcalls. To demonstrate this, let's consider the following function from the C standard library:

The qsort function can be used to sort the contents of an array, using a custom comparator function — compar — which is passed as a function pointer. To be able to call the qsort function from Java we have first to create a downcall method handle for it:

As before, we use JAVA_LONG and long.class to map the C size_t type, and ADDRESS for both the first pointer parameter (the array pointer) and the last parameter (the function pointer).

This time, in order to invoke the qsort downcall handle, we need a function pointer to be passed as the last parameter; this is where the upcall support in foreign linker comes in handy, as it allows us to create a function pointer out of an existing method handle. First, let's write a function that can compare two int elements (passed as pointers):

Here we can see that the function is performing some unsafe dereference of the pointer contents.

Now let's create a method handle pointing to the comparator function above:

To do that, we first create a function descriptor for the function pointer type, and then we use the CLinker::upcallType to turn that function descriptor into a suitable MethodType instance to be used in a method handle lookup. Now that we have a method handle for our Java comparator function, we finally have all the ingredients to create an upcall stub, and pass it to the qsort downcall handle:

The above code creates an upcall stub — comparFunc — a function pointer that can be used to invoke our Java comparator function, of type MemorySegment. The upcall stub is associated with the provided segment scope instance; this means that the stub will be uninstalled when the arena is closed.

The snippet then creates an off-heap array from a Java array, which is then passed to the qsort handle, along with the comparator function we obtained from the foreign linker. As a side effect, after the call, the contents of the off-heap array will be sorted (as instructed by our comparator function, written in Java). We can than extract a new Java array from the segment, which contains the sorted elements. This is a more advanced example, but one that shows how powerful the native interop support provided by the foreign linker abstraction is, allowing full bidirectional interop support between Java and native.

Varargs

Some C functions are variadic and can take an arbitrary number of arguments. Perhaps the most common example of this is the printf function, defined in the C standard library:

This function takes a format string, which features zero or more holes, and then can take a number of additional arguments that is identical to the number of holes in the format string.

The foreign function support can support variadic calls, but with a caveat: the client must provide a specialized Java signature, and a specialized description of the C signature. For instance, let's say we wanted to model the following C call:

To do this using the foreign function support provided by the FFM API we would have to build a specialized downcall handle for that call shape, using a linker option to specify the position of the first variadic layout, as follows:

Then we can call the specialized downcall handle as usual:

While this works, and provides optimal performance, it has some limitations3:

  • If the variadic function needs to be called with many shapes, we have to create many downcall handles
  • while this approach works for downcalls (since the Java code is in charge of determining which and how many arguments should be passed) it fails to scale to upcalls; in that case, the call comes from native code, so we have no way to guarantee that the shape of the upcall stub we have created will match that required by the native function.

Appendix: full source code

The full source code containing most of the code shown throughout this document can be seen below:

 

+ + diff a/doc/panama_ffi.md b/doc/panama_ffi.md --- /dev/null +++ b/doc/panama_ffi.md @@ -0,0 +1,363 @@ +## State of foreign function support + +**March 2023** + +**Maurizio Cimadamore** + +The Foreign Function & Memory API (FFM API in short) provides access to foreign functions through the `Linker` interface, which has been available as an [incubating](https://openjdk.java.net/jeps/11) API since Java [16](https://openjdk.java.net/jeps/389). A linker allows clients to construct *downcall* method handles — that is, method handles whose invocation targets a native function defined in some native library. In other words, FFM API's foreign function support is completely expressed in terms of Java code and no intermediate native code is required. + +### Zero-length memory segments + +Before we dive into the specifics of the foreign function support, it would be useful to briefly recap some of the main concepts we have learned when exploring the [foreign memory access support](panama_memaccess.md). The Foreign Memory Access API allows client to create and manipulate *memory segments*. A memory segment is a view over a memory source (either on- or off-heap) which is spatially bounded, temporally bounded and thread-confined. The guarantees ensure that dereferencing a segment that has been created by Java code is always *safe*, and can never result in a VM crash, or, worse, in silent memory corruption. + +Now, in the case of memory segments, the above properties (spatial bounds, temporal bounds and confinement) can be known *in full* when the segment is created. But when we interact with native libraries we often receive *raw* pointers; such pointers have no spatial bounds (does a `char*` in C refer to one `char`, or a `char` array of a given size?), no notion of temporal bounds, nor thread-confinement. Raw addresses in the FFM API are modelled using *zero-length memory segments*. + +To work with native zero-length memory segments, clients have several options, all of which are unsafe. First, clients can unsafely resize a zero-length memory segment by obtaining a memory segment with the same base address as the zero-length memory segment, but with the desired size, so that the resulting segment can then be accessed directly, as follows: + +```java +MemorySegment foreign = someSegment.get(ValueLayout.ADDRESS, 0); // size = 0 + .reinterpret(4) // size = 4 +int x = foreign.get(ValueLayout.JAVA_INT, 0); // ok +``` + +In some cases, a client might additionally want to assign new temporal bounds to a zero-length memory segment. This can be done using another variant of the `MemorySegment::reinterpret` method, which returns a new native segment with the desired size and temporal bounds: + +```java +MemorySegment foreign = null; +try (Arena arena = Arena.ofConfined()) { + foreign = someSegment.get(ValueLayout.ADDRESS, 0) // size = 0, scope = always alive + .reinterpret(4, arena, null); // size = 4, scope = arena.scope() + int x = foreign.get(ValueLayout.JAVA_INT, 0); // ok +} +int x = foreign.get(ValueLayout.JAVA_INT, 0); // throws IllegalStateException +``` + +Note how the new segment behaves as if it was allocated in the provided arena: when the arena is closed, the new segment is no longer accessible. + +Alternatively, if the size of the foreign segment is known statically, clients can associate a *target layout* with the address layout used to obtain the segment. When an access operation, or a function descriptor that is passed to a downcall method handle (see below), uses an address value layout with target layout `T`, the runtime will wrap any corresponding raw addresses as segments with size set to `T.byteSize()`: +```java +MemorySegment foreign = someSegment.get(ValueLayout.ADDRESS.withTargetLayout(JAVA_INT), 0); // size = 4 +int x = foreign.get(ValueLayout.JAVA_INT, 0); // ok +``` + +Which approach is taken largely depends on the information that a client has available when obtaining a memory segment wrapping a native pointer. For instance, if such pointer points to a C struct, the client might prefer to resize the segment unsafely, to match the size of the struct (so that out-of-bounds access will be detected by the API). If the size is known statically, using an address layout with the correct target layout might be preferable. In other instances, however, there will be no, or little information as to what spatial and/or temporal bounds should be associated with a given native pointer. In these cases using an unbounded address layout might be preferable. + +> Note: Memory segments created using `MemorySegment::reinterpret`, or `OfAddress::withTargetLayout` are completely *unsafe*. There is no way for the runtime to verify that the provided address indeed points to a valid memory location, or that the size and temporal bounds of the memory region pointed by the address indeed conforms to the parameters provided by the client. For these reasons, these methods are *restricted method* in the FFM API. The first time a restricted method is invoked, a runtime warning is generated. Developers can get rid of warnings by specifying the set of modules that are allowed to call restricted methods. This is done by specifying the option `--enable-native-access=M`, where `M` is a module name. Multiple module names can be specified in a comma-separated list, where the special name `ALL-UNNAMED` is used to enable restricted access for all code on the class path. If the `--enable-native-access` option is specified, any attempt to call restricted operations from a module not listed in the option will fail with a runtime exception. + +### Symbol lookups + +The first ingredient of any foreign function support is a mechanism to lookup symbols in native libraries. In traditional Java/JNI, this is done via the `System::loadLibrary` and `System::load` methods. Unfortunately, these methods do not provide a way for clients to obtain the *address* associated with a given library symbol. For this reason, the Foreign Linker API introduces a new abstraction, namely `SymbolLookup` (similar in spirit to a method handle lookup), which provides capabilities to lookup named symbols; we can obtain a symbol lookup in 3 different ways: + +* `SymbolLookup::libraryLookup(String, SegmentScope)` — creates a symbol lookup which can be used to search symbol in a library with the given name. The provided segment scope parameter controls the library lifecycle: that is, when the scope is no longer alive, the library referred to by the lookup will also be closed; +* `SymbolLookup::loaderLookup` — creates a symbol lookup which can be used to search symbols in all the libraries loaded by the caller's classloader (e.g. using `System::loadLibrary` or `System::load`) +* `Linker::defaultLookup` — returns the default symbol lookup associated with a `Linker` instance. For instance, the default lookup of the native linker (see `Linker::nativeLinker`) can be used to look up platform-specific symbols in the standard C library (such as `strlen`, or `getpid`). + +Once a lookup has been obtained, a client can use it to retrieve handles to library symbols (either global variables or functions) using the `find(String)` method, which returns an `Optional`. The memory segments returned by the `lookup` are zero-length segments, whose base address is the address of the function or variable in the library. + +For instance, the following code can be used to look up the `clang_getClangVersion` function provided by the `clang` library; it does so by creating a *library lookup* whose lifecycle is associated to that of a confined arena. + +```java +try (Arena arena = Arena.ofConfined()) { + SymbolLookup libclang = SymbolLookup.libraryLookup("libclang.so", arena); + MemorySegment clangVersion = libclang.find("clang_getClangVersion").get(); +} +``` + +### Linker + +At the core of the FFM API's foreign function support we find the `Linker` abstraction. This abstraction plays a dual role: first, for downcalls, it allows modelling foreign function calls as plain `MethodHandle` calls (see `Linker::downcallHandle`); second, for upcalls, it allows to convert an existing `MethodHandle` (which might point to some Java method) into a `MemorySegment` which could then be passed to foreign functions as a function pointer (see `Linker::upcallStub`): + +```java +interface Linker { + MethodHandle downcallHandle(MemorySegment symbol, FunctionDescriptor function); + MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, SegmentScope scope); + ... // some overloads omitted here + + static Linker nativeLinker() { ... } +} +``` + +Both functions take a `FunctionDescriptor` instance — essentially an aggregate of memory layouts which is used to describe the argument and return types of a foreign function in full. Supported layouts are *value layouts* (for scalars and pointers) and *group layouts* (for structs/unions). Each layout in a function descriptor is associated with a carrier Java type (see table below); together, all the carrier types associated with layouts in a function descriptor will determine a unique Java `MethodType` — that is, the Java signature that clients will be using when interacting with said downcall handles, or upcall stubs. + +The `Linker::nativeLinker` factory is used to obtain a `Linker` implementation for the ABI associated with the OS and processor where the Java runtime is currently executing. As such, the native linker can be used to call C functions. The following table shows the mapping between C types, layouts and Java carriers under the Linux/macOS native linker implementation; note that the mappings can be platform dependent: on Windows/x64, the C type `long` is 32-bit, so the `JAVA_INT` layout (and the Java carrier `int.class`) would have to be used instead: + +| C type | Layout | Java carrier | +| ------------------------------------------------------------ | ------------------------------------------------------------ | --------------- | +| `bool` | `JAVA_BOOLEAN` | `byte` | +| `char` | `JAVA_BYTE` | `byte` | +| `short` | `JAVA_SHORT` | `short`, `char` | +| `int` | `JAVA_INT` | `int` | +| `long` | `JAVA_LONG` | `long` | +| `long long` | `JAVA_LONG` | `long` | +| `float` | `JAVA_FLOAT` | `float` | +| `double` | `JAVA_DOUBLE` | `double` | +| `char*`
`int**`
... | `ADDRESS` | `MemorySegment` | +| `struct Point { int x; int y; };`
`union Choice { float a; int b; };`
... | `MemoryLayout.structLayout(...)`
`MemoryLayout.unionLayout(...)`
| `MemorySegment` | + +Both C structs/unions and pointers are modelled using the `MemorySegment` carrier type. However, C structs/unions are modelled in function descriptors with memory layouts of type `GroupLayout`, whereas pointers are modelled using the `ADDRESS` value layout constant (whose size is platform-specific). Moreover, the behavior of a downcall method handle returning a struct/union type is radically different from that of a downcall method handle returning a C pointer: + +* downcall method handles returning C pointers will wrap the pointer address into a fresh zero-length memory segment (unless an unbounded address layout is specified); +* downcall method handles returning a C struct/union type will return a *new* segment, of given size (the size of the struct/union). The segment is allocated using a user-provided `SegmentAllocator`, which is provided using an additional prefix parameter inserted in the downcall method handle signature. + +A tool, such as `jextract`, will generate all the required C layouts (for scalars and structs/unions) *automatically*, so that clients do not have to worry about platform-dependent details such as sizes, alignment constraints and padding. + +### Downcalls + +We will now look at how foreign functions can be called from Java using the native linker. Assume we wanted to call the following function from the standard C library: + +```c +size_t strlen(const char *s); +``` + +In order to do that, we have to: + +* lookup the `strlen` symbol +* describe the signature of the C function using a function descriptor + +* create a *downcall* native method handle with the above information, using the native linker + +Here's an example of how we might want to do that (a full listing of all the examples in this and subsequent sections will be provided in the [appendix](#appendix-full-source-code)): + +```java +Linker linker = Linker.nativeLinker(); +MethodHandle strlen = linker.downcallHandle( + linker.defaultLookup().find("strlen").get(), + FunctionDescriptor.of(JAVA_LONG, ADDRESS) +); +``` + +Note that, since the function `strlen` is part of the standard C library, which is loaded with the VM, we can just use the default lookup of the native linker to look it up. The rest is pretty straightforward — the only tricky detail is how to model `size_t`: typically this type has the size of a pointer, so we can use `JAVA_LONG` both Linux and Windows. On the Java side, we model the `size_t` using a `long` and the pointer is modelled using an `MemorySegment` parameter. + +Once we have obtained the downcall method handle, we can just use it as any other method handle: + +```java +try (Arena arena = Arena.ofConfined()) { + long len = strlen.invokeExact(arena.allocateUtf8String("Hello")); // 5 +} +``` + +Here we are using a confined arena to convert a Java string into an off-heap memory segment which contains a `NULL` terminated C string. We then pass that segment to the method handle and retrieve our result in a Java `long`. Note how all this is possible *without* any piece of intervening native code — all the interop code can be expressed in (low level) Java. Note also how we use an arena to control the lifecycle of the allocated C string, which ensures timely deallocation of the memory segment holding the native string. + +The `Linker` interface also supports linking of native functions without an address known at link time; when that happens, an address (of type `MemorySegment`) must be provided when the method handle returned by the linker is invoked — this is very useful to support *virtual calls*. For instance, the above code can be rewritten as follows: + +```java +MethodHandle strlen_virtual = linker.downcallHandle( // address parameter missing! + FunctionDescriptor.of(JAVA_LONG, ADDRESS) +); + +try (Arena arena = Arena.ofConfined()) { + long len = strlen_virtual.invokeExact( + linker.defaultLookup().find("strlen").get() // address provided here! + arena.allocateUtf8String("Hello") + ); // 5 +} +``` + +It is important to note that, albeit the interop code is written in Java, the above code can *not* be considered 100% safe. There are many arbitrary decisions to be made when setting up downcall method handles such as the one above, some of which might be obvious to us (e.g. how many parameters does the function take), but which cannot ultimately be verified by the Java runtime. After all, a symbol in a dynamic library is nothing but a numeric offset and, unless we are using a shared library with debugging information, no type information is attached to a given library symbol. This means that the Java runtime has to *trust* the function descriptor passed in
1; for this reason, the `Linker::nativeLinker` factory is also a restricted method. + +When working with shared arenas, it is always possible for the arena associated with a memory segment passed *by reference* to a native function to be closed (by another thread) *while* the native function is executing. When this happens, the native code is at risk of dereferencing already-freed memory, which might trigger a JVM crash, or even result in silent memory corruption. For this reason, the `Linker` API provides some basic temporal safety guarantees: any `MemorySegment` instance passed by reference to a downcall method handle will be *kept alive* for the entire duration of the call. + +Performance-wise, the reader might ask how efficient calling a foreign function using a native method handle is; the answer is *very*. The JVM comes with some special support for native method handles, so that, if a give method handle is invoked many times (e.g, inside a *hot* loop), the JIT compiler might decide to generate a snippet of assembly code required to call the native function, and execute that directly. In most cases, invoking native function this way is as efficient as doing so through JNI. + +### Upcalls + +Sometimes, it is useful to pass Java code as a function pointer to some native function; we can achieve that by using foreign linker support for upcalls. To demonstrate this, let's consider the following function from the C standard library: + +```c +void qsort(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); +``` + +The `qsort` function can be used to sort the contents of an array, using a custom comparator function — `compar` — which is passed as a function pointer. To be able to call the `qsort` function from Java we have first to create a downcall method handle for it: + +```java +Linker linker = Linker.nativeLinker(); +MethodHandle qsort = linker.downcallHandle( + linker.defaultLookup().lookup("qsort").get(), + FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) +); +``` + +As before, we use `JAVA_LONG` and `long.class` to map the C `size_t` type, and `ADDRESS` for both the first pointer parameter (the array pointer) and the last parameter (the function pointer). + +This time, in order to invoke the `qsort` downcall handle, we need a *function pointer* to be passed as the last parameter; this is where the upcall support in foreign linker comes in handy, as it allows us to create a function pointer out of an existing method handle. First, let's write a function that can compare two int elements (passed as pointers): + +```java +class Qsort { + static int qsortCompare(MemorySegment elem1, MemorySegmet elem2) { + return elem1.get(JAVA_INT, 0) - elem2.get(JAVA_INT, 0); + } +} +``` + +Here we can see that the function is performing some *unsafe* dereference of the pointer contents. + +Now let's create a method handle pointing to the comparator function above: + +```java +FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, + ADDRESS.withTargetLayout(JAVA_INT), + ADDRESS.withTargetLayout(JAVA_INT)); +MethodHandle comparHandle = MethodHandles.lookup() + .findStatic(Qsort.class, "qsortCompare", + comparDesc.toMethodType()); +``` + +To do that, we first create a function descriptor for the function pointer type. This descriptor uses address layouts that have a `JAVA_INT` target layout, to allow access operations inside the upcall method handle. We use the `FunctionDescriptor::toMethodType` to turn that function descriptor into a suitable `MethodType` instance to be used in a method handle lookup. Now that we have a method handle for our Java comparator function, we finally have all the ingredients to create an upcall stub, and pass it to the `qsort` downcall handle: + +```java +try (Arena arena = Arena.ofConfined()) { + MemorySegment comparFunc = linker.upcallStub(comparHandle, comparDesc, arena); + MemorySegment array = session.allocateArray(0, 9, 3, 4, 6, 5, 1, 8, 2, 7); + qsort.invokeExact(array, 10L, 4L, comparFunc); + int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] +} +``` + +The above code creates an upcall stub — `comparFunc` — a function pointer that can be used to invoke our Java comparator function, of type `MemorySegment`. The upcall stub is associated with the provided segment scope instance; this means that the stub will be uninstalled when the arena is closed. + +The snippet then creates an off-heap array from a Java array, which is then passed to the `qsort` handle, along with the comparator function we obtained from the foreign linker. As a side effect, after the call, the contents of the off-heap array will be sorted (as instructed by our comparator function, written in Java). We can than extract a new Java array from the segment, which contains the sorted elements. This is a more advanced example, but one that shows how powerful the native interop support provided by the foreign linker abstraction is, allowing full bidirectional interop support between Java and native. + +### Variadic calls + +Some C functions are *variadic* and can take an arbitrary number of arguments. Perhaps the most common example of this is the `printf` function, defined in the C standard library: + +```c +int printf(const char *format, ...); +``` + +This function takes a format string, which features zero or more *holes*, and then can take a number of additional arguments that is identical to the number of holes in the format string. + +The foreign function support can support variadic calls, but with a caveat: the client must provide a specialized Java signature, and a specialized description of the C signature. For instance, let's say we wanted to model the following C call: + +```c +printf("%d plus %d equals %d", 2, 2, 4); +``` + +To do this using the foreign function support provided by the FFM API we would have to build a *specialized* downcall handle for that call shape, using a linker option to specify the position of the first variadic layout, as follows: + +```java +Linker linker = Linker.nativeLinker(); +MethodHandle printf = linker.downcallHandle( + linker.defaultLookup().lookup("printf").get(), + FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT) + Linker.Option.firstVariadicArg(1) // first int is variadic +); +``` + +Then we can call the specialized downcall handle as usual: + +```java +try (Arena arena = Arena.ofConfined()) { + int res = (int)printf.invokeExact(arena.allocateUtf8String("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4" +} +``` + +While this works, and provides optimal performance, it has some limitations2: + +* If the variadic function needs to be called with many shapes, we have to create many downcall handles +* while this approach works for downcalls (since the Java code is in charge of determining which and how many arguments should be passed) it fails to scale to upcalls; in that case, the call comes from native code, so we have no way to guarantee that the shape of the upcall stub we have created will match that required by the native function. + +### Appendix: full source code + +The full source code containing most of the code shown throughout this document can be seen below: + +```java +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.MemorySegment; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; + +import static java.lang.foreign.ValueLayout.*; + +public class Examples { + + static Linker LINKER = Linker.nativeLinker(); + static SymbolLookup STDLIB = LINKER.defaultLookup(); + + public static void main(String[] args) throws Throwable { + strlen(); + strlen_virtual(); + qsort(); + printf(); + } + + public static void strlen() throws Throwable { + MethodHandle strlen = LINKER.downcallHandle( + STDLIB.find("strlen").get(), + FunctionDescriptor.of(JAVA_LONG, ADDRESS) + ); + + try (Arena arena = Arena.ofConfined()) { + MemorySegment hello = arena.allocateFrom("Hello"); + long len = (long) strlen.invokeExact(hello); // 5 + System.out.println(len); + } + } + + public static void strlen_virtual() throws Throwable { + MethodHandle strlen_virtual = LINKER.downcallHandle( + FunctionDescriptor.of(JAVA_LONG, ADDRESS) + ); + + try (Arena arena = Arena.ofConfined()) { + MemorySegment hello = arena.allocateFrom("Hello"); + long len = (long) strlen_virtual.invokeExact( + STDLIB.find("strlen").get(), + hello); // 5 + System.out.println(len); + } + } + + static class Qsort { + static int qsortCompare(MemorySegment addr1, MemorySegment addr2) { + return addr1.get(JAVA_INT, 0) - addr2.get(JAVA_INT, 0); + } + } + + public static void qsort() throws Throwable { + MethodHandle qsort = LINKER.downcallHandle( + STDLIB.find("qsort").get(), + FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) + ); + FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, + ADDRESS.withTargetLayout(JAVA_INT), + ADDRESS.withTargetLayout(JAVA_INT)); + MethodHandle comparHandle = MethodHandles.lookup() + .findStatic(Qsort.class, "qsortCompare", + comparDesc.toMethodType()); + + try (Arena arena = Arena.ofConfined()) { + MemorySegment comparFunc = LINKER.upcallStub( + comparHandle, comparDesc, arena); + + MemorySegment array = arena.allocateFrom(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); + qsort.invokeExact(array, 10L, 4L, comparFunc); + int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] + System.out.println(Arrays.toString(sorted)); + } + } + + public static void printf() throws Throwable { + MethodHandle printf = LINKER.downcallHandle( + STDLIB.find("printf").get(), + FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), + Linker.Option.firstVariadicArg(1) // first int is variadic + ); + try (Arena arena = Arena.ofConfined()) { + MemorySegment s = arena.allocateFrom("%d plus %d equals %d\n"); + int res = (int) printf.invokeExact(s, 2, 2, 4); + } + } +} +``` + + + +* (1): In reality this is not entirely new; even in JNI, when you call a `native` method the VM trusts that the corresponding implementing function in C will feature compatible parameter types and return values; if not a crash might occur. +* (2): Previous iterations of the FFM API provided a `VaList` class that could be used to model a C `va_list`. This class was later dropped from the FFM API as too implementation specific. It is possible that a future version of the `jextract` tool might provide higher-level bindings for variadic calls.  diff a/doc/panama_memaccess.md b/doc/panama_memaccess.md --- /dev/null +++ b/doc/panama_memaccess.md @@ -0,0 +1,245 @@ +## State of foreign memory support + +**March 2023** + +**Maurizio Cimadamore** + +A crucial part of any native interop story lies in the ability of accessing off-heap memory efficiently and safely. Java achieves this goal through the Foreign Function & Memory API (FFM API in short), parts of which have been available as an [incubating](https://openjdk.java.net/jeps/11) API since Java [14](https://openjdk.java.net/jeps/370). The FFM API introduces abstractions to allocate and access flat memory regions (whether on- or off-heap), to manage the lifecycle of memory resources and to model native memory addresses. + +### Memory segments and arenas + +Memory segments are abstractions which can be used to model contiguous memory regions, located either on-heap (i.e. *heap segments*) or off- the Java heap (i.e. *native segments*). Memory segments provide *strong* spatial, temporal and thread-confinement guarantees which make memory dereference operation *safe* (more on that later), although in most simple cases some properties of memory segments can safely be ignored. + +For instance, the following snippet allocates 100 bytes off-heap: + +```java +MemorySegment segment = Arena.global().allocate(100); +``` + +The above code allocates a 100-bytes long memory segment, using an *arena*. The FFM API provides several kinds of arena, which can be used to control the lifecycle of the allocated native segments in different ways. In this example, the segment is allocated with the *global* arena. Memory segments allocated with this arena are always *alive* and their backing regions of memory are never deallocated. In other words, we say that the above segment has an *unbounded* lifetime. + +> Note: the lifetime of a memory segment is modelled by a *scope* (see `MemorySegment.Scope`). A memory segment can be accessed as long as its associated scope is *alive* (see `Scope::isAlive`). In most cases, the scope of a memory segment is the scope of the arena which allocated that segment. Accessing the scope of a segment can be useful to perform lifetime queries (e.g. asking whether a segment has the same lifetime as that of another segment), creating custom arenas and unsafely assigning new temporal bounds to an existing native memory segments (these topics are explored in more details below). + +Most programs, though, require off-heap memory to be deallocated while the program is running, and thus need memory segments with *bounded* lifetimes. The simplest way to obtain a segment with bounded lifetime is to use an *automatic arena*: + +```java +MemorySegment segment = Arena.ofAuto().allocate(100); +``` + +Segments allocated with an automatic arena are alive as long as they are determined to be reachable by the garbage collector. In other words, the above snippet creates a native segment whose behavior closely matches that of a `ByteBuffer` allocated with the `allocateDirect` factory. + +There are cases, however, where automatic deallocation is not enough: consider the case where a large memory segment is mapped from a file (this is possible using `FileChannel::map`); in this case, an application would probably prefer to release (e.g. `unmap`) the memory associated with this segment in a *deterministic* fashion, to ensure that memory doesn't remain available for longer than it needs to. + +A *confined* arena allocates segment featuring a bounded *and* deterministic lifetime. A memory segment allocated with a confined arena is alive from the time when the arena is opened, until the time when the arena is closed (at which point the segments become inaccessible). Multiple segments allocated with the same arena enjoy the *same* bounded lifetime and can safely contain mutual references. For example, this code opens an arena and uses it to allocate several native segments: + +```java +try (Arena arena = Arena.ofConfined()) { + MemorySegment segment1 = arena.allocate(100); + MemorySegment segment2 = arena.allocate(100); + ... + MemorySegment segmentN = arena.allocate(100); +} // all segments are deallocated here +``` + +When the arena is closed (above, this is done with the *try-with-resources* construct) the arena is no longer alive, all the segments associated with it are invalidated atomically, and the regions of memory backing the segments are deallocated. + +A confined arena's deterministic lifetime comes at a price: only one thread can access the memory segments allocated in a confined arena. If multiple threads need access to a segment, then a *shared* arena can be used (`Arena::ofShared`). The memory segments allocated in a shared arena can be accessed by multiple threads, and any thread (regardless of whether it was involved in access) can close the shared arena to deallocate the segments. The closure will atomically invalidate the segments, though deallocation of the regions of memory backing the segments might not occur immediately: an expensive synchronization operation1 is needed to detect and cancel pending concurrent access operations on the segments. + +In summary, an arena controls *which* threads can access a memory segment and *when*, in order to provide both strong temporal safety and a predictable performance model. The FFM API offers a choice of arenas so that a client can trade off breadth-of-access against timeliness of deallocation. + +### Slicing segments + +Memory segments support *slicing* — that is, given a segment, it is possible to create a new segment whose spatial bounds are stricter than that of the original segment: + +```java +MemorySegment segment = Arena.ofAuto().allocate(10); +MemorySegment slice = segment.asSlice(4, 4); +``` + +The above code creates a slice that starts at offset 4 and has a length of 4 bytes. Slices have the *same* temporal bounds (i.e. segment scope) as the parent segment. In the above example, the memory associated with the parent segment will not be released as long as there is at least one *reachable* slice derived from that segment. + +To process the contents of a memory segment in bulk, a memory segment can be turned into a stream of slices, using the `MemorySegment::stream` method: + +```java +SequenceLayout seq = MemoryLayout.sequenceLayout(1_000_000, JAVA_INT); +SequenceLayout bulk_element = MemoryLayout.sequenceLayout(100, JAVA_INT); + +try (Arena arena = Arena.ofShared()) { + MemorySegment segment = arena.allocate(seq); + int sum = segment.elements(bulk_element).parallel() + .mapToInt(slice -> { + int res = 0; + for (int i = 0; i < 100 ; i++) { + res += slice.getAtIndex(JAVA_INT, i); + } + return res; + }).sum(); +} +``` + +The `MemorySegment::elements` method takes an element layout and returns a new stream. The stream is built on top of a spliterator instance (see `MemorySegment::spliterator`) which splits the segment into chunks whose size match that of the provided layout. Here, we want to sum elements in an array which contains a million of elements; now, doing a parallel sum where each computation processes *exactly* one element would be inefficient, so instead we use a *bulk* element layout. The bulk element layout is a sequence layout containing a group of 100 elements — which should make it more amenable to parallel processing. Since we are using `Stream::parallel` to work on disjoint slices in parallel, here we use a *shared* arena, to ensure that the resulting segment can be accessed by multiple threads. + +### Accessing segments + +Memory segments can be dereferenced easily, by using *value layouts* (layouts are covered in greater details in the next section). A value layout captures information such as: + +- The number of bytes to be dereferenced; +- The alignment constraints of the address at which dereference occurs; +- The endianness with which bytes are stored in said memory region; +- The Java type to be used in the dereference operation (e.g. `int` vs `float`). + +For instance, the layout constant `ValueLayout.JAVA_INT` is four bytes wide, has no alignment constraints, uses the native platform endianness (e.g. little-endian on Linux/x64) and is associated with the Java type `int`. The following example reads pairs of 32-bit values (as Java ints) and uses them to construct an array of points: + +```java +record Point(int x, int y); +MemorySegment segment = Arena.ofAuto().allocate(10 * 4 * 2); +Point[] values = new Point[10]; +for (int i = 0 ; i < values.length ; i++) { + int x = segment.getAtIndex(JAVA_INT, i * 2); + int y = segment.getAtIndex(JAVA_INT, (i * 2) + 1); + values[i] = new Point(x, y); +} +``` + +The above snippet allocates a flat array of 80 bytes using an automatic arena. Then, inside the loop, elements in the array are accessed using the `MemorySegment::getAtIndex` method, which accesses `int` elements in a segment at a certain *logical* index (under the hood, the segment offset being accessed is obtained by multiplying the logical index by 4, which is the stride of a Java `int` array). Thus, all coordinates `x` and `y` are collected into instances of a `Point` record. + +### Structured access + +Expressing byte offsets (as in the example above) can lead to code that is hard to read, and very fragile — as memory layout invariants are captured, implicitly, in the constants used to scale offsets. To address this issue, clients can use a `MemoryLayout` to describe the contents of a memory segment *programmatically*. For instance, the layout of the array used in the above example can be expressed using the following code 2: + +```java +MemoryLayout points = MemoryLayout.sequenceLayout(10, + MemoryLayout.structLayout( + JAVA_INT.withName("x"), + JAVA_INT.withName("y") + ) +); +``` + +That is, our layout is a repetition of 10 *struct* elements, each struct element containing two 32-bit values each. Once defined, a memory layout can be queried — for instance we can compute the offset of the `y` coordinate in the 4th element of the `points` array: + +```java +long y3 = points.byteOffset(PathElement.sequenceElement(3), PathElement.groupElement("y")); // 28 +``` + +To specify which nested layout element should be used for the offset calculation we use a *layout path*, a selection expression that navigates the layout, from the *root* layout, down to the leaf layout we wish to select; in this case we need to select the 4th layout element in the sequence, and then select the layout named `y` inside the selected group layout. + +One of the things that can be derived from a layout is a *memory access var handle*. A memory access var handle is a special kind of var handle which takes a memory segment access coordinate, together with a byte offset — the offset, relative to the segment's base address at which the dereference operation should occur. With memory access var handles we can rewrite our example above as follows: + +```java +MemorySegment segment = Arena.ofAuto().allocate(points); +VarHandle xHandle = points.varHandle(PathElement.sequenceElement(), PathElement.groupElement("x")); +VarHandle yHandle = points.varHandle(PathElement.sequenceElement(), PathElement.groupElement("y")); +Point[] values = new Point[10]; +for (int i = 0 ; i < values.length ; i++) { + int x = (int)xHandle.get(segment, (long)i); + int y = (int)yHandle.get(segment, (long)i); +} +``` + +In the above, `xHandle` and `yHandle` are two var handle instances whose type is `int` and which takes two access coordinates: + +1. a `MemorySegment` instance; the segment whose memory should be dereferenced +2. a *logical* index, which is used to select the element of the sequence we want to access (as the layout path used to construct these var handles contains one free dimension) + +Note that memory access var handles (as any other var handle) are *strongly* typed; and to get maximum efficiency, it is generally necessary to introduce casts to make sure that the access coordinates match the expected types — in this case we have to cast `i` into a `long`; similarly, since the signature polymorphic method `VarHandle::get` notionally returns `Object` a cast is necessary to force the right return type the var handle operation 3. + +In other words, manual offset computation is no longer needed — offsets and strides can in fact be derived from the layout object; note how `yHandle` is able to compute the required offset of the `y` coordinate in the flat array without the need of any error-prone arithmetic computation. + +### Combining memory access handles + +We have seen in the previous sections how memory access var handles dramatically simplify user code when structured access is involved. While deriving memory access var handles from layout is the most convenient option, the FFM API also allows to create such memory access var handles in a standalone fashion, as demonstrated in the following code: + +```java +VarHandle intHandle = MethodHandles.memorySegmentViewVarHandle(JAVA_INT); // (MS, J) -> I +``` + +The above code creates a memory access var handle which reads/writes `int` values at a certain byte offset in a segment. To create this var handle we have to specify a carrier type — the type we want to use e.g. to extract values from memory, as well as whether any byte swapping should be applied when contents are read from or stored to memory. Additionally, the user might want to impose additional constraints on how memory dereferences should occur; for instance, a client might want to prevent access to misaligned 32 bit values. Of course, all this information can be succinctly derived from the provided value layout (`JAVA_INT` in the above example). + +The attentive reader might have noted how rich the var handles obtained from memory layouts are, compared to the simple memory access var handle we have constructed here. How do we go from a simple access var handle that takes a byte offset to a var handle that can dereference a complex layout path? The answer is, by using var handle *combinators*. Developers familiar with the method handles know how simpler method handles can be combined into more complex ones using the various combinator methods in the `MethodHandles` class. These methods allow, for instance, to insert (or bind) arguments into a target method handle, filter return values, permute arguments and much more. + +The FFM API adds a rich set of var handle combinators in the `MethodHandles` class; with these tools, developers can express var handle transformations such as: + +* mapping a var handle carrier type into a different one, using an embedding/projection method handle pairs +* filter one or more var handle access coordinates using unary filters +* permute var handle access coordinates +* bind concrete access coordinates to an existing var handle + +Without diving too deep, let's consider how we might want to take a basic memory access handle and turn it into a var handle which dereference a segment at a specific offset (again using the `points` layout defined previously): + +```java +VarHandle intHandle = MemoryHandles.memorySegmentViewVarHandle(JAVA_INT); // (MS, J) -> I +long offsetOfY = points.byteOffset(PathElement.sequenceElement(3), PathElement.groupElement("y")); +VarHandle valueHandle = MethodHandles.insertCoordinates(intHandle, 1, offsetOfValue); // (MS) -> I +``` + +We have been able to derive, from a basic memory access var handle, a new var handle that dereferences a segment at a given fixed offset. It is easy to see how other, richer, var handles obtained using a memory layout can also be constructed manually using the var handle combinators provided by the FFM API. + +### Segment allocators and custom arenas + +Memory allocation is often a bottleneck when clients use off-heap memory. The FFM API therefore includes a `SegmentAllocator` interface to define operations to allocate and initialize memory segments. As a convenience, the `Arena` interface extends the `SegmentAllocator` interface so that arenas can be used to allocate native segments. In other words, `Arena` is a "one-stop shop" for flexible allocation and timely deallocation of off-heap memory: + +```java +FileChannel channel = ... +try (Arena offHeap = Arena.ofConfined()) { + MemorySegment nativeArray = offHeap.allocateArray(ValueLayout.JAVA_INT, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + MemorySegment nativeString = offHeap.allocateUtf8String("Hello!"); + + MemorySegment mappedSegment = channel.map(MapMode.READ_WRITE, 0, 1000, arena); + ... +} // memory released here +``` + +Segment allocators can also be obtained via factories in the `SegmentAllocator` interface. For example, one factory creates a *slicing allocator* that responds to allocation requests by returning memory segments which are part of a previously allocated segment; thus, many requests can be satisfied without physically allocating more memory. The following code obtains a slicing allocator over an existing segment, then uses it to allocate a segment initialized from a Java array: + +```java +MemorySegment segment = ... +SegmentAllocator allocator = SegmentAllocator.slicingAllocator(segment); +for (int i = 0 ; i < 10 ; i++) { + MemorySegment s = allocator.allocateArray(JAVA_INT, new int[] { 1, 2, 3, 4, 5 }); + ... +} +``` + +A segment allocator can be used as a building block to create an arena that supports a custom allocation strategy. For example, if many segments share the same bounded lifetime, then an arena could use a slicing allocator to allocate the segments efficiently. This lets clients enjoy both scalable allocation (thanks to slicing) and deterministic deallocation (thanks to the arena). + +As an example, the following code defines a *slicing arena* that behaves like a confined arena (i.e., single-threaded access), but internally uses a slicing allocator to respond to allocation requests. When the slicing arena is closed, the underlying confined arena is also closed; this will invalidate all segments allocated with the slicing arena: + +```java +class SlicingArena { + final Arena arena = Arena.ofConfined(); + final SegmentAllocator slicingAllocator; + + SlicingArena(long size) { + slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size)); + } + +public void allocate(long byteSize, long byteAlignment) { + return slicingAllocator.allocate(byteSize, byteAlignment); + } + + public MemorySegment.Scope scope() { + return arena.scope(); + } + + public void close() { + return arena.close(); + } +} +``` + +The earlier code which used a slicing allocator directly can now be written more succinctly, as follows: + +```java +try (Arena slicingArena = new SlicingArena(1000)) { + for (int i = 0 ; i < 10 ; i++) { + MemorySegment s = arena.allocateArray(JAVA_INT, new int[] { 1, 2, 3, 4, 5 }); + ... + } +} // all memory allocated is released here +``` + +* (1): Shared arenas rely on VM thread-local handshakes (JEP [312](https://openjdk.java.net/jeps/312)) to implement lock-free, safe, shared memory access; that is, when it comes to memory access, there should be no difference in performance between a shared segment and a confined segment. On the other hand, `Arena::close` might be slower on shared arenas than on confined ones. +* (2): In general, deriving a complete layout from a C `struct` declaration is no trivial matter, and it's one of those areas where tooling can help greatly. +* (3): Clients can enforce stricter type checking when interacting with `VarHandle` instances, by obtaining an *exact* var handle, using the `VarHandle::withInvokeExactBehavior` method. + diff a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -424,13 +424,18 @@ "linux-x86": { target_os: "linux", target_cpu: "x86", build_cpu: "x64", - dependencies: ["devkit", "gtest"], - configure_args: concat(common.configure_args_32bit, - "--with-jvm-variants=minimal,server", "--with-zlib=system"), + dependencies: ["devkit", "gtest", "libffi"], + configure_args: concat(common.configure_args_32bit, [ + "--with-jvm-variants=minimal,server", + "--with-zlib=system", + "--with-libffi=" + input.get("libffi", "home_path"), + "--enable-libffi-bundling", + "--enable-fallback-linker" + ]) }, "macosx-x64": { target_os: "macosx", target_cpu: "x64", diff a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -104,10 +104,11 @@ --add-exports java.base/jdk.internal.classfile.impl=ALL-UNNAMED \ --add-exports java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED \ --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, \ JAVA_FLAGS := --add-modules jdk.unsupported --limit-modules java.management \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ --enable-preview, \ )) diff a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -81,10 +81,11 @@ import jdk.internal.reflect.ConstantPool; import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; +import jdk.internal.vm.annotation.Stable; import sun.invoke.util.Wrapper; import sun.reflect.generics.factory.CoreReflectionFactory; import sun.reflect.generics.factory.GenericsFactory; import sun.reflect.generics.repository.ClassRepository; @@ -1058,10 +1059,11 @@ public Module getModule() { return module; } // set by VM + @Stable private transient Module module; // Initialized in JVM not by private constructor // This field is filtered from reflection access, i.e. getDeclaredField // will throw NoSuchFieldException diff a/src/java.base/share/classes/java/lang/Module.java b/src/java.base/share/classes/java/lang/Module.java --- a/src/java.base/share/classes/java/lang/Module.java +++ b/src/java.base/share/classes/java/lang/Module.java @@ -270,13 +270,12 @@ /** * Returns {@code true} if this module can access * restricted methods. * * @return {@code true} if this module can access restricted methods. - * @since 20 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) public boolean isNativeAccessEnabled() { Module target = moduleForNativeAccess(); return EnableNativeAccess.isNativeAccessEnabled(target); } @@ -307,28 +306,31 @@ private Module moduleForNativeAccess() { return isNamed() ? this : ALL_UNNAMED_MODULE; } // This is invoked from Reflection.ensureNativeAccess - void ensureNativeAccess(Class owner, String methodName) { + void ensureNativeAccess(Class owner, String methodName, Class currentClass) { // The target module whose enableNativeAccess flag is ensured Module target = moduleForNativeAccess(); if (!EnableNativeAccess.isNativeAccessEnabled(target)) { if (ModuleBootstrap.hasEnableNativeAccessFlag()) { throw new IllegalCallerException("Illegal native access from: " + this); } if (EnableNativeAccess.trySetEnableNativeAccess(target)) { // warn and set flag, so that only one warning is reported per module String cls = owner.getName(); String mtd = cls + "::" + methodName; - String mod = isNamed() ? "module " + getName() : "the unnamed module"; + String mod = isNamed() ? "module " + getName() : "an unnamed module"; String modflag = isNamed() ? getName() : "ALL-UNNAMED"; + String caller = currentClass != null ? + " by " + currentClass.getName() : ""; System.err.printf(""" WARNING: A restricted method in %s has been called - WARNING: %s has been called by %s - WARNING: Use --enable-native-access=%s to avoid a warning for this module - %n""", cls, mtd, mod, modflag); + WARNING: %s has been called%s in %s + WARNING: Use --enable-native-access=%s to avoid a warning for callers in this module + WARNING: Restricted methods will be blocked in a future release unless native access is enabled + %n""", cls, mtd, caller, mod, modflag); } } } /** diff a/src/java.base/share/classes/java/lang/ModuleLayer.java b/src/java.base/share/classes/java/lang/ModuleLayer.java --- a/src/java.base/share/classes/java/lang/ModuleLayer.java +++ b/src/java.base/share/classes/java/lang/ModuleLayer.java @@ -305,13 +305,11 @@ * Enables native access for a module in the layer if the caller's module * has native access. * *

This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain - * from depending on restricted methods, and use safe and supported functionalities, - * where possible. + * the JVM or, worse, silently result in memory corruption. * * @param target * The module to update * * @return This controller @@ -320,13 +318,12 @@ * If {@code target} is not in the module layer * * @throws IllegalCallerException * If the caller is in a module that does not have native access enabled * - * @since 20 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) @CallerSensitive @Restricted public Controller enableNativeAccess(Module target) { ensureInLayer(target); Reflection.ensureNativeAccess(Reflection.getCallerClass(), Module.class, diff a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -26,10 +26,12 @@ 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; import java.nio.ByteBuffer; import java.nio.CharBuffer; @@ -1834,10 +1836,25 @@ */ 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 * String} object that represents the same sequence of characters as this * object. diff a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -33,10 +33,11 @@ import java.io.IOException; 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; import java.lang.module.ModuleDescriptor; import java.lang.reflect.Constructor; @@ -86,11 +87,10 @@ import jdk.internal.util.SystemProps; 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; import sun.reflect.annotation.AnnotationType; import sun.nio.ch.Interruptible; @@ -2452,12 +2452,12 @@ return m.implAddEnableNativeAccess(); } public void addEnableNativeAccessToAllUnnamed() { Module.implAddEnableNativeAccessToAllUnnamed(); } - public void ensureNativeAccess(Module m, Class owner, String methodName) { - m.ensureNativeAccess(owner, methodName); + public void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass) { + m.ensureNativeAccess(owner, methodName, currentClass); } public ServicesCatalog getServicesCatalog(ModuleLayer layer) { return layer.getServicesCatalog(); } public void bindToLoader(ModuleLayer layer, ClassLoader loader) { @@ -2667,8 +2667,18 @@ } 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 a/src/java.base/share/classes/java/lang/foreign/AddressLayout.java b/src/java.base/share/classes/java/lang/foreign/AddressLayout.java --- a/src/java.base/share/classes/java/lang/foreign/AddressLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/AddressLayout.java @@ -24,11 +24,10 @@ */ package java.lang.foreign; import jdk.internal.foreign.layout.ValueLayouts; -import jdk.internal.javac.PreviewFeature; import jdk.internal.javac.Restricted; import jdk.internal.reflect.CallerSensitive; import java.lang.foreign.Linker.Option; import java.lang.invoke.MethodHandle; @@ -49,15 +48,17 @@ * memory segment, e.g. using {@link MemorySegment#getAtIndex(AddressLayout, long)}; *

  • When creating a downcall method handle, using {@link Linker#downcallHandle(FunctionDescriptor, Option...)}; *
  • When creating an upcall stub, using {@link Linker#upcallStub(MethodHandle, FunctionDescriptor, Arena, Option...)}. * * + * @implSpec + * This class is immutable, thread-safe and value-based. + * * @see #ADDRESS * @see #ADDRESS_UNALIGNED - * @since 19 + * @since 22 */ -@PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) public sealed interface AddressLayout extends ValueLayout permits ValueLayouts.OfAddressImpl { /** * {@inheritDoc} */ @@ -93,17 +94,16 @@ * segments with maximal size (e.g. {@linkplain Long#MAX_VALUE}). This can be done by using a target sequence * layout with unspecified size, as follows: * {@snippet lang = java: * AddressLayout addressLayout = ... * AddressLayout unboundedLayout = addressLayout.withTargetLayout( - * MemoryLayout.sequenceLayout(ValueLayout.JAVA_BYTE)); + * MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE)); *} *

    * This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on - * restricted methods, and use safe and supported functionalities, where possible. + * the JVM or, worse, silently result in memory corruption. * * @param layout the target layout. * @return an address layout with same characteristics as this layout, but with the provided target layout. * @throws IllegalCallerException If the caller is in a module that does not have native access enabled. * @see #targetLayout() diff a/src/java.base/share/classes/java/lang/foreign/Arena.java b/src/java.base/share/classes/java/lang/foreign/Arena.java --- a/src/java.base/share/classes/java/lang/foreign/Arena.java +++ b/src/java.base/share/classes/java/lang/foreign/Arena.java @@ -24,11 +24,10 @@ */ package java.lang.foreign; import jdk.internal.foreign.MemorySessionImpl; -import jdk.internal.javac.PreviewFeature; import jdk.internal.ref.CleanerFactory; import java.lang.foreign.MemorySegment.Scope; /** @@ -182,24 +181,23 @@ * the timely deallocation guarantee provided by the underlying confined arena: * * {@snippet lang = java: * try (Arena slicingArena = new SlicingArena(1000)) { * for (int i = 0; i < 10; i++) { - * MemorySegment s = slicingArena.allocateArray(JAVA_INT, 1, 2, 3, 4, 5); + * MemorySegment s = slicingArena.allocateFrom(JAVA_INT, 1, 2, 3, 4, 5); * ... * } * } // all memory allocated is released here * } * * @implSpec * Implementations of this interface are thread-safe. * * @see MemorySegment * - * @since 20 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public interface Arena extends SegmentAllocator, AutoCloseable { /** * Creates a new arena that is managed, automatically, by the garbage collector. * Segments allocated with the returned arena can be @@ -219,11 +217,11 @@ * * @return the global arena. */ static Arena global() { class Holder { - static final Arena GLOBAL = MemorySessionImpl.GLOBAL.asArena(); + static final Arena GLOBAL = MemorySessionImpl.createGlobal().asArena(); } return Holder.GLOBAL; } /** @@ -267,13 +265,11 @@ * @throws IllegalStateException if this arena has already been {@linkplain #close() closed}. * @throws WrongThreadException if this arena is confined, and this method is called from a thread * other than the arena's owner thread. */ @Override - default MemorySegment allocate(long byteSize, long byteAlignment) { - return ((MemorySessionImpl)scope()).allocate(byteSize, byteAlignment); - } + MemorySegment allocate(long byteSize, long byteAlignment); /** * {@return the arena scope} */ Scope scope(); diff a/src/java.base/share/classes/java/lang/foreign/FunctionDescriptor.java b/src/java.base/share/classes/java/lang/foreign/FunctionDescriptor.java --- a/src/java.base/share/classes/java/lang/foreign/FunctionDescriptor.java +++ b/src/java.base/share/classes/java/lang/foreign/FunctionDescriptor.java @@ -30,11 +30,10 @@ import java.util.Objects; import java.util.Optional; import java.util.List; import jdk.internal.foreign.FunctionDescriptorImpl; -import jdk.internal.javac.PreviewFeature; /** * A function descriptor models the signature of a foreign function. A function descriptor is made up of zero or more * argument layouts, and zero or one return layout. A function descriptor is used to create * {@linkplain Linker#downcallHandle(MemorySegment, FunctionDescriptor, Linker.Option...) downcall method handles} and @@ -42,13 +41,12 @@ * * @implSpec * Implementing classes are immutable, thread-safe and value-based. * * @see MemoryLayout - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface FunctionDescriptor permits FunctionDescriptorImpl { /** * {@return the return layout (if any) of this function descriptor} */ diff a/src/java.base/share/classes/java/lang/foreign/GroupLayout.java b/src/java.base/share/classes/java/lang/foreign/GroupLayout.java --- a/src/java.base/share/classes/java/lang/foreign/GroupLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/GroupLayout.java @@ -24,11 +24,10 @@ */ package java.lang.foreign; import java.util.List; -import jdk.internal.javac.PreviewFeature; /** * A compound layout that is an aggregation of multiple, heterogeneous member layouts. There are two ways in which member layouts * can be combined: if member layouts are laid out one after the other, the resulting group layout is a * {@linkplain StructLayout struct layout}; conversely, if all member layouts are laid out at the same starting offset, @@ -36,13 +35,12 @@ * * @implSpec * This class is immutable, thread-safe and value-based. * * @sealedGraph - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface GroupLayout extends MemoryLayout permits StructLayout, UnionLayout { /** * {@return the member layouts of this group layout} * diff a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -27,19 +27,17 @@ import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; import jdk.internal.foreign.abi.CapturableState; import jdk.internal.foreign.abi.SharedUtils; -import jdk.internal.javac.PreviewFeature; import jdk.internal.javac.Restricted; import jdk.internal.reflect.CallerSensitive; -import jdk.internal.reflect.Reflection; import java.lang.invoke.MethodHandle; import java.nio.ByteOrder; +import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -58,10 +56,16 @@ *

  • A linker allows Java code to link against foreign functions, via * {@linkplain #downcallHandle(MemorySegment, FunctionDescriptor, Option...) downcall method handles}; and
  • *
  • A linker allows foreign functions to call Java method handles, * via the generation of {@linkplain #upcallStub(MethodHandle, FunctionDescriptor, Arena, Option...) upcall stubs}.
  • * + * A linker provides a way to look up the canonical layouts associated with the data types used by the ABI. + * For example, a linker implementing the C ABI might choose to provide a canonical layout for the C {@code size_t} + * type. On 64-bit platforms, this canonical layout might be equal to {@link ValueLayout#JAVA_LONG}. The canonical + * layouts supported by a linker are exposed via the {@link #canonicalLayouts()} method, which returns a map from + * type names to canonical layouts. + *

    * In addition, a linker provides a way to look up foreign functions in libraries that conform to the ABI. Each linker * chooses a set of libraries that are commonly used on the OS and processor combination associated with the ABI. * For example, a linker for Linux/x64 might choose two libraries: {@code libc} and {@code libm}. The functions in these * libraries are exposed via a {@linkplain #defaultLookup() symbol lookup}. * @@ -91,26 +95,23 @@ * {@link #downcallHandle(MemorySegment, FunctionDescriptor, Option...)} method. * The obtained downcall method handle is then invoked as follows: * * {@snippet lang = java: * try (Arena arena = Arena.ofConfined()) { - * MemorySegment str = arena.allocateUtf8String("Hello"); + * MemorySegment str = arena.allocateFrom("Hello"); * long len = (long) strlen.invokeExact(str); // 5 * } - * } + *} *

    Describing C signatures

    * * When interacting with the native linker, clients must provide a platform-dependent description of the signature * of the C function they wish to link against. This description, a {@link FunctionDescriptor function descriptor}, * defines the layouts associated with the parameter types and return type (if any) of the C function. *

    * Scalar C types such as {@code bool}, {@code int} are modelled as {@linkplain ValueLayout value layouts} - * of a suitable carrier. The mapping between a scalar type and its corresponding layout is dependent on the ABI - * implemented by the native linker. For instance, the C type {@code long} maps to the layout constant - * {@link ValueLayout#JAVA_LONG} on Linux/x64, but maps to the layout constant {@link ValueLayout#JAVA_INT} on - * Windows/x64. Similarly, the C type {@code size_t} maps to the layout constant {@link ValueLayout#JAVA_LONG} - * on 64-bit platforms, but maps to the layout constant {@link ValueLayout#JAVA_INT} on 32-bit platforms. + * of a suitable carrier. The {@linkplain #canonicalLayouts() mapping} between a scalar type and its corresponding + * canonical layout is dependent on the ABI implemented by the native linker (see below). *

    * Composite types are modelled as {@linkplain GroupLayout group layouts}. More specifically, a C {@code struct} type * maps to a {@linkplain StructLayout struct layout}, whereas a C {@code union} type maps to a {@link UnionLayout union * layout}. When defining a struct or union layout, clients must pay attention to the size and alignment constraint * of the corresponding composite type definition in C. For instance, padding between two struct fields @@ -121,11 +122,37 @@ * {@linkplain AddressLayout address layouts}. When the spatial bounds of the pointer type are known statically, * the address layout can be associated with a {@linkplain AddressLayout#targetLayout() target layout}. For instance, * a pointer that is known to point to a C {@code int[2]} array can be modelled as an address layout whose * target layout is a sequence layout whose element count is 2, and whose element type is {@link ValueLayout#JAVA_INT}. *

    - * The following table shows some examples of how C types are modelled in Linux/x64: + * All native linker implementations are guaranteed to provide canonical layouts for the following set of types: + *

    + * As noted above, the specific canonical layout associated with each type can vary, depending on the data model + * supported by a given ABI. For instance, the C type {@code long} maps to the layout constant {@link ValueLayout#JAVA_LONG} + * on Linux/x64, but maps to the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. Similarly, the C type + * {@code size_t} maps to the layout constant {@link ValueLayout#JAVA_LONG} on 64-bit platforms, but maps to the layout + * constant {@link ValueLayout#JAVA_INT} on 32-bit platforms. + *

    + * A native linker typically does not provide canonical layouts for C's unsigned integral types. Instead, they are + * modelled using the canonical layouts associated with their corresponding signed integral types. For instance, + * the C type {@code unsigned long} maps to the layout constant {@link ValueLayout#JAVA_LONG} on Linux/x64, but maps to + * the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. + *

    + * The following table shows some examples of how C types are modelled in Linux/x64 (all the examples provided + * here will assume these platform-dependent mappings): * *

    * * * @@ -136,23 +163,23 @@ * * * * * - * + * * * - * + * * * - * + * * * - * + * * * - * + * * * * * * @@ -199,24 +226,11 @@ *
    Mapping C types
    {@code bool}{@link ValueLayout#JAVA_BOOLEAN}{@code boolean}
    {@code char}
    {@code char}
    {@code unsigned char}
    {@link ValueLayout#JAVA_BYTE}{@code byte}
    {@code short}
    {@code short}
    {@code unsigned short}
    {@link ValueLayout#JAVA_SHORT}{@code short}
    {@code int}
    {@code int}
    {@code unsigned int}
    {@link ValueLayout#JAVA_INT}{@code int}
    {@code long}
    {@code long}
    {@code unsigned long}
    {@link ValueLayout#JAVA_LONG}{@code long}
    {@code long long}
    {@code long long}
    {@code unsigned long long}
    {@link ValueLayout#JAVA_LONG}{@code long}
    {@code float}{@link ValueLayout#JAVA_FLOAT}{@code float}
    *

    * All native linker implementations operate on a subset of memory layouts. More formally, a layout {@code L} * is supported by a native linker {@code NL} if: *

    * Any attempt to provide a layout path {@code P} that is not well-formed for an initial layout {@code C_0} will result * in an {@link IllegalArgumentException}. * + *

    Access mode restrictions

    + * + * A var handle returned by {@link #varHandle(PathElement...)} or {@link ValueLayout#varHandle()} features certain + * access characteristics, which are derived from the selected layout {@code L}: + * + * Depending on the above characteristics, the returned var handle might feature certain access mode restrictions. + * We say that a var handle is aligned if its alignment constraint {@code A} is compatible with the access size + * {@code S}, that is if {@code A >= S}. An aligned var handle is guaranteed to support the following access modes: + * + * If {@code T} is {@code float}, {@code double} or {@link MemorySegment} then atomic update access modes compare + * values using their bitwise representation (see {@link Float#floatToRawIntBits}, {@link Double#doubleToRawLongBits} + * and {@link MemorySegment#address()}, respectively). + *

    + * Alternatively, a var handle is unaligned if its alignment constraint {@code A} is incompatible with the + * access size {@code S}, that is, if {@code A < S}. An unaligned var handle only supports the {@code get} and {@code set} + * access modes. All other access modes will result in {@link UnsupportedOperationException} being thrown. Moreover, + * while supported, access modes {@code get} and {@code set} might lead to word tearing. + * * @implSpec * Implementations of this interface are immutable, thread-safe and value-based. * * @sealedGraph - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface MemoryLayout permits SequenceLayout, GroupLayout, PaddingLayout, ValueLayout { /** * {@return the layout size, in bytes} */ @@ -290,10 +327,48 @@ * @param byteAlignment the layout alignment constraint, expressed in bytes. * @throws IllegalArgumentException if {@code byteAlignment} is not a power of two. */ MemoryLayout withByteAlignment(long byteAlignment); + /** + * {@return {@code offset + (byteSize() * index)}} + * + * @param offset the base offset + * @param index the index to be scaled by the byte size of this layout + * @throws IllegalArgumentException if {@code offset} or {@code index} is negative + * @throws ArithmeticException if either the addition or multiplication overflows + */ + @ForceInline + default long scale(long offset, long index) { + if (offset < 0) { + throw new IllegalArgumentException("Negative offset: " + offset); + } + if (index < 0) { + throw new IllegalArgumentException("Negative index: " + index); + } + + return Math.addExact(offset, Math.multiplyExact(byteSize(), index)); + } + + /** + *{@return a method handle that can be used to invoke {@link #scale(long, long)} on this layout} + */ + default MethodHandle scaleHandle() { + class Holder { + static final MethodHandle MH_SCALE; + static { + try { + MH_SCALE = MethodHandles.lookup().findVirtual(MemoryLayout.class, "scale", + MethodType.methodType(long.class, long.class, long.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + } + return Holder.MH_SCALE.bindTo(this); + } + /** * Computes the offset, in bytes, of the layout selected by the given layout path, where the initial layout in the * path is this layout. * * @param elements the layout path elements. @@ -312,25 +387,27 @@ * by the given layout path, where the initial layout in the path is this layout. *

    * The returned method handle has the following characteristics: *

    *

    * The final offset returned by the method handle is computed as follows: * *

    {@code
    -     * offset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
    +     * offset = b + c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
          * }
    * - * where {@code x_1}, {@code x_2}, ... {@code x_n} are dynamic values provided as {@code long} - * arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} are static offset constants - * and {@code s_0}, {@code s_1}, ... {@code s_n} are static stride constants which are derived from - * the layout path. + * where {@code b} represents the base offset provided as a dynamic {@code long} argument, {@code x_1}, {@code x_2}, + * ... {@code x_n} represent indices into sequences provided as dynamic {@code long} arguments, whereas + * {@code s_1}, {@code s_2}, ... {@code s_n} are static stride constants derived from the size of the element + * layout of a sequence, and {@code c_1}, {@code c_2}, ... {@code c_m} are other static offset constants + * (such as field offsets) which are derived from the layout path. * * @apiNote The returned method handle can be used to compute a layout offset, similarly to {@link #byteOffset(PathElement...)}, * but more flexibly, as some indices can be specified when invoking the method handle. * * @param elements the layout path elements. @@ -349,75 +426,97 @@ *

    * The returned var handle has the following characteristics: *

    *

    - * The final address accessed by the returned var handle can be computed as follows: - * - *

    {@code
    -     * address = base(segment) + offset
    -     * }
    - * - * Where {@code base(segment)} denotes a function that returns the physical base address of the accessed - * memory segment. For native segments, this function just returns the native segment's - * {@linkplain MemorySegment#address() address}. For heap segments, this function is more complex, as the address - * of heap segments is virtualized. The {@code offset} value can be expressed in the following form: - * - *
    {@code
    -     * offset = c_1 + c_2 + ... + c_m + (x_1 * s_1) + (x_2 * s_2) + ... + (x_n * s_n)
    -     * }
    + * If the provided layout path {@code P} contains no dereference elements, then the offset of the access operation is + * computed as follows: * - * where {@code x_1}, {@code x_2}, ... {@code x_n} are dynamic values provided as {@code long} - * arguments, whereas {@code c_1}, {@code c_2}, ... {@code c_m} are static offset constants - * and {@code s_1}, {@code s_2}, ... {@code s_n} are static stride constants which are derived from - * the layout path. + * {@snippet lang = "java": + * offset = this.offsetHandle(P).invokeExact(B, I1, I2, ... In); + * } *

    - * Additionally, the provided dynamic values must conform to bounds which are derived from the layout path, that is, - * {@code 0 <= x_i < b_i}, where {@code 1 <= i <= n}, or {@link IndexOutOfBoundsException} is thrown. + * Accessing a memory segment using the var handle returned by this method is subject to the following checks: + *

    *

    - * The base address must be aligned according to the {@linkplain - * #byteAlignment() alignment constraint} of the root layout (this layout). Note that this can be more strict - * (but not less) than the alignment constraint of the selected value layout. + * If the selected layout is an {@linkplain AddressLayout address layout}, calling {@link VarHandle#get(Object...)} + * on the returned var handle will return a new memory segment. The segment is associated with a scope that is + * always alive. Moreover, the size of the segment depends on whether the address layout has a + * {@linkplain AddressLayout#targetLayout() target layout}. More specifically: + *

    + * Moreover, if the selected layout is an {@linkplain AddressLayout address layout}, calling {@link VarHandle#set(Object...)} + * can throw {@link IllegalArgumentException} if the memory segment representing the address to be written is not a + * {@linkplain MemorySegment#isNative() native} memory segment. *

    - * Multiple paths can be chained, with dereference path elements. - * A dereference path element constructs a fresh native memory segment whose base address is the address value - * read obtained by accessing a memory segment at the offset determined by the layout path elements immediately preceding - * the dereference path element. In other words, if a layout path contains one or more dereference path elements, - * the final address accessed by the returned var handle can be computed as follows: + * If the provided layout path has size {@code m} and contains a dereference path element in position {@code k} + * (where {@code k <= m}) then two layout paths {@code P} and {@code P'} are derived, where P contains all the path + * elements from 0 to {@code k - 1} and {@code P'} contains all the path elements from {@code k + 1} to + * {@code m} (if any). Then, the returned var handle is computed as follows: * - *

    {@code
    -     * address_1 = base(segment) + offset_1
    -     * address_2 = base(segment_1) + offset_2
    -     * ...
    -     * address_k = base(segment_k-1) + offset_k
    -     * }
    + * {@snippet lang = "java": + * VarHandle baseHandle = this.varHandle(P); + * MemoryLayout target = ((AddressLayout)this.select(P)).targetLayout().get(); + * VarHandle targetHandle = target.varHandle(P'); + * targetHandle = MethodHandles.insertCoordinates(targetHandle, 1, 0L); // always access nested targets at offset 0 + * targetHandle = MethodHandles.collectCoordinates(targetHandle, 0, + * baseHandle.toMethodHandle(VarHandle.AccessMode.GET)); + * } * - * where {@code k} is the number of dereference path elements in a layout path, {@code segment} is the input segment, - * {@code segment_1}, ... {@code segment_k-1} are the segments obtained by dereferencing the address associated with - * a given dereference path element (e.g. {@code segment_1} is a native segment whose base address is {@code address_1}), - * and {@code offset_1}, {@code offset_2}, ... {@code offset_k} are the offsets computed by evaluating - * the path elements after a given dereference operation (these offsets are obtained using the computation described - * above). In these more complex access operations, all memory accesses immediately preceding a dereference operation - * (e.g. those at addresses {@code address_1}, {@code address_2}, ..., {@code address_k-1} are performed using the - * {@link VarHandle.AccessMode#GET} access mode. + * (The above can be trivially generalized to cases where the provided layout path contains more than one dereference + * path elements). + *

    + * As an example, consider the memory layout expressed by a {@link GroupLayout} instance constructed as follows: + * {@snippet lang = "java": + * GroupLayout grp = java.lang.foreign.MemoryLayout.structLayout( + * MemoryLayout.paddingLayout(4), + * ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN).withName("value") + * ); + * } + * To access the member layout named {@code value}, we can construct a var handle as follows: + * {@snippet lang = "java": + * VarHandle handle = grp.varHandle(PathElement.groupElement("value")); //(MemorySegment, long) -> int + * } * - * @apiNote The resulting var handle features certain access mode restrictions, which are common to all - * {@linkplain MethodHandles#memorySegmentViewVarHandle(ValueLayout) memory segment view handles}. + * @apiNote The resulting var handle features certain access mode restrictions, + * which are common to all var handles derived from memory layouts. * * @param elements the layout path elements. * @return a var handle that accesses a memory segment at the offset selected by the given layout path. * @throws IllegalArgumentException if the layout path is not well-formed for this layout. * @throws IllegalArgumentException if the layout selected by the provided path is not a {@linkplain ValueLayout value layout}. - * @see MethodHandles#memorySegmentViewVarHandle(ValueLayout) */ default VarHandle varHandle(PathElement... elements) { + Objects.requireNonNull(elements); + if (this instanceof ValueLayout vl && elements.length == 0) { + return vl.varHandle(); // fast path + } return computePathOp(LayoutPath.rootPath(this), LayoutPath::dereferenceHandle, Set.of(), elements); } /** @@ -425,27 +524,31 @@ * corresponding to the layout selected by the given layout path, where the initial layout in the path is this layout. *

    * The returned method handle has the following characteristics: *

    *

    - * The offset of the returned segment is computed as follows: - * {@snippet lang=java : - * long offset = byteOffset(elements); - * long size = select(elements).byteSize(); - * MemorySegment slice = segment.asSlice(offset, size); - * } + * The offset of the returned segment is computed as if by a call to a + * {@linkplain #byteOffsetHandle(PathElement...) byte offset handle} constructed using the given path elements. *

    - * The segment to be sliced must be aligned according to the - * {@linkplain #byteAlignment() alignment constraint} of the root layout (this layout). Note that this can be more - * strict (but not less) than the alignment constraint of the selected value layout. + * Computing a slice of a memory segment using the method handle returned by this method is subject to the following checks: + *

    * * @apiNote The returned method handle can be used to obtain a memory segment slice, similarly to {@link MemorySegment#asSlice(long, long)}, * but more flexibly, as some indices can be specified when invoking the method handle. * * @param elements the layout path elements. @@ -499,13 +602,12 @@ * open path elements. * * @implSpec * Implementations of this interface are immutable, thread-safe and value-based. * - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) sealed interface PathElement permits LayoutPath.PathElementImpl { /** * Returns a path element which selects a member layout with the given name in a group layout. * @@ -667,28 +769,10 @@ Utils.checkElementAlignment(elementLayout, "Element layout size is not multiple of alignment"); return Utils.wrapOverflow(() -> SequenceLayoutImpl.of(elementCount, elementLayout)); } - /** - * Creates a sequence layout with the given element layout and the maximum element - * count such that it does not overflow a {@code long}. - * - * This is equivalent to the following code: - * {@snippet lang = java: - * sequenceLayout(Long.MAX_VALUE / elementLayout.byteSize(), elementLayout); - * } - * - * @param elementLayout the sequence element layout. - * @return a new sequence layout with the given element layout and maximum element count. - * @throws IllegalArgumentException if {@code elementLayout.byteSize() % elementLayout.byteAlignment() != 0}. - */ - static SequenceLayout sequenceLayout(MemoryLayout elementLayout) { - Objects.requireNonNull(elementLayout); - return sequenceLayout(Long.MAX_VALUE / elementLayout.byteSize(), elementLayout); - } - /** * Creates a struct layout with the given member layouts. * * @param elements The member layouts of the struct layout. * @return a struct layout with the given member layouts. diff a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -24,19 +24,17 @@ */ package java.lang.foreign; import java.io.UncheckedIOException; -import java.lang.foreign.Linker.Option; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.CharBuffer; import java.nio.channels.FileChannel; -import java.nio.channels.FileChannel.*; +import java.nio.channels.FileChannel.MapMode; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Objects; import java.util.Optional; import java.util.Spliterator; @@ -44,14 +42,12 @@ import java.util.stream.Stream; import jdk.internal.foreign.AbstractMemorySegmentImpl; import jdk.internal.foreign.HeapMemorySegmentImpl; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.NativeMemorySegmentImpl; +import jdk.internal.foreign.StringSupport; import jdk.internal.foreign.Utils; -import jdk.internal.foreign.abi.SharedUtils; -import jdk.internal.foreign.layout.ValueLayouts; -import jdk.internal.javac.PreviewFeature; import jdk.internal.javac.Restricted; import jdk.internal.reflect.CallerSensitive; import jdk.internal.vm.annotation.ForceInline; /** @@ -126,36 +122,51 @@ * {@snippet lang=java : * MemorySegment segment = ... * int value = segment.get(ValueLayout.JAVA_INT.withOrder(BIG_ENDIAN), 0); * } * - * For more complex access operations (e.g. structured memory access), clients can obtain a - * {@linkplain MethodHandles#memorySegmentViewVarHandle(ValueLayout) var handle} - * that accepts a segment and a {@code long} offset. More complex var handles - * can be obtained by adapting a segment var handle view using the var handle combinator functions defined in the - * {@link java.lang.invoke.MethodHandles} class: + * More complex access operations can be implemented using var handles. The {@link ValueLayout#varHandle()} + * method can be used to obtain a var handle that can be used to get/set values represented by the given value layout on a memory segment. + * A var handle obtained from a layout supports several additional + * access modes. More importantly, var handles can be combined with method handles to express complex access + * operations. For instance, a var handle that can be used to access an element of an {@code int} array at a given logical + * index can be created as follows: * - * {@snippet lang=java : + * {@snippet lang=java: * MemorySegment segment = ... - * VarHandle intHandle = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_INT); - * MethodHandle multiplyExact = MethodHandles.lookup() - * .findStatic(Math.class, "multiplyExact", - * MethodType.methodType(long.class, long.class, long.class)); - * intHandle = MethodHandles.filterCoordinates(intHandle, 1, - * MethodHandles.insertArguments(multiplyExact, 0, ValueLayout.JAVA_INT.byteSize())); - * int value = (int) intHandle.get(segment, 3L); // get int element at offset 3 * 4 = 12 + * VarHandle intHandle = ValueLayout.JAVA_INT.varHandle(); // (MemorySegment, long) + * MethodHandle scale = ValueLayout.JAVA_INT.scaleHandle(); // + * JAVA_INT.byteSize() + * + * intHandle = MethodHandles.filterCoordinates(intHandle, 1, scale); + * int value = (int) intHandle.get(segment, 0L, 3L); // get int element at offset 0 + 3 * 4 = 12 * } * - * Alternatively, complex var handles can can be obtained - * from {@linkplain MemoryLayout#varHandle(MemoryLayout.PathElement...) memory layouts} - * by providing a so called layout path: + * To make the process of creating these var handles easier, the method + * {@link MemoryLayout#varHandle(MemoryLayout.PathElement...)} can be used, by providing it a so called + * layout path. A layout path, consisting of several layout + * path elements, selects a value layout to be accessed, which can be nested inside another memory layout. For example, + * we can express the access to an element of an {@code int} array using layout paths like so: * * {@snippet lang=java : * MemorySegment segment = ... - * VarHandle intHandle = ValueLayout.JAVA_INT.arrayElementVarHandle(); - * int value = (int) intHandle.get(segment, 3L); // get int element at offset 3 * 4 = 12 + * MemoryLayout segmentLayout = MemoryLayout.structLayout( + * ValueLayout.JAVA_INT.withName("size"), + * MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_INT).withName("data") // array of 4 elements + * ); + * VarHandle intHandle = segmentLayout.varHandle(MemoryLayout.PathElemenet.groupElement("data"), + * MemoryLayout.PathElement.sequenceElement()); + * int value = (int) intHandle.get(segment, 0L, 3L); // get int element at offset 0 + offsetof(data) + 3 * 4 = 12 * } + * Where {@code offsetof(data)} is the offset of the {@code data} element layout of the {@code segmentLayout} layout + * + * Both the var handle returned by {@link ValueLayout#varHandle()} and + * {@link MemoryLayout#varHandle(MemoryLayout.PathElement...)}, as well as the method handle returned by + * {@link MemoryLayout#byteOffsetHandle(MemoryLayout.PathElement...)} and {@link MemoryLayout#sliceHandle(MemoryLayout.PathElement...)} + * feature a base offset parameter. This parameter represents a base offset for the offset computation. This + * parameter allows a client to combine these handles further with additional offset computations. This is demonstrated + * in the first of the two examples above, where {@code intHandle} is combined with a + * {@linkplain MemoryLayout#scaleHandle() scale handle} obtained from {@code ValueLayout.JAVA_INT}. * *

    Slicing memory segments

    * * Memory segments support {@linkplain MemorySegment#asSlice(long, long) slicing}. Slicing a memory segment * returns a new memory segment that is backed by the same region of memory as the original. The address of the sliced @@ -370,11 +381,11 @@ *
  • The size of the segment is zero. any attempt to access these segments will fail with {@link IndexOutOfBoundsException}. * This is a crucial safety feature: as these segments are associated with a region * of memory whose size is not known, any access operations involving these segments cannot be validated. * In effect, a zero-length memory segment wraps an address, and it cannot be used without explicit intent * (see below);
  • - *
  • The segment is associated with a fresh scope that is always alive. Thus, while zero-length + *
  • The segment is associated with a scope that is always alive. Thus, while zero-length * memory segments cannot be accessed directly, they can be passed, opaquely, to other pointer-accepting foreign functions.
  • * *

    * To demonstrate how clients can work with zero-length memory segments, consider the case of a client that wants * to read a pointer from some memory segment. This can be done via the @@ -432,13 +443,12 @@ * the memory segment. * * @implSpec * Implementations of this interface are immutable, thread-safe and value-based. * - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * {@return the address of this memory segment} * @@ -595,12 +605,11 @@ /** * Returns a new memory segment that has the same address and scope as this segment, but with the provided size. *

    * This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on - * restricted methods, and use safe and supported functionalities, where possible. + * the JVM or, worse, silently result in memory corruption. * * @param newSize the size of the returned segment. * @return a new memory segment that has the same address and scope as this segment, but the new * provided size. * @throws IllegalArgumentException if {@code newSize < 0}. @@ -624,17 +633,16 @@ * invalid. This cleanup action receives a fresh memory segment that is obtained from this segment as follows: * {@snippet lang=java : * MemorySegment cleanupSegment = MemorySegment.ofAddress(this.address()) * .reinterpret(byteSize()); * } - * That is, the cleanup action receives a segment that is associated with a fresh scope that is always alive, + * That is, the cleanup action receives a segment that is associated with a scope that is always alive, * and is accessible from any thread. The size of the segment accepted by the cleanup action is {@link #byteSize()}. *

    * This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on - * restricted methods, and use safe and supported functionalities, where possible. + * the JVM or, worse, silently result in memory corruption. * * @apiNote The cleanup action (if present) should take care not to leak the received segment to external * clients which might access the segment after its backing region of memory is no longer available. Furthermore, * if the provided scope is the scope of an {@linkplain Arena#ofAuto() automatic arena}, the cleanup action * must not prevent the scope from becoming unreachable. @@ -664,17 +672,16 @@ * invalid. This cleanup action receives a fresh memory segment that is obtained from this segment as follows: * {@snippet lang=java : * MemorySegment cleanupSegment = MemorySegment.ofAddress(this.address()) * .reinterpret(newSize); * } - * That is, the cleanup action receives a segment that is associated with a fresh scope that is always alive, + * That is, the cleanup action receives a segment that is associated with a scope that is always alive, * and is accessible from any thread. The size of the segment accepted by the cleanup action is {@code newSize}. *

    * This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on - * restricted methods, and use safe and supported functionalities, where possible. + * the JVM or, worse, silently result in memory corruption. * * @apiNote The cleanup action (if present) should take care not to leak the received segment to external * clients which might access the segment after its backing region of memory is no longer available. Furthermore, * if the provided scope is the scope of an {@linkplain Arena#ofAuto() automatic arena}, the cleanup action * must not prevent the scope from becoming unreachable. @@ -737,43 +744,19 @@ * @param other the segment to test for an overlap with this segment. * @return a slice of this segment (where overlapping occurs). */ Optional asOverlappingSlice(MemorySegment other); - /** - * Returns the offset, in bytes, of the provided segment, relative to this - * segment. - * - *

    The offset is relative to the address of this segment and can be - * a negative or positive value. For instance, if both segments are native - * segments, or heap segments backed by the same array, the resulting offset - * can be computed as follows: - * - * {@snippet lang=java : - * other.address() - address() - * } - * - * If the segments share the same address, {@code 0} is returned. If - * {@code other} is a slice of this segment, the offset is always - * {@code 0 <= x < this.byteSize()}. - * - * @param other the segment to retrieve an offset to. - * @throws UnsupportedOperationException if the two segments cannot be compared, e.g. because they are of - * different kinds, or because they are backed by different Java arrays. - * @return the relative offset, in bytes, of the provided segment. - */ - long segmentOffset(MemorySegment other); - /** * Fills the contents of this memory segment with the given value. *

    * More specifically, the given value is written into each address of this * segment. Equivalent to (but likely more efficient than) the following code: * * {@snippet lang=java : * for (long offset = 0; offset < segment.byteSize(); offset++) { - * byteHandle.set(ValueLayout.JAVA_BYTE, offset, value); + * segment.set(ValueLayout.JAVA_BYTE, offset, value); * } * } * * But without any regard or guarantees on the ordering of particular memory * elements being set. @@ -1070,56 +1053,127 @@ * e.g. because {@code byteSize() % 8 != 0}, or {@code byteSize() / 8 > Integer.MAX_VALUE}. */ double[] toArray(ValueLayout.OfDouble elementLayout); /** - * Reads a UTF-8 encoded, null-terminated string from this segment at the given offset. + * Reads a null-terminated string from this segment at the given offset, using the + * {@linkplain StandardCharsets#UTF_8 UTF-8} charset. *

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

    * This method always replaces malformed-input and unmappable-character * sequences with this charset's default replacement string. The {@link * java.nio.charset.CharsetDecoder} class should be used when more control * over the decoding process is required. + * + * @param offset offset in bytes (relative to this segment address) at which this access operation will occur. + * @param charset the charset used to {@linkplain Charset#newDecoder() decode} the string bytes. + * @return a Java string constructed from the bytes read from the given starting address up to (but not including) + * the first {@code '\0'} terminator character (assuming one is found). + * @throws IllegalArgumentException if the size of the string is greater than the largest string supported by the platform. + * @throws IndexOutOfBoundsException if {@code offset < 0}. + * @throws IndexOutOfBoundsException if {@code offset > byteSize() - (B + N)}, where: + *

      + *
    • {@code B} is the size, in bytes, of the string encoded using the provided charset + * (e.g. {@code str.getBytes(charset).length});
    • + *
    • {@code N} is the size (in bytes) of the terminator char according to the provided charset. For instance, + * this is 1 for {@link StandardCharsets#US_ASCII} and 2 for {@link StandardCharsets#UTF_16}.
    • + *
    + * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not + * {@linkplain Scope#isAlive() alive}. + * @throws WrongThreadException if this method is called from a thread {@code T}, + * such that {@code isAccessibleBy(T) == false}. + * @throws UnsupportedOperationException if {@code charset} is not a {@linkplain StandardCharsets standard charset}. + */ + default String getString(long offset, Charset charset) { + Objects.requireNonNull(charset); + return StringSupport.read(this, offset, charset); + } + + /** + * Writes the given string into this segment at the given offset, converting it to a null-terminated byte sequence + * using the {@linkplain StandardCharsets#UTF_8 UTF-8} charset. *

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

    + * This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + *

    + * If the given string contains any {@code '\0'} characters, they will be + * copied as well. This means that, depending on the method used to read + * the string, such as {@link MemorySegment#getString(long)}, the string + * will appear truncated when read again. + * + * @param offset offset in bytes (relative to this segment address) at which this access operation will occur. + * the final address of this write operation can be expressed as {@code address() + offset}. + * @param str the Java string to be written into this segment. + * @param charset the charset used to {@linkplain Charset#newEncoder() encode} the string bytes. + * @throws IndexOutOfBoundsException if {@code offset < 0}. + * @throws IndexOutOfBoundsException if {@code offset > byteSize() - (B + N)}, where: + *

      + *
    • {@code B} is the size, in bytes, of the string encoded using the provided charset + * (e.g. {@code str.getBytes(charset).length});
    • + *
    • {@code N} is the size (in bytes) of the terminator char according to the provided charset. For instance, + * this is 1 for {@link StandardCharsets#US_ASCII} and 2 for {@link StandardCharsets#UTF_16}.
    • + *
    + * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not + * {@linkplain Scope#isAlive() alive}. + * @throws WrongThreadException if this method is called from a thread {@code T}, + * such that {@code isAccessibleBy(T) == false}. + * @throws UnsupportedOperationException if {@code charset} is not a {@linkplain StandardCharsets standard charset}. + */ + default void setString(long offset, String str, Charset charset) { + Objects.requireNonNull(charset); + Objects.requireNonNull(str); + StringSupport.write(this, offset, charset, str); + } /** * Creates a memory segment that is backed by the same region of memory that backs the given {@link Buffer} instance. * The segment starts relative to the buffer's position (inclusive) and ends relative to the buffer's limit (exclusive). *

    @@ -1279,12 +1333,13 @@ * such that {@code srcSegment.isAccessibleBy(T) == false}. * @throws IllegalStateException if the {@linkplain #scope() scope} associated with {@code dstSegment} is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code dstSegment.isAccessibleBy(T) == false}. - * @throws IndexOutOfBoundsException if {@code srcOffset > srcSegment.byteSize() - bytes} or if - * {@code dstOffset > dstSegment.byteSize() - bytes}, or if either {@code srcOffset}, {@code dstOffset} + * @throws IndexOutOfBoundsException if {@code srcOffset > srcSegment.byteSize() - bytes}. + * @throws IndexOutOfBoundsException if {@code dstOffset > dstSegment.byteSize() - bytes}. + * @throws IndexOutOfBoundsException if either {@code srcOffset}, {@code dstOffset} * or {@code bytes} are {@code < 0}. * @throws UnsupportedOperationException if {@code dstSegment} is {@linkplain #isReadOnly() read-only}. */ @ForceInline static void copy(MemorySegment srcSegment, long srcOffset, @@ -1318,21 +1373,25 @@ * @param dstElementLayout the element layout associated with the destination segment. * @param dstOffset the starting offset, in bytes, of the destination segment. * @param elementCount the number of elements to be copied. * @throws IllegalArgumentException if the element layouts have different sizes, if the source (resp. destination) segment/offset are * incompatible with the alignment constraint in the source - * (resp. destination) element layout, or if the source (resp. destination) element layout alignment is greater than its size. + * (resp. destination) element layout. + * @throws IllegalArgumentException if {@code srcElementLayout.byteAlignment() > srcElementLayout.byteSize()}. + * @throws IllegalArgumentException if {@code dstElementLayout.byteAlignment() > dstElementLayout.byteSize()}. * @throws IllegalStateException if the {@linkplain #scope() scope} associated with {@code srcSegment} is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, - * such that {@code srcSegment().isAccessibleBy(T) == false}. + * such that {@code srcSegment.isAccessibleBy(T) == false}. * @throws IllegalStateException if the {@linkplain #scope() scope} associated with {@code dstSegment} is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, - * such that {@code dstSegment().isAccessibleBy(T) == false}. + * such that {@code dstSegment.isAccessibleBy(T) == false}. * @throws UnsupportedOperationException if {@code dstSegment} is {@linkplain #isReadOnly() read-only}. - * @throws IndexOutOfBoundsException if {@code elementCount * srcLayout.byteSize()} or {@code elementCount * dtsLayout.byteSize()} overflows. + * @throws IndexOutOfBoundsException if {@code elementCount * srcLayout.byteSize()} overflows. + * @throws IndexOutOfBoundsException if {@code elementCount * dtsLayout.byteSize()} overflows. + * @throws IndexOutOfBoundsException if {@code srcOffset > srcSegment.byteSize() - (elementCount * srcLayout.byteSize())}. * @throws IndexOutOfBoundsException if {@code dstOffset > dstSegment.byteSize() - (elementCount * dstLayout.byteSize())}. * @throws IndexOutOfBoundsException if either {@code srcOffset}, {@code dstOffset} or {@code elementCount} are {@code < 0}. */ @ForceInline static void copy(MemorySegment srcSegment, ValueLayout srcElementLayout, long srcOffset, @@ -1359,11 +1418,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default byte get(ValueLayout.OfByte layout, long offset) { - return (byte) ((ValueLayouts.OfByteImpl) layout).accessHandle().get(this, offset); + return (byte) layout.varHandle().get(this, offset); } /** * Writes a byte into this segment at the given offset, with the given layout. * @@ -1379,11 +1438,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfByte layout, long offset, byte value) { - ((ValueLayouts.OfByteImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a boolean from this segment at the given offset, with the given layout. * @@ -1398,11 +1457,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default boolean get(ValueLayout.OfBoolean layout, long offset) { - return (boolean) ((ValueLayouts.OfBooleanImpl) layout).accessHandle().get(this, offset); + return (boolean) layout.varHandle().get(this, offset); } /** * Writes a boolean into this segment at the given offset, with the given layout. * @@ -1418,11 +1477,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfBoolean layout, long offset, boolean value) { - ((ValueLayouts.OfBooleanImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a char from this segment at the given offset, with the given layout. * @@ -1437,11 +1496,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default char get(ValueLayout.OfChar layout, long offset) { - return (char) ((ValueLayouts.OfCharImpl) layout).accessHandle().get(this, offset); + return (char) layout.varHandle().get(this, offset); } /** * Writes a char into this segment at the given offset, with the given layout. * @@ -1457,11 +1516,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfChar layout, long offset, char value) { - ((ValueLayouts.OfCharImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a short from this segment at the given offset, with the given layout. * @@ -1476,11 +1535,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default short get(ValueLayout.OfShort layout, long offset) { - return (short) ((ValueLayouts.OfShortImpl) layout).accessHandle().get(this, offset); + return (short) layout.varHandle().get(this, offset); } /** * Writes a short into this segment at the given offset, with the given layout. * @@ -1496,11 +1555,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfShort layout, long offset, short value) { - ((ValueLayouts.OfShortImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads an int from this segment at the given offset, with the given layout. * @@ -1515,11 +1574,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default int get(ValueLayout.OfInt layout, long offset) { - return (int) ((ValueLayouts.OfIntImpl) layout).accessHandle().get(this, offset); + return (int) layout.varHandle().get(this, offset); } /** * Writes an int into this segment at the given offset, with the given layout. * @@ -1535,11 +1594,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfInt layout, long offset, int value) { - ((ValueLayouts.OfIntImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a float from this segment at the given offset, with the given layout. * @@ -1554,11 +1613,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default float get(ValueLayout.OfFloat layout, long offset) { - return (float)((ValueLayouts.OfFloatImpl) layout).accessHandle().get(this, offset); + return (float)layout.varHandle().get(this, offset); } /** * Writes a float into this segment at the given offset, with the given layout. * @@ -1574,11 +1633,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfFloat layout, long offset, float value) { - ((ValueLayouts.OfFloatImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a long from this segment at the given offset, with the given layout. * @@ -1593,11 +1652,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default long get(ValueLayout.OfLong layout, long offset) { - return (long) ((ValueLayouts.OfLongImpl) layout).accessHandle().get(this, offset); + return (long) layout.varHandle().get(this, offset); } /** * Writes a long into this segment at the given offset, with the given layout. * @@ -1613,11 +1672,11 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfLong layout, long offset, long value) { - ((ValueLayouts.OfLongImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a double from this segment at the given offset, with the given layout. * @@ -1632,11 +1691,11 @@ * incompatible with the alignment constraint in the provided layout. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default double get(ValueLayout.OfDouble layout, long offset) { - return (double) ((ValueLayouts.OfDoubleImpl) layout).accessHandle().get(this, offset); + return (double) layout.varHandle().get(this, offset); } /** * Writes a double into this segment at the given offset, with the given layout. * @@ -1652,16 +1711,16 @@ * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void set(ValueLayout.OfDouble layout, long offset, double value) { - ((ValueLayouts.OfDoubleImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads an address from this segment at the given offset, with the given layout. The read address is wrapped in - * a native segment, associated with a fresh scope that is always alive. Under normal conditions, + * a native segment, associated with a scope that is always alive. Under normal conditions, * the size of the returned segment is {@code 0}. However, if the provided address layout has a * {@linkplain AddressLayout#targetLayout() target layout} {@code T}, then the size of the returned segment * is set to {@code T.byteSize()}. * @param layout the layout of the region of memory to be read. * @param offset offset in bytes (relative to this segment address) at which this access operation will occur. @@ -1677,11 +1736,11 @@ * incompatible with the alignment constraint in {@code T}. * @throws IndexOutOfBoundsException if {@code offset > byteSize() - layout.byteSize()}. */ @ForceInline default MemorySegment get(AddressLayout layout, long offset) { - return (MemorySegment) ((ValueLayouts.OfAddressImpl) layout).accessHandle().get(this, offset); + return (MemorySegment) layout.varHandle().get(this, offset); } /** * Writes an address into this segment at the given offset, with the given layout. * @@ -1698,11 +1757,11 @@ * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. * @throws UnsupportedOperationException if {@code value} is not a {@linkplain #isNative() native} segment. */ @ForceInline default void set(AddressLayout layout, long offset, MemorySegment value) { - ((ValueLayouts.OfAddressImpl) layout).accessHandle().set(this, offset, value); + layout.varHandle().set(this, offset, value); } /** * Reads a byte from this segment at the given index, scaled by the given layout size. * @@ -1713,20 +1772,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default byte getAtIndex(ValueLayout.OfByte layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (byte) ((ValueLayouts.OfByteImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (byte) layout.varHandle().get(this, index * layout.byteSize()); } /** * Reads a boolean from this segment at the given index, scaled by the given layout size. * @@ -1737,20 +1796,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default boolean getAtIndex(ValueLayout.OfBoolean layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (boolean) ((ValueLayouts.OfBooleanImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (boolean) layout.varHandle().get(this, index * layout.byteSize()); } /** * Reads a char from this segment at the given index, scaled by the given layout size. * @@ -1761,20 +1820,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default char getAtIndex(ValueLayout.OfChar layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (char) ((ValueLayouts.OfCharImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (char) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes a char into this segment at the given index, scaled by the given layout size. * @@ -1785,21 +1844,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfChar layout, long index, char value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfCharImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Reads a short from this segment at the given index, scaled by the given layout size. * @@ -1810,20 +1869,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default short getAtIndex(ValueLayout.OfShort layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (short) ((ValueLayouts.OfShortImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (short) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes a byte into this segment at the given index, scaled by the given layout size. * @@ -1834,21 +1893,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfByte layout, long index, byte value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfByteImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Writes a boolean into this segment at the given index, scaled by the given layout size. @@ -1860,21 +1919,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfBoolean layout, long index, boolean value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfBooleanImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Writes a short into this segment at the given index, scaled by the given layout size. * @@ -1885,21 +1944,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfShort layout, long index, short value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfShortImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Reads an int from this segment at the given index, scaled by the given layout size. * @@ -1910,20 +1969,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default int getAtIndex(ValueLayout.OfInt layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (int) ((ValueLayouts.OfIntImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (int) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes an int into this segment at the given index, scaled by the given layout size. * @@ -1934,21 +1993,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfInt layout, long index, int value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfIntImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Reads a float from this segment at the given index, scaled by the given layout size. * @@ -1959,20 +2018,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default float getAtIndex(ValueLayout.OfFloat layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (float) ((ValueLayouts.OfFloatImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (float) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes a float into this segment at the given index, scaled by the given layout size. * @@ -1983,21 +2042,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfFloat layout, long index, float value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfFloatImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Reads a long from this segment at the given index, scaled by the given layout size. * @@ -2008,20 +2067,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default long getAtIndex(ValueLayout.OfLong layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (long) ((ValueLayouts.OfLongImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (long) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes a long into this segment at the given index, scaled by the given layout size. * @@ -2032,21 +2091,21 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfLong layout, long index, long value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfLongImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Reads a double from this segment at the given index, scaled by the given layout size. * @@ -2057,20 +2116,20 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default double getAtIndex(ValueLayout.OfDouble layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (double) ((ValueLayouts.OfDoubleImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (double) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes a double into this segment at the given index, scaled by the given layout size. * @@ -2081,26 +2140,26 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. */ @ForceInline default void setAtIndex(ValueLayout.OfDouble layout, long index, double value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfDoubleImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Reads an address from this segment at the given at the given index, scaled by the given layout size. The read address is wrapped in - * a native segment, associated with a fresh scope that is always alive. Under normal conditions, + * a native segment, associated with a scope that is always alive. Under normal conditions, * the size of the returned segment is {@code 0}. However, if the provided address layout has a * {@linkplain AddressLayout#targetLayout() target layout} {@code T}, then the size of the returned segment * is set to {@code T.byteSize()}. * @param layout the layout of the region of memory to be read. * @param index a logical index. The offset in bytes (relative to this segment address) at which the access operation @@ -2109,23 +2168,23 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IllegalArgumentException if provided address layout has a {@linkplain AddressLayout#targetLayout() target layout} * {@code T}, and the address of the returned segment * incompatible with the alignment constraint in {@code T}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. */ @ForceInline default MemorySegment getAtIndex(AddressLayout layout, long index) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - return (MemorySegment) ((ValueLayouts.OfAddressImpl) layout).accessHandle().get(this, index * layout.byteSize()); + return (MemorySegment) layout.varHandle().get(this, index * layout.byteSize()); } /** * Writes an address into this segment at the given index, scaled by the given layout size. * @@ -2136,22 +2195,22 @@ * @throws IllegalStateException if the {@linkplain #scope() scope} associated with this segment is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, * such that {@code isAccessibleBy(T) == false}. * @throws IllegalArgumentException if the access operation is - * incompatible with the alignment constraint in the provided layout, - * or if the layout alignment is greater than its size. + * incompatible with the alignment constraint in the provided layout. + * @throws IllegalArgumentException if {@code layout.byteAlignment() > layout.byteSize()}. * @throws IndexOutOfBoundsException if {@code index * byteSize()} overflows. * @throws IndexOutOfBoundsException if {@code index * byteSize() > byteSize() - layout.byteSize()}. * @throws UnsupportedOperationException if this segment is {@linkplain #isReadOnly() read-only}. * @throws UnsupportedOperationException if {@code value} is not a {@linkplain #isNative() native} segment. */ @ForceInline default void setAtIndex(AddressLayout layout, long index, MemorySegment value) { Utils.checkElementAlignment(layout, "Layout alignment greater than its size"); // note: we know size is a small value (as it comes from ValueLayout::byteSize()) - ((ValueLayouts.OfAddressImpl) layout).accessHandle().set(this, index * layout.byteSize(), value); + layout.varHandle().set(this, index * layout.byteSize(), value); } /** * Compares the specified object with this memory segment for equality. Returns {@code true} if and only if the specified * object is also a memory segment, and if the two segments refer to the same location, in some region of memory. @@ -2196,11 +2255,11 @@ * @param dstIndex the starting index of the destination array. * @param elementCount the number of array elements to be copied. * @throws IllegalStateException if the {@linkplain #scope() scope} associated with {@code srcSegment} is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, - * such that {@code srcSegment().isAccessibleBy(T) == false}. + * such that {@code srcSegment.isAccessibleBy(T) == false}. * @throws IllegalArgumentException if {@code dstArray} is not an array, or if it is an array but whose type is not supported. * @throws IllegalArgumentException if the destination array component type does not match {@code srcLayout.carrier()}. * @throws IllegalArgumentException if {@code offset} is incompatible * with the alignment constraint in the source element layout. * @throws IllegalArgumentException if {@code srcLayout.byteAlignment() > srcLayout.byteSize()}. @@ -2235,11 +2294,11 @@ * @param dstOffset the starting offset, in bytes, of the destination segment. * @param elementCount the number of array elements to be copied. * @throws IllegalStateException if the {@linkplain #scope() scope} associated with {@code dstSegment} is not * {@linkplain Scope#isAlive() alive}. * @throws WrongThreadException if this method is called from a thread {@code T}, - * such that {@code dstSegment().isAccessibleBy(T) == false}. + * such that {@code dstSegment.isAccessibleBy(T) == false}. * @throws IllegalArgumentException if {@code srcArray} is not an array, or if it is an array but whose type is not supported. * @throws IllegalArgumentException if the source array component type does not match {@code srcLayout.carrier()}. * @throws IllegalArgumentException if {@code offset} is incompatible * with the alignment constraint in the source element layout. * @throws IllegalArgumentException if {@code dstLayout.byteAlignment() > dstLayout.byteSize()}. @@ -2309,12 +2368,31 @@ * cannot be accessed if its associated scope is not {@linkplain #isAlive() alive}. A new scope is typically * obtained indirectly, by creating a new {@linkplain Arena arena}. *

    * Scope instances can be compared for equality. That is, two scopes * are considered {@linkplain #equals(Object)} if they denote the same lifetime. + *

    + * If two memory segments are obtained from the same {@linkplain #ofBuffer(Buffer) buffer} + * or {@linkplain #ofArray(int[]) array}, the scopes associated with said segments are considered + * {@linkplain #equals(Object) equal}, as the two segments have the same lifetime: + * {@snippet lang=java : + * byte[] arr = new byte[10]; + * MemorySegment segment1 = MemorySegment.ofArray(arr); + * MemorySegment segment2 = MemorySegment.ofArray(arr); + * assert segment1.scope().equals(segment2.scope()); + * } + *

    + * If two distinct memory segments are zero-length memory segments, their scopes + * are always considered {@linkplain #equals(Object) equal}: + * {@snippet lang=java : + * MemorySegment segment1 = MemorySegment.ofAddress(42L); + * MemorySegment segment2 = MemorySegment.ofAddress(1000L); + * assert segment1.scope().equals(segment2.scope()); + * } + * The scope of a zero-length memory segment can always be overridden using the + * {@link MemorySegment#reinterpret(Arena, Consumer)} method. */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) sealed interface Scope permits MemorySessionImpl { /** * {@return {@code true}, if the regions of memory backing the memory segments associated with this scope are * still valid} */ diff a/src/java.base/share/classes/java/lang/foreign/PaddingLayout.java b/src/java.base/share/classes/java/lang/foreign/PaddingLayout.java --- a/src/java.base/share/classes/java/lang/foreign/PaddingLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/PaddingLayout.java @@ -24,22 +24,20 @@ */ package java.lang.foreign; import jdk.internal.foreign.layout.PaddingLayoutImpl; -import jdk.internal.javac.PreviewFeature; /** * A padding layout. A padding layout specifies the size of extra space which is typically not accessed by applications, * and is typically used for aligning member layouts around word boundaries. * * @implSpec * Implementing classes are immutable, thread-safe and value-based. * - * @since 20 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface PaddingLayout extends MemoryLayout permits PaddingLayoutImpl { /** * {@inheritDoc} */ diff a/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java b/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java --- a/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java +++ b/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java @@ -23,20 +23,20 @@ * questions. */ package java.lang.foreign; -import java.lang.invoke.VarHandle; -import java.lang.reflect.Array; import java.nio.ByteOrder; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Objects; -import java.util.function.Function; + import jdk.internal.foreign.AbstractMemorySegmentImpl; +import jdk.internal.foreign.ArenaImpl; import jdk.internal.foreign.SlicingAllocator; -import jdk.internal.foreign.Utils; -import jdk.internal.javac.PreviewFeature; +import jdk.internal.foreign.StringSupport; +import jdk.internal.vm.annotation.ForceInline; /** * An object that may be used to allocate {@linkplain MemorySegment memory segments}. Clients implementing this interface * must implement the {@link #allocate(long, long)} method. A segment allocator defines several methods * which can be useful to create segments from several kinds of Java values such as primitives and arrays. @@ -68,36 +68,72 @@ * only interact with a segment allocator they own. *

    * Clients should consider using an {@linkplain Arena arena} instead, which, provides strong thread-safety, * lifetime and non-overlapping guarantees. * - * @since 19 + * @since 22 */ @FunctionalInterface -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public interface SegmentAllocator { /** - * {@return a new memory segment with a Java string converted into a UTF-8 encoded, null-terminated C string} + * Converts a Java string into a null-terminated C string using the {@linkplain StandardCharsets#UTF_8 UTF-8} charset, + * storing the result into a memory segment. + *

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

    * This method always replaces malformed-input and unmappable-character * sequences with this charset's default replacement byte array. The * {@link java.nio.charset.CharsetEncoder} class should be used when more * control over the encoding process is required. *

    * If the given string contains any {@code '\0'} characters, they will be * copied as well. This means that, depending on the method used to read - * the string, such as {@link MemorySegment#getUtf8String(long)}, the string + * the string, such as {@link MemorySegment#getString(long)}, the string * will appear truncated when read again. * + * @param str the Java string to be converted into a C string. + * @param charset the charset used to {@linkplain Charset#newEncoder() encode} the string bytes. + * @return a new native segment containing the converted C string. + * @throws UnsupportedOperationException if {@code charset} is not a {@linkplain StandardCharsets standard charset}. * @implSpec The default implementation for this method copies the contents of the provided Java string - * into a new memory segment obtained by calling {@code this.allocate(str.length() + 1)}. - * @param str the Java string to be converted into a C string. + * into a new memory segment obtained by calling {@code this.allocate(B + N)}, where: + *

      + *
    • {@code B} is the size, in bytes, of the string encoded using the provided charset + * (e.g. {@code str.getBytes(charset).length});
    • + *
    • {@code N} is the size (in bytes) of the terminator char according to the provided charset. For instance, + * this is 1 for {@link StandardCharsets#US_ASCII} and 2 for {@link StandardCharsets#UTF_16}.
    • + *
    */ - default MemorySegment allocateUtf8String(String str) { + @ForceInline + default MemorySegment allocateFrom(String str, Charset charset) { + Objects.requireNonNull(charset); Objects.requireNonNull(str); - return Utils.toCString(str.getBytes(StandardCharsets.UTF_8), this); + int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize(); + byte[] bytes = str.getBytes(charset); + MemorySegment segment = allocateNoInit(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); + } + return segment; } /** * {@return a new memory segment initialized with the provided {@code byte} {@code value} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} @@ -110,15 +146,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfByte layout, byte value) { + default MemorySegment allocateFrom(ValueLayout.OfByte layout, byte value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the provided {@code char} {@code value} as @@ -132,15 +167,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfChar layout, char value) { + default MemorySegment allocateFrom(ValueLayout.OfChar layout, char value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the provided {@code short} {@code value} as @@ -154,15 +188,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfShort layout, short value) { + default MemorySegment allocateFrom(ValueLayout.OfShort layout, short value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the provided {@code int} {@code value} as @@ -176,15 +209,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfInt layout, int value) { + default MemorySegment allocateFrom(ValueLayout.OfInt layout, int value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the provided {@code float} {@code value} as @@ -198,15 +230,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfFloat layout, float value) { + default MemorySegment allocateFrom(ValueLayout.OfFloat layout, float value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the provided {@code long} {@code value} as @@ -220,15 +251,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfLong layout, long value) { + default MemorySegment allocateFrom(ValueLayout.OfLong layout, long value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the provided {@code double} {@code value} as @@ -242,15 +272,14 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(ValueLayout.OfDouble layout, double value) { + default MemorySegment allocateFrom(ValueLayout.OfDouble layout, double value) { Objects.requireNonNull(layout); - VarHandle handle = layout.varHandle(); - MemorySegment seg = allocate(layout); - handle.set(seg, value); + MemorySegment seg = allocateNoInit(layout); + seg.set(layout, 0, value); return seg; } /** * {@return a new memory segment initialized with the address of the provided {@code value} as @@ -267,167 +296,196 @@ * } * * @param layout the layout of the block of memory to be allocated. * @param value the value to be set in the newly allocated memory segment. */ - default MemorySegment allocate(AddressLayout layout, MemorySegment value) { + default MemorySegment allocateFrom(AddressLayout layout, MemorySegment value) { Objects.requireNonNull(value); Objects.requireNonNull(layout); - MemorySegment seg = allocate(layout); - layout.varHandle().set(seg, value); - return seg; + MemorySegment segment = allocateNoInit(layout); + segment.set(layout, 0, value); + return segment; + } + + /** + * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of + * {@code elementCount*elementLayout.byteSize()} initialized with the contents of the provided {@code source} segment + * as specified by the provided {@code elementLayout} (i.e. byte ordering, alignment and size)} + * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * MemorySegment dest = this.allocate(elementLayout, elementCount); + * MemorySegment.copy(source, sourceElementLayout, sourceOffset, dest, elementLayout, 0, elementCount); + * return dest; + * } + * @param elementLayout the element layout of the allocated array. + * @param source the source segment. + * @param sourceElementLayout the element layout of the source segment. + * @param sourceOffset the starting offset, in bytes, of the source segment. + * @param elementCount the number of elements in the source segment to be copied. + * @throws IllegalArgumentException if {@code elementLayout.byteSize() != sourceElementLayout.byteSize()}. + * @throws IllegalArgumentException if the source segment/offset are incompatible with the alignment constraint + * in the source element layout. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. + * @throws IllegalArgumentException if {@code sourceElementLayout.byteAlignment() > sourceElementLayout.byteSize()}. + * @throws IllegalStateException if the {@linkplain MemorySegment#scope() scope} associated with {@code source} is not + * {@linkplain MemorySegment.Scope#isAlive() alive}. + * @throws WrongThreadException if this method is called from a thread {@code T}, + * such that {@code source.isAccessibleBy(T) == false}. + * @throws IndexOutOfBoundsException if {@code elementCount * sourceElementLayout.byteSize()} overflows. + * @throws IndexOutOfBoundsException if {@code sourceOffset > source.byteSize() - (elementCount * sourceElementLayout.byteSize())}. + * @throws IndexOutOfBoundsException if either {@code sourceOffset} or {@code elementCount} are {@code < 0}. + */ + @ForceInline + default MemorySegment allocateFrom(ValueLayout elementLayout, MemorySegment source, + ValueLayout sourceElementLayout, long sourceOffset, long elementCount) { + Objects.requireNonNull(source); + Objects.requireNonNull(sourceElementLayout); + Objects.requireNonNull(elementLayout); + MemorySegment dest = allocateNoInit(elementLayout, elementCount); + MemorySegment.copy(source, sourceElementLayout, sourceOffset, dest, elementLayout, 0, elementCount); + return dest; } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code byte} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_BYTE, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. - * @param elements the short elements to be copied to the newly allocated memory block. + * @param elements the byte elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfByte elementLayout, byte... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfByte elementLayout, byte... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_BYTE, 0, elements.length); } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code short} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_SHORT, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. * @param elements the short elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfShort elementLayout, short... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfShort elementLayout, short... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_SHORT, 0, elements.length); } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code char} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_CHAR, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. - * @param elements the short elements to be copied to the newly allocated memory block. + * @param elements the char elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfChar elementLayout, char... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfChar elementLayout, char... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_CHAR, 0, elements.length); } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code int} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_INT, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. - * @param elements the short elements to be copied to the newly allocated memory block. + * @param elements the int elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfInt elementLayout, int... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfInt elementLayout, int... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_INT, 0, elements.length); } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code float} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_FLOAT, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. - * @param elements the short elements to be copied to the newly allocated memory block. + * @param elements the float elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfFloat elementLayout, float... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfFloat elementLayout, float... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_FLOAT, 0, elements.length); } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code long} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_LONG, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. - * @param elements the short elements to be copied to the newly allocated memory block. + * @param elements the long elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfLong elementLayout, long... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfLong elementLayout, long... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_LONG, 0, elements.length); } /** * {@return a new memory segment with a {@linkplain MemorySegment#byteSize() byteSize()} of * {@code E*layout.byteSize()} initialized with the provided {@code E} {@code double} {@code elements} as * specified by the provided {@code layout} (i.e. byte ordering, alignment and size)} * - * @implSpec The default implementation is equivalent to: - * {@snippet lang=java : - * int size = Objects.requireNonNull(elements).length; - * MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - * MemorySegment.copy(elements, 0, seg, elementLayout, 0, size); - * return seg; - * } - * + * @implSpec the default implementation for this method is equivalent to the following code: + * {@snippet lang = java: + * this.allocateFrom(layout, MemorySegment.ofArray(array), + * ValueLayout.JAVA_DOUBLE, 0, array.length) + *} * @param elementLayout the element layout of the array to be allocated. - * @param elements the short elements to be copied to the newly allocated memory block. + * @param elements the double elements to be copied to the newly allocated memory block. + * @throws IllegalArgumentException if {@code elementLayout.byteAlignment() > elementLayout.byteSize()}. */ - default MemorySegment allocateArray(ValueLayout.OfDouble elementLayout, double... elements) { - return copyArrayWithSwapIfNeeded(elements, elementLayout, MemorySegment::ofArray); - } - - private MemorySegment copyArrayWithSwapIfNeeded(Z array, ValueLayout elementLayout, - Function heapSegmentFactory) { - int size = Array.getLength(Objects.requireNonNull(array)); - MemorySegment seg = allocateArray(Objects.requireNonNull(elementLayout), size); - if (size > 0) { - MemorySegment.copy(heapSegmentFactory.apply(array), elementLayout, 0, - seg, elementLayout.withOrder(ByteOrder.nativeOrder()), 0, size); - } - return seg; + @ForceInline + default MemorySegment allocateFrom(ValueLayout.OfDouble elementLayout, double... elements) { + return allocateFrom(elementLayout, MemorySegment.ofArray(elements), + ValueLayout.JAVA_DOUBLE, 0, elements.length); } /** * {@return a new memory segment with the given layout} * @@ -450,11 +508,11 @@ * @param elementLayout the array element layout. * @param count the array element count. * @throws IllegalArgumentException if {@code elementLayout.byteSize() * count} overflows. * @throws IllegalArgumentException if {@code count < 0}. */ - default MemorySegment allocateArray(MemoryLayout elementLayout, long count) { + default MemorySegment allocate(MemoryLayout elementLayout, long count) { Objects.requireNonNull(elementLayout); if (count < 0) { throw new IllegalArgumentException("Negative array size"); } return allocate(MemoryLayout.sequenceLayout(count, elementLayout)); @@ -523,6 +581,27 @@ * @return an allocator which recycles an existing segment upon each new allocation request. */ static SegmentAllocator prefixAllocator(MemorySegment segment) { return (AbstractMemorySegmentImpl)Objects.requireNonNull(segment); } + + @ForceInline + private MemorySegment allocateNoInit(long byteSize) { + return this instanceof ArenaImpl arenaImpl ? + arenaImpl.allocateNoInit(byteSize, 1) : + allocate(byteSize); + } + + @ForceInline + private MemorySegment allocateNoInit(MemoryLayout layout) { + return this instanceof ArenaImpl arenaImpl ? + arenaImpl.allocateNoInit(layout.byteSize(), layout.byteAlignment()) : + allocate(layout); + } + + @ForceInline + private MemorySegment allocateNoInit(MemoryLayout layout, long size) { + return this instanceof ArenaImpl arenaImpl ? + arenaImpl.allocateNoInit(layout.byteSize() * size, layout.byteAlignment()) : + allocate(layout, size); + } } diff a/src/java.base/share/classes/java/lang/foreign/SequenceLayout.java b/src/java.base/share/classes/java/lang/foreign/SequenceLayout.java --- a/src/java.base/share/classes/java/lang/foreign/SequenceLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/SequenceLayout.java @@ -24,11 +24,10 @@ */ package java.lang.foreign; import jdk.internal.foreign.layout.SequenceLayoutImpl; -import jdk.internal.javac.PreviewFeature; /** * A compound layout that denotes a homogeneous repetition of a given element layout. * The repetition count is said to be the sequence layout's element count. A sequence layout can be thought of as a * struct layout where the sequence layout's element layout is repeated a number of times that is equal to the sequence @@ -48,13 +47,12 @@ * } * * @implSpec * This class is immutable, thread-safe and value-based. * - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface SequenceLayout extends MemoryLayout permits SequenceLayoutImpl { /** * {@return the element layout of this sequence layout} diff a/src/java.base/share/classes/java/lang/foreign/StructLayout.java b/src/java.base/share/classes/java/lang/foreign/StructLayout.java --- a/src/java.base/share/classes/java/lang/foreign/StructLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/StructLayout.java @@ -24,21 +24,19 @@ */ package java.lang.foreign; import jdk.internal.foreign.layout.StructLayoutImpl; -import jdk.internal.javac.PreviewFeature; /** * A group layout whose member layouts are laid out one after the other. * * @implSpec * Implementing classes are immutable, thread-safe and value-based. * - * @since 20 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface StructLayout extends GroupLayout permits StructLayoutImpl { /** * {@inheritDoc} */ diff a/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java b/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java --- a/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java +++ b/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java @@ -27,11 +27,10 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.Utils; -import jdk.internal.javac.PreviewFeature; import jdk.internal.javac.Restricted; import jdk.internal.loader.BuiltinClassLoader; import jdk.internal.loader.NativeLibrary; import jdk.internal.loader.RawNativeLibraries; import jdk.internal.reflect.CallerSensitive; @@ -118,13 +117,12 @@ * Linker nativeLinker = Linker.nativeLinker(); * SymbolLookup stdlib = nativeLinker.defaultLookup(); * MemorySegment malloc = stdlib.find("malloc").orElseThrow(); *} * - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) @FunctionalInterface public interface SymbolLookup { /** * Returns the address of the symbol with the given name. @@ -165,11 +163,11 @@ * current: it reflects all the libraries associated with the relevant class loader, even if they were loaded after * this method returned. *

    * Libraries associated with a class loader are unloaded when the class loader becomes * unreachable. The symbol lookup - * returned by this method is associated with a fresh {@linkplain MemorySegment.Scope scope} which keeps the caller's + * returned by this method is associated with a {@linkplain MemorySegment.Scope scope} which keeps the caller's * class loader reachable. Therefore, libraries associated with the caller's class loader are kept loaded * (and their symbols available) as long as a loader lookup for that class loader, or any of the segments * obtained by it, is reachable. *

    * In cases where this method is called from a context where there is no caller frame on the stack @@ -189,11 +187,11 @@ ClassLoader.getSystemClassLoader(); Arena loaderArena;// builtin loaders never go away if ((loader == null || loader instanceof BuiltinClassLoader)) { loaderArena = Arena.global(); } else { - MemorySessionImpl session = MemorySessionImpl.heapSession(loader); + MemorySessionImpl session = MemorySessionImpl.createHeap(loader); loaderArena = session.asArena(); } return name -> { Objects.requireNonNull(name); if (Utils.containsNullChars(name)) return Optional.empty(); @@ -214,12 +212,11 @@ * associated with the returned lookup will be unloaded when the provided confined arena is * {@linkplain Arena#close() closed}. *

    * This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on - * restricted methods, and use safe and supported functionalities, where possible. + * the JVM or, worse, silently result in memory corruption. * * @implNote The process of resolving a library name is OS-specific. For instance, in a POSIX-compliant OS, * the library name is resolved according to the specification of the {@code dlopen} function for that OS. * In Windows, the library name is resolved according to the specification of the {@code LoadLibrary} function. * @@ -249,12 +246,11 @@ * associated with the returned lookup will be unloaded when the provided confined arena is * {@linkplain Arena#close() closed}. *

    * This method is restricted. * Restricted methods are unsafe, and, if used incorrectly, their use might crash - * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on - * restricted methods, and use safe and supported functionalities, where possible. + * the JVM or, worse, silently result in memory corruption. * * @implNote On Linux, the functionalities provided by this factory method and the returned symbol lookup are * implemented using the {@code dlopen}, {@code dlsym} and {@code dlclose} functions. * @param path the path of the library in which symbols should be looked up. * @param arena the arena associated with symbols obtained from the returned lookup. diff a/src/java.base/share/classes/java/lang/foreign/UnionLayout.java b/src/java.base/share/classes/java/lang/foreign/UnionLayout.java --- a/src/java.base/share/classes/java/lang/foreign/UnionLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/UnionLayout.java @@ -24,21 +24,19 @@ */ package java.lang.foreign; import jdk.internal.foreign.layout.UnionLayoutImpl; -import jdk.internal.javac.PreviewFeature; /** * A group layout whose member layouts are laid out at the same starting offset. * * @implSpec * Implementing classes are immutable, thread-safe and value-based. * - * @since 20 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface UnionLayout extends GroupLayout permits UnionLayoutImpl { /** * {@inheritDoc} */ diff a/src/java.base/share/classes/java/lang/foreign/ValueLayout.java b/src/java.base/share/classes/java/lang/foreign/ValueLayout.java --- a/src/java.base/share/classes/java/lang/foreign/ValueLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/ValueLayout.java @@ -23,16 +23,13 @@ * questions. */ package java.lang.foreign; -import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.nio.ByteOrder; - import jdk.internal.foreign.layout.ValueLayouts; -import jdk.internal.javac.PreviewFeature; /** * A layout that models values of basic data types. Examples of values modelled by a value layout are * integral values (either signed or unsigned), floating-point values and * address values. @@ -49,13 +46,12 @@ * 4 bytes on 32-bit platforms. * * @implSpec implementing classes and subclasses are immutable, thread-safe and value-based. * * @sealedGraph - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public sealed interface ValueLayout extends MemoryLayout permits ValueLayout.OfBoolean, ValueLayout.OfByte, ValueLayout.OfChar, ValueLayout.OfShort, ValueLayout.OfInt, ValueLayout.OfFloat, ValueLayout.OfLong, ValueLayout.OfDouble, AddressLayout { /** @@ -74,70 +70,10 @@ * {@inheritDoc} */ @Override ValueLayout withoutName(); - /** - * Creates a strided var handle that can be used to access a memory segment as multi-dimensional - * array. This array has a notional sequence layout featuring {@code shape.length} nested sequence layouts. The element - * layout of the innermost sequence layout in the notional sequence layout is this value layout. The resulting var handle - * is obtained as if calling the {@link #varHandle(PathElement...)} method on the notional layout, with a layout - * path containing exactly {@code shape.length + 1} {@linkplain PathElement#sequenceElement() open sequence layout path elements}. - *

    - * For instance, the following method call: - * - * {@snippet lang=java : - * VarHandle arrayHandle = ValueLayout.JAVA_INT.arrayElementVarHandle(10, 20); - * } - * - * Is equivalent to the following code: - * - * {@snippet lang = java: - * SequenceLayout notionalLayout = MemoryLayout.sequenceLayout( - * MemoryLayout.sequenceLayout(10, MemoryLayout.sequenceLayout(20, ValueLayout.JAVA_INT))); - * VarHandle arrayHandle = notionalLayout.varHandle(PathElement.sequenceElement(), - * PathElement.sequenceElement(), - * PathElement.sequenceElement()); - *} - * - * The resulting var handle {@code arrayHandle} will feature 3 coordinates of type {@code long}; each coordinate - * is interpreted as an index into the corresponding sequence layout. If we refer to the var handle coordinates, from left - * to right, as {@code x}, {@code y} and {@code z} respectively, the final offset accessed by the var handle can be - * computed with the following formula: - * - *

    {@code
    -     * offset = (10 * 20 * 4 * x) + (20 * 4 * y) + (4 * z)
    -     * }
    - * - * Additionally, the values of {@code x}, {@code y} and {@code z} are constrained as follows: - *
      - *
    • {@code 0 <= x < notionalLayout.elementCount() }
    • - *
    • {@code 0 <= y < 10 }
    • - *
    • {@code 0 <= z < 20 }
    • - *
    - *

    - * Consider the following access expressions: - * {@snippet lang=java : - * int value1 = (int) arrayHandle.get(10, 2, 4); // ok, accessed offset = 8176 - * int value2 = (int) arrayHandle.get(0, 0, 30); // out of bounds value for z - * } - * In the first case, access is well-formed, as the values for {@code x}, {@code y} and {@code z} conform to - * the bounds specified above. In the second case, access fails with {@link IndexOutOfBoundsException}, - * as the value for {@code z} is outside its specified bounds. - * - * @param shape the size of each nested array dimension. - * @return a var handle which can be used to access a memory segment as a multi-dimensional array, - * featuring {@code shape.length + 1} - * {@code long} coordinates. - * @throws IllegalArgumentException if {@code shape[i] < 0}, for at least one index {@code i}. - * @throws UnsupportedOperationException if {@code byteAlignment() > byteSize()}. - * @see MethodHandles#memorySegmentViewVarHandle - * @see MemoryLayout#varHandle(PathElement...) - * @see SequenceLayout - */ - VarHandle arrayElementVarHandle(int... shape); - /** * {@return the carrier associated with this value layout} */ Class carrier(); @@ -147,23 +83,44 @@ @Override ValueLayout withName(String name); /** * {@inheritDoc} + * * @throws IllegalArgumentException {@inheritDoc} */ @Override ValueLayout withByteAlignment(long byteAlignment); + /** + * {@return a var handle which can be used to access values described by this value layout, in a given memory segment.} + *

    + * The returned var handle's {@linkplain VarHandle#varType() var type} is the {@linkplain ValueLayout#carrier() carrier type} of + * this value layout, and the list of coordinate types is {@code (MemorySegment, long)}, where the memory segment coordinate + * corresponds to the memory segment to be accessed, and the {@code long} coordinate corresponds to the byte offset + * into the accessed memory segment at which the access occurs. + *

    + * The returned var handle checks that accesses are aligned according to this value layout's + * {@linkplain MemoryLayout#byteAlignment() alignment constraint}. + * + * @apiNote This method is similar, but more efficient, than calling {@code MemoryLayout#varHandle(PathElement...)} + * with an empty path element array, as it avoids the creation of the var args array. + * + * @apiNote The returned var handle features certain access mode + * restrictions common to all memory access var handles derived from memory layouts. + * + * @see MemoryLayout#varHandle(PathElement...) + */ + VarHandle varHandle(); + /** * A value layout whose carrier is {@code boolean.class}. * * @see #JAVA_BOOLEAN - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfBoolean extends ValueLayout permits ValueLayouts.OfBooleanImpl { + sealed interface OfBoolean extends ValueLayout permits ValueLayouts.OfBooleanImpl { /** * {@inheritDoc} */ @Override @@ -192,14 +149,13 @@ /** * A value layout whose carrier is {@code byte.class}. * * @see #JAVA_BYTE - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfByte extends ValueLayout permits ValueLayouts.OfByteImpl { + sealed interface OfByte extends ValueLayout permits ValueLayouts.OfByteImpl { /** * {@inheritDoc} */ @Override @@ -229,14 +185,13 @@ /** * A value layout whose carrier is {@code char.class}. * * @see #JAVA_CHAR * @see #JAVA_CHAR_UNALIGNED - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfChar extends ValueLayout permits ValueLayouts.OfCharImpl { + sealed interface OfChar extends ValueLayout permits ValueLayouts.OfCharImpl { /** * {@inheritDoc} */ @Override @@ -266,14 +221,13 @@ /** * A value layout whose carrier is {@code short.class}. * * @see #JAVA_SHORT * @see #JAVA_SHORT_UNALIGNED - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfShort extends ValueLayout permits ValueLayouts.OfShortImpl { + sealed interface OfShort extends ValueLayout permits ValueLayouts.OfShortImpl { /** * {@inheritDoc} */ @Override @@ -303,14 +257,13 @@ /** * A value layout whose carrier is {@code int.class}. * * @see #JAVA_INT * @see #JAVA_INT_UNALIGNED - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfInt extends ValueLayout permits ValueLayouts.OfIntImpl { + sealed interface OfInt extends ValueLayout permits ValueLayouts.OfIntImpl { /** * {@inheritDoc} */ @Override @@ -340,14 +293,13 @@ /** * A value layout whose carrier is {@code float.class}. * * @see #JAVA_FLOAT * @see #JAVA_FLOAT_UNALIGNED - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfFloat extends ValueLayout permits ValueLayouts.OfFloatImpl { + sealed interface OfFloat extends ValueLayout permits ValueLayouts.OfFloatImpl { /** * {@inheritDoc} */ @Override @@ -376,14 +328,13 @@ /** * A value layout whose carrier is {@code long.class}. * * @see #JAVA_LONG * @see #JAVA_LONG_UNALIGNED - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfLong extends ValueLayout permits ValueLayouts.OfLongImpl { + sealed interface OfLong extends ValueLayout permits ValueLayouts.OfLongImpl { /** * {@inheritDoc} */ @Override @@ -413,14 +364,13 @@ /** * A value layout whose carrier is {@code double.class}. * * @see #JAVA_DOUBLE * @see #JAVA_DOUBLE_UNALIGNED - * @since 19 + * @since 22 */ - @PreviewFeature(feature = PreviewFeature.Feature.FOREIGN) - sealed interface OfDouble extends ValueLayout permits ValueLayouts.OfDoubleImpl { + sealed interface OfDouble extends ValueLayout permits ValueLayouts.OfDoubleImpl { /** * {@inheritDoc} */ @Override diff a/src/java.base/share/classes/java/lang/foreign/package-info.java b/src/java.base/share/classes/java/lang/foreign/package-info.java --- a/src/java.base/share/classes/java/lang/foreign/package-info.java +++ b/src/java.base/share/classes/java/lang/foreign/package-info.java @@ -91,11 +91,11 @@ * stdlib.find("strlen").orElseThrow(), * FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS) * ); * * try (Arena arena = Arena.ofConfined()) { - * MemorySegment cString = arena.allocateUtf8String("Hello"); + * MemorySegment cString = arena.allocateFrom("Hello"); * long len = (long)strlen.invokeExact(cString); // 5 * } *} * * Here, we obtain a {@linkplain java.lang.foreign.Linker#nativeLinker() native linker} and we use it @@ -107,11 +107,11 @@ * From this information, the linker will uniquely determine the sequence of steps which will turn * the method handle invocation (here performed using {@link java.lang.invoke.MethodHandle#invokeExact(java.lang.Object...)}) * into a foreign function call, according to the rules specified by the ABI of the underlying platform. * The {@link java.lang.foreign.Arena} class also provides many useful methods for * interacting with foreign code, such as - * {@linkplain java.lang.foreign.SegmentAllocator#allocateUtf8String(java.lang.String) converting} Java strings into + * {@linkplain java.lang.foreign.SegmentAllocator#allocateFrom(java.lang.String) converting} Java strings into * zero-terminated, UTF-8 strings, as demonstrated in the above example. * *

    Restricted methods

    * Some methods in this package are considered restricted. Restricted methods are typically used to bind native * foreign data and/or functions to first-class Java API elements which can then be used directly by clients. For instance @@ -145,11 +145,9 @@ * restricted methods is only granted to the modules listed by that option. If this option is not specified, * access to restricted methods is enabled for all modules, but access to restricted methods will result in runtime warnings. * * @spec jni/index.html Java Native Interface Specification * - * @since 19 + * @since 22 */ -@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) package java.lang.foreign; -import jdk.internal.javac.PreviewFeature; diff a/src/java.base/share/classes/java/lang/foreign/snippet-files/Snippets.java b/src/java.base/share/classes/java/lang/foreign/snippet-files/Snippets.java --- a/src/java.base/share/classes/java/lang/foreign/snippet-files/Snippets.java +++ b/src/java.base/share/classes/java/lang/foreign/snippet-files/Snippets.java @@ -120,11 +120,11 @@ public static void main(String[] args) { // @start region="slicing-arena-main": try (Arena slicingArena = new SlicingArena(1000)) { for (int i = 0; i < 10; i++) { - MemorySegment s = slicingArena.allocateArray(JAVA_INT, 1, 2, 3, 4, 5); + MemorySegment s = slicingArena.allocateFrom(JAVA_INT, 1, 2, 3, 4, 5); // ... } } // all memory allocated is released here // @end } @@ -146,11 +146,11 @@ static class AddressLayoutSnippets { void withTargetLayout() { AddressLayout addressLayout = ADDRESS; AddressLayout unboundedLayout = addressLayout.withTargetLayout( - sequenceLayout(ValueLayout.JAVA_BYTE)); + sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE)); } } static class FunctionDescriptionSnippets { } @@ -166,11 +166,11 @@ linker.defaultLookup().find("strlen").orElseThrow(), FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); try (Arena arena = Arena.ofConfined()) { - MemorySegment str = arena.allocateUtf8String("Hello"); + MemorySegment str = arena.allocateFrom("Hello"); long len = (long) strlen.invokeExact(str); // 5 } } @@ -195,11 +195,11 @@ comparDesc.toMethodType()); try (Arena arena = Arena.ofConfined()) { MemorySegment compareFunc = linker.upcallStub(comparHandle, comparDesc, arena); - MemorySegment array = arena.allocateArray(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); + MemorySegment array = arena.allocateFrom(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7); qsort.invokeExact(array, 10L, 4L, compareFunc); int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] } } @@ -286,11 +286,11 @@ FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1) // first int is variadic ); try (Arena arena = Arena.ofConfined()) { - int res = (int) printf.invokeExact(arena.allocateUtf8String("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4" + int res = (int) printf.invokeExact(arena.allocateFrom("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4" } } void downcallHandle() { @@ -311,11 +311,11 @@ StructLayout capturedStateLayout = Linker.Option.captureStateLayout(); VarHandle errnoHandle = capturedStateLayout.varHandle(PathElement.groupElement("errno")); try (Arena arena = Arena.ofConfined()) { MemorySegment capturedState = arena.allocate(capturedStateLayout); handle.invoke(capturedState); - int errno = (int) errnoHandle.get(capturedState); + int errno = (int) errnoHandle.get(capturedState, 0L); // use errno } } void captureStateLayout() { @@ -349,12 +349,12 @@ VarHandle valueHandle = taggedValues.varHandle(PathElement.sequenceElement(), PathElement.groupElement("value")); MethodHandle offsetHandle = taggedValues.byteOffsetHandle(PathElement.sequenceElement(), PathElement.groupElement("kind")); - long offset1 = (long) offsetHandle.invokeExact(1L); // 8 - long offset2 = (long) offsetHandle.invokeExact(2L); // 16 + long offset1 = (long) offsetHandle.invokeExact(0L, 1L); // 8 + long offset2 = (long) offsetHandle.invokeExact(0L, 2L); // 16 } void sliceHandle() { MemorySegment segment = null; long offset = 0; @@ -394,11 +394,11 @@ } { MemorySegment segment = null; // ... - VarHandle intHandle = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_INT); + VarHandle intHandle = ValueLayout.JAVA_INT.varHandle(); MethodHandle multiplyExact = MethodHandles.lookup() .findStatic(Math.class, "multiplyExact", MethodType.methodType(long.class, long.class, long.class)); intHandle = MethodHandles.filterCoordinates(intHandle, 1, MethodHandles.insertArguments(multiplyExact, 0, ValueLayout.JAVA_INT.byteSize())); @@ -406,12 +406,17 @@ } { MemorySegment segment = null; // ... - VarHandle intHandle = ValueLayout.JAVA_INT.arrayElementVarHandle(); - int value = (int) intHandle.get(segment, 3L); // get int element at offset 3 * 4 = 12 + MemoryLayout segmentLayout = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("size"), + MemoryLayout.sequenceLayout(4, ValueLayout.JAVA_INT).withName("data") // array of 4 elements + ); + VarHandle intHandle = segmentLayout.varHandle(MemoryLayout.PathElement.groupElement("data"), + MemoryLayout.PathElement.sequenceElement()); + int value = (int) intHandle.get(segment, 0L, 3L); // get int element at offset 0 + offsetof(data) + 3 * 4 = 12 } { Arena arena = Arena.ofConfined(); MemorySegment segment = arena.allocate(100); @@ -522,14 +527,12 @@ void fill() { MemorySegment segment = null; byte value = 42; - var byteHandle = MemoryLayout.sequenceLayout(ValueLayout.JAVA_BYTE) - .varHandle(MemoryLayout.PathElement.sequenceElement()); for (long l = 0; l < segment.byteSize(); l++) { - byteHandle.set(segment.address(), l, value); + segment.set(JAVA_BYTE, l, value); } } void copyFrom() { MemorySegment src = null; @@ -568,11 +571,11 @@ stdlib.find("strlen").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS) ); try (Arena arena = Arena.ofConfined()) { - MemorySegment cString = arena.allocateUtf8String("Hello"); + MemorySegment cString = arena.allocateFrom("Hello"); long len = (long) strlen.invokeExact(cString); // 5 } } } @@ -652,21 +655,10 @@ static class UnionLayoutSnippets { } static class ValueLayoutSnippets { - void arrayElementVarHandle() { - VarHandle arrayHandle = ValueLayout.JAVA_INT.arrayElementVarHandle(10, 20); - - SequenceLayout arrayLayout = MemoryLayout.sequenceLayout( - MemoryLayout.sequenceLayout(10, - MemoryLayout.sequenceLayout(20, ValueLayout.JAVA_INT))); - - int value1 = (int) arrayHandle.get(10, 2, 4); // ok, accessed offset = 8176 - int value2 = (int) arrayHandle.get(0, 0, 30); // out of bounds value for z - } - void statics() { ADDRESS.withByteAlignment(1); JAVA_CHAR.withByteAlignment(1); JAVA_SHORT.withByteAlignment(1); JAVA_INT.withByteAlignment(1); diff a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -7976,89 +7976,10 @@ } return expectedType; } - /** - * Creates a var handle object, which can be used to dereference a {@linkplain java.lang.foreign.MemorySegment memory segment} - * at a given byte offset, using the provided value layout. - * - *

    The provided layout specifies the {@linkplain ValueLayout#carrier() carrier type}, - * the {@linkplain ValueLayout#byteSize() byte size}, - * the {@linkplain ValueLayout#byteAlignment() byte alignment} and the {@linkplain ValueLayout#order() byte order} - * associated with the returned var handle. - * - *

    The list of coordinate types associated with the returned var handle is {@code (MemorySegment, long)}, - * where the {@code long} coordinate type corresponds to byte offset into the given memory segment coordinate. - * Thus, the returned var handle accesses bytes at an offset in a given memory segment, composing bytes to or from - * a value of the var handle type. Moreover, the access operation will honor the endianness and the - * alignment constraints expressed in the provided layout. - * - *

    As an example, consider the memory layout expressed by a {@link GroupLayout} instance constructed as follows: - * {@snippet lang="java" : - * GroupLayout seq = java.lang.foreign.MemoryLayout.structLayout( - * MemoryLayout.paddingLayout(4), - * ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN).withName("value") - * ); - * } - * To access the member layout named {@code value}, we can construct a memory segment view var handle as follows: - * {@snippet lang="java" : - * VarHandle handle = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN)); //(MemorySegment, long) -> int - * handle = MethodHandles.insertCoordinates(handle, 1, 4); //(MemorySegment) -> int - * } - * - * @apiNote The resulting var handle features certain access mode restrictions, - * which are common to all memory segment view var handles. A memory segment view var handle is associated - * with an access size {@code S} and an alignment constraint {@code B} - * (both expressed in bytes). We say that a memory access operation is fully aligned if it occurs - * at a memory address {@code A} which is compatible with both alignment constraints {@code S} and {@code B}. - * If access is fully aligned then following access modes are supported and are - * guaranteed to support atomic access: - *

      - *
    • read write access modes for all {@code T}, with the exception of - * access modes {@code get} and {@code set} for {@code long} and - * {@code double} on 32-bit platforms. - *
    • atomic update access modes for {@code int}, {@code long}, - * {@code float}, {@code double} or {@link MemorySegment}. - * (Future major platform releases of the JDK may support additional - * types for certain currently unsupported access modes.) - *
    • numeric atomic update access modes for {@code int}, {@code long} and {@link MemorySegment}. - * (Future major platform releases of the JDK may support additional - * numeric types for certain currently unsupported access modes.) - *
    • bitwise atomic update access modes for {@code int}, {@code long} and {@link MemorySegment}. - * (Future major platform releases of the JDK may support additional - * numeric types for certain currently unsupported access modes.) - *
    - * - * If {@code T} is {@code float}, {@code double} or {@link MemorySegment} then atomic - * update access modes compare values using their bitwise representation - * (see {@link Float#floatToRawIntBits}, - * {@link Double#doubleToRawLongBits} and {@link MemorySegment#address()}, respectively). - *

    - * Alternatively, a memory access operation is partially aligned if it occurs at a memory address {@code A} - * which is only compatible with the alignment constraint {@code B}; in such cases, access for anything other than the - * {@code get} and {@code set} access modes will result in an {@code IllegalStateException}. If access is partially aligned, - * atomic access is only guaranteed with respect to the largest power of two that divides the GCD of {@code A} and {@code S}. - *

    - * In all other cases, we say that a memory access operation is misaligned; in such cases an - * {@code IllegalStateException} is thrown, irrespective of the access mode being used. - *

    - * Finally, if {@code T} is {@code MemorySegment} all write access modes throw {@link IllegalArgumentException} - * unless the value to be written is a {@linkplain MemorySegment#isNative() native} memory segment. - * - * @param layout the value layout for which a memory access handle is to be obtained. - * @return the new memory segment view var handle. - * @throws NullPointerException if {@code layout} is {@code null}. - * @see MemoryLayout#varHandle(MemoryLayout.PathElement...) - * @since 19 - */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) - public static VarHandle memorySegmentViewVarHandle(ValueLayout layout) { - Objects.requireNonNull(layout); - return Utils.makeSegmentViewVarHandle(layout); - } - /** * Adapts a target var handle by pre-processing incoming and outgoing values using a pair of filter functions. *

    * When calling e.g. {@link VarHandle#set(Object...)} on the resulting var handle, the incoming value (of type {@code T}, where * {@code T} is the last parameter type of the first filter function) is processed using the first filter and then passed @@ -8085,13 +8006,12 @@ * @return an adapter var handle which accepts a new type, performing the provided boxing/unboxing conversions. * @throws IllegalArgumentException if {@code filterFromTarget} and {@code filterToTarget} are not well-formed, that is, they have types * other than {@code (A... , S) -> T} and {@code (A... , T) -> S}, respectively, where {@code T} is the type of the target var handle, * or if it's determined that either {@code filterFromTarget} or {@code filterToTarget} throws any checked exceptions. * @throws NullPointerException if any of the arguments is {@code null}. - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public static VarHandle filterValue(VarHandle target, MethodHandle filterToTarget, MethodHandle filterFromTarget) { return VarHandles.filterValue(target, filterToTarget, filterFromTarget); } /** @@ -8121,13 +8041,12 @@ * other than {@code S1 -> T1, S2 -> T2, ... Sn -> Tn} where {@code T1, T2 ... Tn} are the coordinate types starting * at position {@code pos} of the target var handle, if {@code pos} is not between 0 and the target var handle coordinate arity, inclusive, * or if more filters are provided than the actual number of coordinate types available starting at {@code pos}, * or if it's determined that any of the filters throws any checked exceptions. * @throws NullPointerException if any of the arguments is {@code null} or {@code filters} contains {@code null}. - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public static VarHandle filterCoordinates(VarHandle target, int pos, MethodHandle... filters) { return VarHandles.filterCoordinates(target, pos, filters); } /** @@ -8153,13 +8072,12 @@ * or if more values are provided than the actual number of coordinate types available starting at {@code pos}. * @throws ClassCastException if the bound coordinates in {@code values} are not well-formed, that is, they have types * other than {@code T1, T2 ... Tn }, where {@code T1, T2 ... Tn} are the coordinate types starting at position {@code pos} * of the target var handle. * @throws NullPointerException if any of the arguments is {@code null} or {@code values} contains {@code null}. - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public static VarHandle insertCoordinates(VarHandle target, int pos, Object... values) { return VarHandles.insertCoordinates(target, pos, values); } /** @@ -8196,13 +8114,12 @@ * @throws IllegalArgumentException if the index array length is not equal to * the number of coordinates of the target var handle, or if any index array element is not a valid index for * a coordinate of {@code newCoordinates}, or if two corresponding coordinate types in * the target var handle and in {@code newCoordinates} are not identical. * @throws NullPointerException if any of the arguments is {@code null} or {@code newCoordinates} contains {@code null}. - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public static VarHandle permuteCoordinates(VarHandle target, List> newCoordinates, int... reorder) { return VarHandles.permuteCoordinates(target, newCoordinates, reorder); } /** @@ -8240,13 +8157,12 @@ * is not void, and it is not the same as the {@code pos} coordinate of the target var handle, * if {@code pos} is not between 0 and the target var handle coordinate arity, inclusive, * if the resulting var handle's type would have too many coordinates, * or if it's determined that {@code filter} throws any checked exceptions. * @throws NullPointerException if any of the arguments is {@code null}. - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public static VarHandle collectCoordinates(VarHandle target, int pos, MethodHandle filter) { return VarHandles.collectCoordinates(target, pos, filter); } /** @@ -8266,12 +8182,11 @@ * @param valueTypes the type(s) of the coordinate(s) to drop * @return an adapter var handle which drops some dummy coordinates, * before calling the target var handle * @throws IllegalArgumentException if {@code pos} is not between 0 and the target var handle coordinate arity, inclusive. * @throws NullPointerException if any of the arguments is {@code null} or {@code valueTypes} contains {@code null}. - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public static VarHandle dropCoordinates(VarHandle target, int pos, Class... valueTypes) { return VarHandles.dropCoordinates(target, pos, valueTypes); } } diff a/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java b/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java --- a/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java +++ b/src/java.base/share/classes/java/lang/invoke/VarHandleSegmentViewBase.java @@ -23,10 +23,12 @@ * questions. */ package java.lang.invoke; +import jdk.internal.foreign.Utils; + /** * Base class for memory segment var handle view implementations. */ abstract sealed class VarHandleSegmentViewBase extends VarHandle permits VarHandleSegmentAsBytes, @@ -52,8 +54,12 @@ this.length = length; this.alignmentMask = alignmentMask; } 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) { + return new UnsupportedOperationException("Unsupported access mode for alignment: " + alignment); } } diff a/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template b/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template --- a/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template +++ b/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template @@ -41,11 +41,11 @@ static final boolean BE = UNSAFE.isBigEndian(); static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess(); - static final int VM_ALIGN = $BoxType$.BYTES - 1; + static final int NON_PLAIN_ACCESS_MIN_ALIGN_MASK = $BoxType$.BYTES - 1; static final VarForm FORM = new VarForm(VarHandleSegmentAs$Type$s.class, MemorySegment.class, $type$.class, long.class); VarHandleSegmentAs$Type$s(boolean be, long length, long alignmentMask, boolean exact) { super(FORM, be, length, alignmentMask, exact); @@ -102,20 +102,19 @@ oo.checkAccess(offset, length, ro); return oo; } @ForceInline - static long offset(AbstractMemorySegmentImpl bb, long offset, long alignmentMask) { - long address = offsetNoVMAlignCheck(bb, offset, alignmentMask); - if ((address & VM_ALIGN) != 0) { - throw VarHandleSegmentViewBase.newIllegalArgumentExceptionForMisalignedAccess(address); + static long offsetNonPlain(AbstractMemorySegmentImpl bb, long offset, long alignmentMask) { + if ((alignmentMask & NON_PLAIN_ACCESS_MIN_ALIGN_MASK) != NON_PLAIN_ACCESS_MIN_ALIGN_MASK) { + throw VarHandleSegmentViewBase.newUnsupportedAccessModeForAlignment(alignmentMask + 1); } - return address; + return offsetPlain(bb, offset, alignmentMask); } @ForceInline - static long offsetNoVMAlignCheck(AbstractMemorySegmentImpl bb, long offset, long alignmentMask) { + static long offsetPlain(AbstractMemorySegmentImpl bb, long offset, long alignmentMask) { long base = bb.unsafeGetOffset(); long address = base + offset; long maxAlignMask = bb.maxAlignMask(); if (((address | maxAlignMask) & alignmentMask) != 0) { throw VarHandleSegmentViewBase.newIllegalArgumentExceptionForMisalignedAccess(address); @@ -128,22 +127,22 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, true); #if[floatingPoint] $rawType$ rawValue = SCOPED_MEMORY_ACCESS.get$RawType$Unaligned(bb.sessionImpl(), bb.unsafeGetBase(), - offsetNoVMAlignCheck(bb, base, handle.alignmentMask), + offsetPlain(bb, base, handle.alignmentMask), handle.be); return $Type$.$rawType$BitsTo$Type$(rawValue); #else[floatingPoint] #if[byte] return SCOPED_MEMORY_ACCESS.get$Type$(bb.sessionImpl(), bb.unsafeGetBase(), - offsetNoVMAlignCheck(bb, base, handle.alignmentMask)); + offsetPlain(bb, base, handle.alignmentMask)); #else[byte] return SCOPED_MEMORY_ACCESS.get$Type$Unaligned(bb.sessionImpl(), bb.unsafeGetBase(), - offsetNoVMAlignCheck(bb, base, handle.alignmentMask), + offsetPlain(bb, base, handle.alignmentMask), handle.be); #end[byte] #end[floatingPoint] } @@ -152,23 +151,23 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); #if[floatingPoint] SCOPED_MEMORY_ACCESS.put$RawType$Unaligned(bb.sessionImpl(), bb.unsafeGetBase(), - offsetNoVMAlignCheck(bb, base, handle.alignmentMask), + offsetPlain(bb, base, handle.alignmentMask), $Type$.$type$ToRaw$RawType$Bits(value), handle.be); #else[floatingPoint] #if[byte] SCOPED_MEMORY_ACCESS.put$Type$(bb.sessionImpl(), bb.unsafeGetBase(), - offsetNoVMAlignCheck(bb, base, handle.alignmentMask), + offsetPlain(bb, base, handle.alignmentMask), value); #else[byte] SCOPED_MEMORY_ACCESS.put$Type$Unaligned(bb.sessionImpl(), bb.unsafeGetBase(), - offsetNoVMAlignCheck(bb, base, handle.alignmentMask), + offsetPlain(bb, base, handle.alignmentMask), value, handle.be); #end[byte] #end[floatingPoint] } @@ -178,177 +177,177 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, true); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.get$RawType$Volatile(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask))); + offsetNonPlain(bb, base, handle.alignmentMask))); } @ForceInline static void setVolatile(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); SCOPED_MEMORY_ACCESS.put$RawType$Volatile(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, value)); } @ForceInline static $type$ getAcquire(VarHandle ob, Object obb, long base) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, true); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.get$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask))); + offsetNonPlain(bb, base, handle.alignmentMask))); } @ForceInline static void setRelease(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); SCOPED_MEMORY_ACCESS.put$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, value)); } @ForceInline static $type$ getOpaque(VarHandle ob, Object obb, long base) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, true); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.get$RawType$Opaque(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask))); + offsetNonPlain(bb, base, handle.alignmentMask))); } @ForceInline static void setOpaque(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); SCOPED_MEMORY_ACCESS.put$RawType$Opaque(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, value)); } #if[CAS] @ForceInline static boolean compareAndSet(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return SCOPED_MEMORY_ACCESS.compareAndSet$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value)); } @ForceInline static $type$ compareAndExchange(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.compareAndExchange$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value))); } @ForceInline static $type$ compareAndExchangeAcquire(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.compareAndExchange$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value))); } @ForceInline static $type$ compareAndExchangeRelease(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.compareAndExchange$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value))); } @ForceInline static boolean weakCompareAndSetPlain(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return SCOPED_MEMORY_ACCESS.weakCompareAndSet$RawType$Plain(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value)); } @ForceInline static boolean weakCompareAndSet(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return SCOPED_MEMORY_ACCESS.weakCompareAndSet$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value)); } @ForceInline static boolean weakCompareAndSetAcquire(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return SCOPED_MEMORY_ACCESS.weakCompareAndSet$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value)); } @ForceInline static boolean weakCompareAndSetRelease(VarHandle ob, Object obb, long base, $type$ expected, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return SCOPED_MEMORY_ACCESS.weakCompareAndSet$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, expected), convEndian(handle.be, value)); } @ForceInline static $type$ getAndSet(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.getAndSet$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, value))); } @ForceInline static $type$ getAndSetAcquire(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.getAndSet$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, value))); } @ForceInline static $type$ getAndSetRelease(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); return convEndian(handle.be, SCOPED_MEMORY_ACCESS.getAndSet$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), convEndian(handle.be, value))); } #end[CAS] #if[AtomicAdd] @@ -357,42 +356,42 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndAdd$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), delta); } else { - return getAndAddConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), delta); + return getAndAddConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), delta); } } @ForceInline static $type$ getAndAddAcquire(VarHandle ob, Object obb, long base, $type$ delta) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndAdd$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), delta); } else { - return getAndAddConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), delta); + return getAndAddConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), delta); } } @ForceInline static $type$ getAndAddRelease(VarHandle ob, Object obb, long base, $type$ delta) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndAdd$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), delta); } else { - return getAndAddConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), delta); + return getAndAddConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), delta); } } @ForceInline static $type$ getAndAddConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ delta) { @@ -413,42 +412,42 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseOr$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseOrConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseOrConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseOrRelease(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseOr$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseOrConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseOrConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseOrAcquire(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseOr$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseOrConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseOrConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseOrConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ value) { @@ -467,42 +466,42 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseAnd$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseAndConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseAndConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseAndRelease(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseAnd$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseAndConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseAndConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseAndAcquire(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseAnd$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseAndConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseAndConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseAndConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ value) { @@ -522,42 +521,42 @@ VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseXor$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseXorConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseXorConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseXorRelease(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseXor$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseXorConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseXorConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseXorAcquire(VarHandle ob, Object obb, long base, $type$ value) { VarHandleSegmentViewBase handle = (VarHandleSegmentViewBase)ob; AbstractMemorySegmentImpl bb = checkAddress(obb, base, handle.length, false); if (handle.be == BE) { return SCOPED_MEMORY_ACCESS.getAndBitwiseXor$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, handle.alignmentMask), + offsetNonPlain(bb, base, handle.alignmentMask), value); } else { - return getAndBitwiseXorConvEndianWithCAS(bb, offset(bb, base, handle.alignmentMask), value); + return getAndBitwiseXorConvEndianWithCAS(bb, offsetNonPlain(bb, base, handle.alignmentMask), value); } } @ForceInline static $type$ getAndBitwiseXorConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ value) { diff a/src/java.base/share/classes/java/nio/channels/FileChannel.java b/src/java.base/share/classes/java/nio/channels/FileChannel.java --- a/src/java.base/share/classes/java/nio/channels/FileChannel.java +++ b/src/java.base/share/classes/java/nio/channels/FileChannel.java @@ -1086,13 +1086,12 @@ * If some other I/O error occurs. * * @throws UnsupportedOperationException * If an unsupported map mode is specified. * - * @since 19 + * @since 22 */ - @PreviewFeature(feature=PreviewFeature.Feature.FOREIGN) public MemorySegment map(MapMode mode, long offset, long size, Arena arena) throws IOException { throw new UnsupportedOperationException(); } diff a/src/java.base/share/classes/java/util/jar/Attributes.java b/src/java.base/share/classes/java/util/jar/Attributes.java --- a/src/java.base/share/classes/java/util/jar/Attributes.java +++ b/src/java.base/share/classes/java/util/jar/Attributes.java @@ -713,13 +713,14 @@ // Common attributes used in MANIFEST.MF et.al; adding these has a // small footprint cost, but is likely to be quickly paid for by // reducing allocation when reading and parsing typical manifests - // JDK internal attributes + // JDK specific attributes addName(names, new Name("Add-Exports")); addName(names, new Name("Add-Opens")); + addName(names, new Name("Enable-Native-Access")); // LauncherHelper attributes addName(names, new Name("Launcher-Agent-Class")); addName(names, new Name("JavaFX-Application-Class")); // jarsigner attributes addName(names, new Name("Name")); diff a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -25,10 +25,11 @@ 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; import java.lang.reflect.Executable; import java.lang.reflect.Method; @@ -271,11 +272,11 @@ /** * Ensure that the given module has native access. If not, warn or * throw exception depending on the configuration. */ - void ensureNativeAccess(Module m, Class owner, String methodName); + void ensureNativeAccess(Module m, Class owner, String methodName, Class currentClass); /** * Returns the ServicesCatalog for the given Layer. */ ServicesCatalog getServicesCatalog(ModuleLayer layer); @@ -572,6 +573,16 @@ /** * Returns '' @ if classloader has a name * 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 a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java --- a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java @@ -46,16 +46,16 @@ import jdk.internal.access.JavaNioAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.access.foreign.UnmapperProxy; import jdk.internal.misc.ScopedMemoryAccess; -import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.util.ArraysSupport; import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.ForceInline; +import sun.nio.ch.DirectBuffer; import static java.lang.foreign.ValueLayout.JAVA_BYTE; /** * This abstract class provides an immutable implementation for the {@code MemorySegment} interface. This class contains information @@ -262,27 +262,18 @@ final long thatStart = that.unsafeGetOffset(); final long thisEnd = thisStart + this.byteSize(); final long thatEnd = thatStart + that.byteSize(); if (thisStart < thatEnd && thisEnd > thatStart) { //overlap occurs - long offsetToThat = this.segmentOffset(that); + long offsetToThat = that.address() - this.address(); long newOffset = offsetToThat >= 0 ? offsetToThat : 0; return Optional.of(asSlice(newOffset, Math.min(this.byteSize() - newOffset, that.byteSize() + offsetToThat))); } } return Optional.empty(); } - @Override - public final long segmentOffset(MemorySegment other) { - AbstractMemorySegmentImpl that = (AbstractMemorySegmentImpl) Objects.requireNonNull(other); - if (unsafeGetBase() == that.unsafeGetBase()) { - return that.unsafeGetOffset() - this.unsafeGetOffset(); - } - throw new UnsupportedOperationException("Cannot compute offset from native to heap (or vice versa)."); - } - @Override public void load() { throw notAMappedSegment(); } @@ -544,55 +535,46 @@ int scaleFactor = getScaleFactor(bb); final MemorySessionImpl bufferScope; if (bufferSegment != null) { bufferScope = bufferSegment.scope; } else { - bufferScope = MemorySessionImpl.heapSession(bb); + bufferScope = MemorySessionImpl.createHeap(bufferRef(bb)); } if (base != null) { - if (base instanceof byte[]) { - return new HeapMemorySegmentImpl.OfByte(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else if (base instanceof short[]) { - return new HeapMemorySegmentImpl.OfShort(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else if (base instanceof char[]) { - return new HeapMemorySegmentImpl.OfChar(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else if (base instanceof int[]) { - return new HeapMemorySegmentImpl.OfInt(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else if (base instanceof float[]) { - return new HeapMemorySegmentImpl.OfFloat(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else if (base instanceof long[]) { - return new HeapMemorySegmentImpl.OfLong(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else if (base instanceof double[]) { - return new HeapMemorySegmentImpl.OfDouble(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); - } else { - throw new AssertionError("Cannot get here"); - } + return switch (base) { + case byte[] __ -> + new HeapMemorySegmentImpl.OfByte(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + case short[] __ -> + new HeapMemorySegmentImpl.OfShort(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + case char[] __ -> + new HeapMemorySegmentImpl.OfChar(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + case int[] __ -> + new HeapMemorySegmentImpl.OfInt(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + case float[] __ -> + new HeapMemorySegmentImpl.OfFloat(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + case long[] __ -> + new HeapMemorySegmentImpl.OfLong(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + case double[] __ -> + new HeapMemorySegmentImpl.OfDouble(bbAddress + (pos << scaleFactor), base, size << scaleFactor, readOnly, bufferScope); + default -> throw new AssertionError("Cannot get here"); + }; } else if (unmapper == null) { return new NativeMemorySegmentImpl(bbAddress + (pos << scaleFactor), size << scaleFactor, readOnly, bufferScope); } else { // we can ignore scale factor here, a mapped buffer is always a byte buffer, so scaleFactor == 0. return new MappedMemorySegmentImpl(bbAddress + pos, unmapper, size, readOnly, bufferScope); } } - private static int getScaleFactor(Buffer buffer) { - if (buffer instanceof ByteBuffer) { - return 0; - } else if (buffer instanceof CharBuffer) { - return 1; - } else if (buffer instanceof ShortBuffer) { - return 1; - } else if (buffer instanceof IntBuffer) { - return 2; - } else if (buffer instanceof FloatBuffer) { - return 2; - } else if (buffer instanceof LongBuffer) { - return 3; - } else if (buffer instanceof DoubleBuffer) { - return 3; + private static Object bufferRef(Buffer buffer) { + if (buffer instanceof DirectBuffer directBuffer) { + // direct buffer, return either the buffer attachment (for slices and views), or the buffer itself + return directBuffer.attachment() != null ? + directBuffer.attachment() : directBuffer; } else { - throw new AssertionError("Cannot get here"); + // heap buffer, return the underlying array + return NIO_ACCESS.getBufferBase(buffer); } } @ForceInline public static void copy(MemorySegment srcSegment, ValueLayout srcElementLayout, long srcOffset, @@ -629,60 +611,56 @@ @ForceInline public static void copy(MemorySegment srcSegment, ValueLayout srcLayout, long srcOffset, Object dstArray, int dstIndex, int elementCount) { - long baseAndScale = getBaseAndScale(dstArray.getClass()); + var dstInfo = Utils.BaseAndScale.of(dstArray); if (dstArray.getClass().componentType() != srcLayout.carrier()) { throw new IllegalArgumentException("Incompatible value layout: " + srcLayout); } - int dstBase = (int)baseAndScale; - long dstWidth = (int)(baseAndScale >> 32); // Use long arithmetics below AbstractMemorySegmentImpl srcImpl = (AbstractMemorySegmentImpl)srcSegment; Utils.checkElementAlignment(srcLayout, "Source layout alignment greater than its size"); if (!srcImpl.isAlignedForElement(srcOffset, srcLayout)) { throw new IllegalArgumentException("Source segment incompatible with alignment constraints"); } - srcImpl.checkAccess(srcOffset, elementCount * dstWidth, true); + srcImpl.checkAccess(srcOffset, elementCount * dstInfo.scale(), true); Objects.checkFromIndexSize(dstIndex, elementCount, Array.getLength(dstArray)); - if (dstWidth == 1 || srcLayout.order() == ByteOrder.nativeOrder()) { + if (dstInfo.scale() == 1 || srcLayout.order() == ByteOrder.nativeOrder()) { ScopedMemoryAccess.getScopedMemoryAccess().copyMemory(srcImpl.sessionImpl(), null, srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcOffset, - dstArray, dstBase + (dstIndex * dstWidth), elementCount * dstWidth); + dstArray, dstInfo.base() + (dstIndex * dstInfo.scale()), elementCount * dstInfo.scale()); } else { ScopedMemoryAccess.getScopedMemoryAccess().copySwapMemory(srcImpl.sessionImpl(), null, srcImpl.unsafeGetBase(), srcImpl.unsafeGetOffset() + srcOffset, - dstArray, dstBase + (dstIndex * dstWidth), elementCount * dstWidth, dstWidth); + dstArray, dstInfo.base() + (dstIndex * dstInfo.scale()), elementCount * dstInfo.scale(), dstInfo.scale()); } } @ForceInline public static void copy(Object srcArray, int srcIndex, MemorySegment dstSegment, ValueLayout dstLayout, long dstOffset, int elementCount) { - long baseAndScale = getBaseAndScale(srcArray.getClass()); + var srcInfo = Utils.BaseAndScale.of(srcArray); if (srcArray.getClass().componentType() != dstLayout.carrier()) { throw new IllegalArgumentException("Incompatible value layout: " + dstLayout); } - int srcBase = (int)baseAndScale; - long srcWidth = (int)(baseAndScale >> 32); // Use long arithmetics below Objects.checkFromIndexSize(srcIndex, elementCount, Array.getLength(srcArray)); AbstractMemorySegmentImpl destImpl = (AbstractMemorySegmentImpl)dstSegment; Utils.checkElementAlignment(dstLayout, "Destination layout alignment greater than its size"); if (!destImpl.isAlignedForElement(dstOffset, dstLayout)) { throw new IllegalArgumentException("Destination segment incompatible with alignment constraints"); } - destImpl.checkAccess(dstOffset, elementCount * srcWidth, false); - if (srcWidth == 1 || dstLayout.order() == ByteOrder.nativeOrder()) { + destImpl.checkAccess(dstOffset, elementCount * srcInfo.scale(), false); + if (srcInfo.scale() == 1 || dstLayout.order() == ByteOrder.nativeOrder()) { ScopedMemoryAccess.getScopedMemoryAccess().copyMemory(null, destImpl.sessionImpl(), - srcArray, srcBase + (srcIndex * srcWidth), - destImpl.unsafeGetBase(), destImpl.unsafeGetOffset() + dstOffset, elementCount * srcWidth); + srcArray, srcInfo.base() + (srcIndex * srcInfo.scale()), + destImpl.unsafeGetBase(), destImpl.unsafeGetOffset() + dstOffset, elementCount * srcInfo.scale()); } else { ScopedMemoryAccess.getScopedMemoryAccess().copySwapMemory(null, destImpl.sessionImpl(), - srcArray, srcBase + (srcIndex * srcWidth), - destImpl.unsafeGetBase(), destImpl.unsafeGetOffset() + dstOffset, elementCount * srcWidth, srcWidth); + srcArray, srcInfo.base() + (srcIndex * srcInfo.scale()), + destImpl.unsafeGetBase(), destImpl.unsafeGetOffset() + dstOffset, elementCount * srcInfo.scale(), srcInfo.scale()); } } public static long mismatch(MemorySegment srcSegment, long srcFromOffset, long srcToOffset, MemorySegment dstSegment, long dstFromOffset, long dstToOffset) { @@ -720,25 +698,17 @@ } } return srcBytes != dstBytes ? bytes : -1; } - private static long getBaseAndScale(Class arrayType) { - if (arrayType.equals(byte[].class)) { - return (long) Unsafe.ARRAY_BYTE_BASE_OFFSET | ((long)Unsafe.ARRAY_BYTE_INDEX_SCALE << 32); - } else if (arrayType.equals(char[].class)) { - return (long) Unsafe.ARRAY_CHAR_BASE_OFFSET | ((long)Unsafe.ARRAY_CHAR_INDEX_SCALE << 32); - } else if (arrayType.equals(short[].class)) { - return (long)Unsafe.ARRAY_SHORT_BASE_OFFSET | ((long)Unsafe.ARRAY_SHORT_INDEX_SCALE << 32); - } else if (arrayType.equals(int[].class)) { - return (long)Unsafe.ARRAY_INT_BASE_OFFSET | ((long) Unsafe.ARRAY_INT_INDEX_SCALE << 32); - } else if (arrayType.equals(float[].class)) { - return (long)Unsafe.ARRAY_FLOAT_BASE_OFFSET | ((long)Unsafe.ARRAY_FLOAT_INDEX_SCALE << 32); - } else if (arrayType.equals(long[].class)) { - return (long)Unsafe.ARRAY_LONG_BASE_OFFSET | ((long)Unsafe.ARRAY_LONG_INDEX_SCALE << 32); - } else if (arrayType.equals(double[].class)) { - return (long)Unsafe.ARRAY_DOUBLE_BASE_OFFSET | ((long)Unsafe.ARRAY_DOUBLE_INDEX_SCALE << 32); - } else { - throw new IllegalArgumentException("Not a supported array class: " + arrayType.getSimpleName()); - } + private static int getScaleFactor(Buffer buffer) { + return switch (buffer) { + case ByteBuffer __ -> 0; + case CharBuffer __ -> 1; + case ShortBuffer __ -> 1; + case IntBuffer __ -> 2; + case FloatBuffer __ -> 2; + case LongBuffer __ -> 3; + case DoubleBuffer __ -> 3; + }; } } diff a/src/java.base/share/classes/jdk/internal/foreign/ArenaImpl.java b/src/java.base/share/classes/jdk/internal/foreign/ArenaImpl.java --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/foreign/ArenaImpl.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +package jdk.internal.foreign; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.MemorySegment.Scope; + +public final class ArenaImpl implements Arena { + + private final MemorySessionImpl session; + private final boolean shouldReserveMemory; + ArenaImpl(MemorySessionImpl session) { + this.session = session; + shouldReserveMemory = session instanceof ImplicitSession; + } + + @Override + public Scope scope() { + return session; + } + + @Override + public void close() { + session.close(); + } + + public MemorySegment allocateNoInit(long byteSize, long byteAlignment) { + Utils.checkAllocationSizeAndAlign(byteSize, byteAlignment); + return NativeMemorySegmentImpl.makeNativeSegment(byteSize, byteAlignment, session, shouldReserveMemory); + } + + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + MemorySegment segment = allocateNoInit(byteSize, byteAlignment); + return segment.fill((byte)0); + } +} diff a/src/java.base/share/classes/jdk/internal/foreign/ConfinedSession.java b/src/java.base/share/classes/jdk/internal/foreign/ConfinedSession.java --- a/src/java.base/share/classes/jdk/internal/foreign/ConfinedSession.java +++ b/src/java.base/share/classes/jdk/internal/foreign/ConfinedSession.java @@ -25,11 +25,10 @@ package jdk.internal.foreign; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.lang.ref.Cleaner; import jdk.internal.vm.annotation.ForceInline; /** * A confined session, which features an owner thread. The liveness check features an additional diff a/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java b/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java --- a/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java +++ b/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java @@ -23,24 +23,27 @@ * questions. */ package jdk.internal.foreign; +import jdk.internal.access.JavaNioAccess; +import jdk.internal.access.SharedSecrets; import jdk.internal.vm.annotation.ForceInline; +import sun.nio.ch.DirectBuffer; + +import java.nio.Buffer; +import java.util.Objects; /** * The global, non-closeable, shared session. Similar to a shared session, but its {@link #close()} method throws unconditionally. * Adding new resources to the global session, does nothing: as the session can never become not-alive, there is nothing to track. * Acquiring and or releasing a memory session similarly does nothing. */ -final class GlobalSession extends MemorySessionImpl { - - final Object ref; +non-sealed class GlobalSession extends MemorySessionImpl { - public GlobalSession(Object ref) { + public GlobalSession() { super(null, null); - this.ref = ref; } @Override @ForceInline public void release0() { @@ -65,6 +68,34 @@ @Override public void justClose() { throw nonCloseable(); } + + /** + * This is a global session that wraps a heap object. Possible objects are: Java arrays, buffers and + * class loaders. Objects of two heap sessions are compared by identity. That is, if the wrapped object is the same, + * then the resulting heap sessions are also considered equals. We do not compare the objects using + * {@link Object#equals(Object)}, as that would be problematic when comparing buffers, whose equality and + * hash codes are content-dependent. + */ + static class HeapSession extends GlobalSession { + + final Object ref; + + public HeapSession(Object ref) { + super(); + this.ref = Objects.requireNonNull(ref); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof HeapSession session && + ref == session.ref; + } + + @Override + public int hashCode() { + return System.identityHashCode(ref); + } + } } diff a/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java --- a/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java @@ -32,33 +32,34 @@ import java.util.Objects; import java.util.Optional; import jdk.internal.access.JavaNioAccess; import jdk.internal.access.SharedSecrets; -import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; /** * Implementation for heap memory segments. A heap memory segment is composed by an offset and * a base object (typically an array). To enhance performances, the access to the base object needs to feature * sharp type information, as well as sharp null-check information. For this reason, many concrete subclasses - * of {@link HeapMemorySegmentImpl} are defined (e.g. {@link OfFloat}, so that each subclass can override the + * of {@link HeapMemorySegmentImpl} are defined (e.g. {@link OfFloat}), so that each subclass can override the * {@link HeapMemorySegmentImpl#unsafeGetBase()} method so that it returns an array of the correct (sharp) type. Note that * the field type storing the 'base' coordinate is just Object; similarly, all the constructor in the subclasses * accept an Object 'base' parameter instead of a sharper type (e.g. {@code byte[]}). This is deliberate, as * using sharper types would require use of type-conversions, which in turn would inhibit some C2 optimizations, * such as the elimination of store barriers in methods like {@link HeapMemorySegmentImpl#dup(long, long, boolean, MemorySessionImpl)}. */ public abstract sealed class HeapMemorySegmentImpl extends AbstractMemorySegmentImpl { - private static final Unsafe UNSAFE = Unsafe.getUnsafe(); - private static final int BYTE_ARR_BASE = UNSAFE.arrayBaseOffset(byte[].class); + // Constants defining the maximum alignment supported by various kinds of heap arrays. + // While for most arrays, the maximum alignment is constant (the size, in bytes, of the array elements), + // note that the alignment of a long[]/double[] depends on the platform: it's 4-byte on x86, but 8 bytes on x64 + // (as specified by the JAVA_LONG layout constant). - private static final long MAX_ALIGN_1 = ValueLayout.JAVA_BYTE.byteAlignment(); - private static final long MAX_ALIGN_2 = ValueLayout.JAVA_SHORT.byteAlignment(); - private static final long MAX_ALIGN_4 = ValueLayout.JAVA_INT.byteAlignment(); - private static final long MAX_ALIGN_8 = ValueLayout.JAVA_LONG.byteAlignment(); + private static final long MAX_ALIGN_BYTE_ARRAY = ValueLayout.JAVA_BYTE.byteAlignment(); + private static final long MAX_ALIGN_SHORT_ARRAY = ValueLayout.JAVA_SHORT.byteAlignment(); + private static final long MAX_ALIGN_INT_ARRAY = ValueLayout.JAVA_INT.byteAlignment(); + private static final long MAX_ALIGN_LONG_ARRAY = ValueLayout.JAVA_LONG.byteAlignment(); final long offset; final Object base; @Override @@ -87,11 +88,11 @@ ByteBuffer makeByteBuffer() { if (!(base instanceof byte[] baseByte)) { throw new UnsupportedOperationException("Not an address to an heap-allocated byte array"); } JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess(); - return nioAccess.newHeapByteBuffer(baseByte, (int)offset - BYTE_ARR_BASE, (int) byteSize(), null); + return nioAccess.newHeapByteBuffer(baseByte, (int)offset - Utils.BaseAndScale.BYTE.base(), (int) byteSize(), null); } // factories public static final class OfByte extends HeapMemorySegmentImpl { @@ -110,23 +111,23 @@ return (byte[])Objects.requireNonNull(base); } public static MemorySegment fromArray(byte[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_BYTE_INDEX_SCALE; - return new OfByte(Unsafe.ARRAY_BYTE_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.BYTE.scale(); + return new OfByte(Utils.BaseAndScale.BYTE.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_1; + return MAX_ALIGN_BYTE_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_BYTE_BASE_OFFSET; + return offset - Utils.BaseAndScale.BYTE.base(); } } public static final class OfChar extends HeapMemorySegmentImpl { @@ -144,23 +145,23 @@ return (char[])Objects.requireNonNull(base); } public static MemorySegment fromArray(char[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_CHAR_INDEX_SCALE; - return new OfChar(Unsafe.ARRAY_CHAR_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.CHAR.scale(); + return new OfChar(Utils.BaseAndScale.CHAR.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_2; + return MAX_ALIGN_SHORT_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_CHAR_BASE_OFFSET; + return offset - Utils.BaseAndScale.CHAR.base(); } } public static final class OfShort extends HeapMemorySegmentImpl { @@ -178,23 +179,23 @@ return (short[])Objects.requireNonNull(base); } public static MemorySegment fromArray(short[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_SHORT_INDEX_SCALE; - return new OfShort(Unsafe.ARRAY_SHORT_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.SHORT.scale(); + return new OfShort(Utils.BaseAndScale.SHORT.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_2; + return MAX_ALIGN_SHORT_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_SHORT_BASE_OFFSET; + return offset - Utils.BaseAndScale.SHORT.base(); } } public static final class OfInt extends HeapMemorySegmentImpl { @@ -212,23 +213,23 @@ return (int[])Objects.requireNonNull(base); } public static MemorySegment fromArray(int[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_INT_INDEX_SCALE; - return new OfInt(Unsafe.ARRAY_INT_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.INT.scale(); + return new OfInt(Utils.BaseAndScale.INT.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_4; + return MAX_ALIGN_INT_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_INT_BASE_OFFSET; + return offset - Utils.BaseAndScale.INT.base(); } } public static final class OfLong extends HeapMemorySegmentImpl { @@ -246,23 +247,23 @@ return (long[])Objects.requireNonNull(base); } public static MemorySegment fromArray(long[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_LONG_INDEX_SCALE; - return new OfLong(Unsafe.ARRAY_LONG_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.LONG.scale(); + return new OfLong(Utils.BaseAndScale.LONG.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_8; + return MAX_ALIGN_LONG_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_LONG_BASE_OFFSET; + return offset - Utils.BaseAndScale.LONG.base(); } } public static final class OfFloat extends HeapMemorySegmentImpl { @@ -280,23 +281,23 @@ return (float[])Objects.requireNonNull(base); } public static MemorySegment fromArray(float[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_FLOAT_INDEX_SCALE; - return new OfFloat(Unsafe.ARRAY_FLOAT_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.FLOAT.scale(); + return new OfFloat(Utils.BaseAndScale.FLOAT.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_4; + return MAX_ALIGN_INT_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_FLOAT_BASE_OFFSET; + return offset - Utils.BaseAndScale.FLOAT.base(); } } public static final class OfDouble extends HeapMemorySegmentImpl { @@ -314,22 +315,22 @@ return (double[])Objects.requireNonNull(base); } public static MemorySegment fromArray(double[] arr) { Objects.requireNonNull(arr); - long byteSize = (long)arr.length * Unsafe.ARRAY_DOUBLE_INDEX_SCALE; - return new OfDouble(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + long byteSize = (long)arr.length * Utils.BaseAndScale.DOUBLE.scale(); + return new OfDouble(Utils.BaseAndScale.DOUBLE.base(), arr, byteSize, false, + MemorySessionImpl.createHeap(arr)); } @Override public long maxAlignMask() { - return MAX_ALIGN_8; + return MAX_ALIGN_LONG_ARRAY; } @Override public long address() { - return offset - Unsafe.ARRAY_DOUBLE_BASE_OFFSET; + return offset - Utils.BaseAndScale.DOUBLE.base(); } } } diff a/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java b/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java --- a/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java +++ b/src/java.base/share/classes/jdk/internal/foreign/LayoutPath.java @@ -24,11 +24,10 @@ * */ package jdk.internal.foreign; import jdk.internal.vm.annotation.ForceInline; - import java.lang.foreign.AddressLayout; import java.lang.foreign.GroupLayout; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; import java.lang.foreign.SequenceLayout; @@ -37,15 +36,20 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.util.Arrays; +import java.util.List; 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)}, + * This class provide support for constructing layout paths; that is, starting from a root path (see {@link #rootPath(MemoryLayout)}), * a path can be constructed by selecting layout elements using the selector methods provided by this class * (see {@link #sequenceElement()}, {@link #sequenceElement(long)}, {@link #sequenceElement(long, long)}, {@link #groupElement(String)}). * Once a path has been fully constructed, clients can ask for the offset associated with the layout element selected * by the path (see {@link #offset}), or obtain var handle to access the selected layout element * given an address pointing to a segment associated with the root layout (see {@link #dereferenceHandle()}). @@ -59,10 +63,11 @@ private static final MethodHandle MH_ADD_SCALED_OFFSET; private static final MethodHandle MH_SLICE; private static final MethodHandle MH_SLICE_LAYOUT; private static final MethodHandle MH_CHECK_ALIGN; private static final MethodHandle MH_SEGMENT_RESIZE; + private static final MethodHandle MH_ADD; static { try { MethodHandles.Lookup lookup = MethodHandles.lookup(); MH_ADD_SCALED_OFFSET = lookup.findStatic(LayoutPath.class, "addScaledOffset", @@ -70,23 +75,24 @@ MH_SLICE = lookup.findVirtual(MemorySegment.class, "asSlice", MethodType.methodType(MemorySegment.class, long.class, long.class)); MH_SLICE_LAYOUT = lookup.findVirtual(MemorySegment.class, "asSlice", MethodType.methodType(MemorySegment.class, long.class, MemoryLayout.class)); MH_CHECK_ALIGN = lookup.findStatic(LayoutPath.class, "checkAlign", - MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class)); + MethodType.methodType(void.class, MemorySegment.class, long.class, MemoryLayout.class)); MH_SEGMENT_RESIZE = lookup.findStatic(LayoutPath.class, "resizeSegment", MethodType.methodType(MemorySegment.class, MemorySegment.class, MemoryLayout.class)); + MH_ADD = lookup.findStatic(Long.class, "sum", + MethodType.methodType(long.class, long.class, long.class)); } catch (Throwable ex) { throw new ExceptionInInitializerError(ex); } } private final MemoryLayout layout; private final long offset; private final LayoutPath enclosing; private final long[] strides; - private final long[] bounds; private final MethodHandle[] derefAdapters; private LayoutPath(MemoryLayout layout, long offset, long[] strides, long[] bounds, MethodHandle[] derefAdapters, LayoutPath enclosing) { this.layout = layout; @@ -98,42 +104,38 @@ } // 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(); long nelems = step > 0 ? seq.elementCount() - start : 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++) { MemoryLayout l = g.memberLayouts().get(i); if (l.name().isPresent() && @@ -143,24 +145,25 @@ } else if (g instanceof StructLayout) { offset += l.byteSize(); } } 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) { offset += elem.byteSize(); } @@ -169,11 +172,12 @@ } 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); handle = MethodHandles.filterReturnValue(handle, MethodHandles.insertArguments(MH_SEGMENT_RESIZE, 1, derefLayout)); @@ -194,30 +198,45 @@ return dereferenceHandle(true); } 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, // we check the root layout instead ValueLayout accessedLayout = enclosing != null ? valueLayout.withByteAlignment(1) : valueLayout; - VarHandle handle = Utils.makeSegmentViewVarHandle(accessedLayout); + VarHandle handle = accessedLayout.varHandle(); handle = MethodHandles.collectCoordinates(handle, 1, offsetHandle()); // we only have to check the alignment of the root layout for the first dereference we do, // as each dereference checks the alignment of the target address when constructing its segment // (see Utils::longToAddress) if (derefAdapters.length == 0 && enclosing != null) { - MethodHandle checkHandle = MethodHandles.insertArguments(MH_CHECK_ALIGN, 1, rootLayout()); - handle = MethodHandles.filterCoordinates(handle, 0, checkHandle); + // insert align check for the root layout on the initial MS + offset + List> coordinateTypes = handle.coordinateTypes(); + MethodHandle alignCheck = MethodHandles.insertArguments(MH_CHECK_ALIGN, 2, rootLayout()); + handle = MethodHandles.collectCoordinates(handle, 0, alignCheck); + int[] reorder = IntStream.concat(IntStream.of(0, 1), IntStream.range(0, coordinateTypes.size())).toArray(); + handle = MethodHandles.permuteCoordinates(handle, coordinateTypes, reorder); } if (adapt) { + if (derefAdapters.length > 0) { + // plug up the base offset if we have at least 1 enclosing dereference + handle = MethodHandles.insertCoordinates(handle, 1, 0); + } for (int i = derefAdapters.length; i > 0; i--) { - handle = MethodHandles.collectCoordinates(handle, 0, derefAdapters[i - 1]); + MethodHandle adapter = derefAdapters[i - 1]; + // the first/outermost adapter will have a base offset coordinate, the rest are constant 0 + if (i > 1) { + // plug in a constant 0 base offset for all but the outermost access in a deref chain + adapter = MethodHandles.insertArguments(adapter, 1, 0); + } + handle = MethodHandles.collectCoordinates(handle, 0, adapter); } } return handle; } @@ -226,18 +245,18 @@ Objects.checkIndex(index, bound); return base + (stride * index); } public MethodHandle offsetHandle() { - MethodHandle mh = MethodHandles.identity(long.class); - for (int i = strides.length - 1; i >=0; i--) { + MethodHandle mh = MethodHandles.insertArguments(MH_ADD, 0, offset); + for (int i = strides.length - 1; i >= 0; i--) { MethodHandle collector = MethodHandles.insertArguments(MH_ADD_SCALED_OFFSET, 2, strides[i], bounds[i]); // (J, ...) -> J to (J, J, ...) -> J // i.e. new coord is prefixed. Last coord will correspond to innermost layout mh = MethodHandles.collectArguments(mh, 0, collector); } - mh = MethodHandles.insertArguments(mh, 0, offset); + return mh; } private MemoryLayout rootLayout() { return enclosing != null ? enclosing.rootLayout() : this.layout; @@ -251,25 +270,30 @@ sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout.byteSize()); // (MS, long) -> MS } else { sliceHandle = MH_SLICE_LAYOUT; // (MS, long, MemoryLayout) -> MS sliceHandle = MethodHandles.insertArguments(sliceHandle, 2, layout); // (MS, long) -> MS } - sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle()); // (MS, ...) -> MS + sliceHandle = MethodHandles.collectArguments(sliceHandle, 1, offsetHandle()); // (MS, long, ...) -> MS if (enclosing != null) { - MethodHandle checkHandle = MethodHandles.insertArguments(MH_CHECK_ALIGN, 1, rootLayout()); - sliceHandle = MethodHandles.filterArguments(sliceHandle, 0, checkHandle); + // insert align check for the root layout on the initial MS + offset + MethodType oldType = sliceHandle.type(); + MethodHandle alignCheck = MethodHandles.insertArguments(MH_CHECK_ALIGN, 2, rootLayout()); + sliceHandle = MethodHandles.collectArguments(sliceHandle, 0, alignCheck); // (MS, long, MS, long) -> MS + int[] reorder = IntStream.concat(IntStream.of(0, 1), IntStream.range(0, oldType.parameterCount())).toArray(); + sliceHandle = MethodHandles.permuteArguments(sliceHandle, oldType, reorder); // (MS, long, ...) -> MS } return sliceHandle; } - private static MemorySegment checkAlign(MemorySegment segment, MemoryLayout constraint) { - if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(0, constraint)) { - throw new IllegalArgumentException("Target offset incompatible with alignment constraints: " + constraint.byteAlignment()); + private static void checkAlign(MemorySegment segment, long offset, MemoryLayout constraint) { + if (!((AbstractMemorySegmentImpl) segment).isAlignedForElement(offset, constraint)) { + throw new IllegalArgumentException(String.format( + "Target offset %d is incompatible with alignment constraint %d (of %s) for segment %s" + , offset, constraint.byteAlignment(), constraint, segment)); } - return segment; } public MemoryLayout layout() { return layout; } @@ -290,19 +314,31 @@ return new LayoutPath(layout, 0L, EMPTY_STRIDES, EMPTY_BOUNDS, handles, null); } // 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())); } } private static IllegalArgumentException badLayoutPath(String cause) { return new IllegalArgumentException("Bad layout path: " + cause); @@ -318,10 +354,17 @@ long[] newBounds = Arrays.copyOf(bounds, bounds.length + 1); newBounds[bounds.length] = 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. */ public static final class PathElementImpl implements MemoryLayout.PathElement, UnaryOperator { diff a/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java --- a/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java @@ -46,12 +46,11 @@ this.unmapper = unmapper; } @Override ByteBuffer makeByteBuffer() { - return NIO_ACCESS.newMappedByteBuffer(unmapper, min, (int)length, null, - scope == MemorySessionImpl.GLOBAL ? null : this); + return NIO_ACCESS.newMappedByteBuffer(unmapper, min, (int)length, null, this); } @Override MappedMemorySegmentImpl dup(long offset, long size, boolean readOnly, MemorySessionImpl scope) { return new MappedMemorySegmentImpl(min + offset, unmapper, size, readOnly, scope); diff a/src/java.base/share/classes/jdk/internal/foreign/MemoryInspection.java b/src/java.base/share/classes/jdk/internal/foreign/MemoryInspection.java --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/foreign/MemoryInspection.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022, 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. + */ +package jdk.internal.foreign; + +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.Buffer; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; +import static jdk.internal.foreign.MemoryInspectionUtil.*; + +/** + * Class that supports inspection of MemorySegments through MemoryLayouts. + *

    + * Memory abstractions such as ByteBuffers and byte arrays can be inspected via wrapping methods + * such as {@link MemorySegment#ofArray(byte[])} and {@link MemorySegment#ofBuffer(Buffer)}. + * + * @since 20 + */ +public final class MemoryInspection { + + // Suppresses default constructor, ensuring non-instantiability. + private MemoryInspection() { + } + + /** + * Returns a human-readable view of the provided {@linkplain MemorySegment memory} viewed + * through the provided {@linkplain MemoryLayout layout} using the provided {@code renderer}. + *

    + * The exact format of the returned view is unspecified and should not + * be acted upon programmatically. + *

    + * As an example, a MemorySegment viewed though the following memory layout + * {@snippet lang = java: + * var layout = MemoryLayout.structLayout( + * ValueLayout.JAVA_INT.withName("x"), + * ValueLayout.JAVA_INT.withName("y") + * ).withName("Point"); + * + * MemoryInspection.inspect(segment, layout, ValueLayoutRenderer.standard()) + * .forEach(System.out::println); + * + *} + * might be rendered to something like this: + * {@snippet lang = text: + * Point { + * x=1, + * y=2 + * } + *} + *

    + * This method is intended to view memory segments through small and medium-sized memory layouts. + * + * @param segment to be viewed + * @param layout to use as a layout when viewing the memory segment + * @param renderer to apply when rendering value layouts + * @return a view of the memory abstraction viewed through the memory layout + */ + public static Stream inspect(MemorySegment segment, + MemoryLayout layout, + BiFunction renderer) { + requireNonNull(segment); + requireNonNull(layout); + requireNonNull(renderer); + return MemoryInspectionUtil.inspect(segment, layout, renderer); + } + + /** + * {@return a standard value layout renderer that will render numeric values into decimal form and where + * other value types are rendered to a reasonable "natural" form} + *

    + * More specifically, values types are rendered as follows: + *

      + *
    • Numeric values are rendered in decimal form (e.g 1 or 1.2).
    • + *
    • Boolean values are rendered as {@code true} or {@code false}.
    • + *
    • Character values are rendered as {@code char}.
    • + *
    • Address values are rendered in hexadecimal form e.g. {@code 0x0000000000000000} (on 64-bit platforms) or + * {@code 0x00000000} (on 32-bit platforms)
    • + *
    + */ + public static BiFunction standardRenderer() { + return STANDARD_VALUE_LAYOUT_RENDERER; + } + +} diff a/src/java.base/share/classes/jdk/internal/foreign/MemoryInspectionUtil.java b/src/java.base/share/classes/jdk/internal/foreign/MemoryInspectionUtil.java --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/foreign/MemoryInspectionUtil.java @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2022, 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. + */ + +package jdk.internal.foreign; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.GroupLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; +import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.ValueLayout; +import java.lang.foreign.UnionLayout; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * Internal class to support inspection MemorySegments into various formats. + */ +final class MemoryInspectionUtil { + + static final BiFunction STANDARD_VALUE_LAYOUT_RENDERER = new StandardValueLayoutRenderer(); + + // Suppresses default constructor, ensuring non-instantiability. + private MemoryInspectionUtil() { + } + + static Stream inspect(MemorySegment segment, + MemoryLayout layout, + BiFunction renderer) { + requireNonNull(segment); + requireNonNull(layout); + requireNonNull(renderer); + + final var builder = Stream.builder(); + toString0(segment, layout, renderer, builder::add, new ViewState(), ""); + return builder.build(); + } + + private static void toString0(MemorySegment segment, + MemoryLayout layout, + BiFunction renderer, + Consumer action, + ViewState state, + String suffix) { + + switch (layout) { + case ValueLayout.OfBoolean ofBoolean -> + action.accept(renderValueLayout(state, ofBoolean, renderer.apply(ofBoolean, segment.get(ofBoolean, state.indexAndAdd(ofBoolean))), suffix)); + case ValueLayout.OfByte ofByte -> + action.accept(renderValueLayout(state, ofByte, renderer.apply(ofByte, segment.get(ofByte, state.indexAndAdd(ofByte))), suffix)); + case ValueLayout.OfShort ofShort -> + action.accept(renderValueLayout(state, ofShort, renderer.apply(ofShort, segment.get(ofShort, state.indexAndAdd(ofShort))), suffix)); + case ValueLayout.OfChar ofChar -> + action.accept(renderValueLayout(state, ofChar, renderer.apply(ofChar, segment.get(ofChar, state.indexAndAdd(ofChar))), suffix)); + case ValueLayout.OfInt ofInt -> + action.accept(renderValueLayout(state, ofInt, renderer.apply(ofInt, segment.get(ofInt, state.indexAndAdd(ofInt))), suffix)); + case ValueLayout.OfLong ofLong -> + action.accept(renderValueLayout(state, ofLong, renderer.apply(ofLong, segment.get(ofLong, state.indexAndAdd(ofLong))), suffix)); + case ValueLayout.OfFloat ofFloat -> + action.accept(renderValueLayout(state, ofFloat, renderer.apply(ofFloat, segment.get(ofFloat, state.indexAndAdd(ofFloat))), suffix)); + case ValueLayout.OfDouble ofDouble -> + action.accept(renderValueLayout(state, ofDouble, renderer.apply(ofDouble, segment.get(ofDouble, state.indexAndAdd(ofDouble))), suffix)); + case AddressLayout addressLayout -> + action.accept(renderValueLayout(state, addressLayout, renderer.apply(addressLayout, segment.get(addressLayout, state.indexAndAdd(addressLayout))), suffix)); + case PaddingLayout paddingLayout -> { + action.accept(state.indentSpaces() + paddingLayout.byteSize() + " padding bytes"); + state.indexAndAdd(paddingLayout); + } + case GroupLayout groupLayout -> { + /* Strictly, we should provide all permutations of unions. + * So, if we have a union U = (A|B),(C|D) then we should present: + * (A,C), (A,D), (B,C) and (B,D) + */ + + final var separator = groupLayout instanceof StructLayout + ? "," // Struct separator + : "|"; // Union separator + + action.accept(indentedLabel(state, groupLayout) + " {"); + state.incrementIndent(); + final var members = groupLayout.memberLayouts(); + final long initialIndex = state.index(); + long maxIndex = initialIndex; + for (int i = 0; i < members.size(); i++) { + if (groupLayout instanceof UnionLayout) { + // If it is a union, we need to reset the index for each member + state.index(initialIndex); + // We record the max index used for any union member so that we can leave off from there + maxIndex = Math.max(maxIndex, state.index()); + } + toString0(segment, members.get(i), renderer, action, state, (i != (members.size() - 1)) ? separator : ""); + if (groupLayout instanceof UnionLayout) { + // This is the best we can do. + state.index(maxIndex); + } + } + state.decrementIndent(); + action.accept(state.indentSpaces() + "}" + suffix); + } + case SequenceLayout sequenceLayout -> { + action.accept(indentedLabel(state, sequenceLayout) + " ["); + state.incrementIndent(); + final long elementCount = sequenceLayout.elementCount(); + for (long i = 0; i < elementCount; i++) { + toString0(segment, sequenceLayout.elementLayout(), renderer, action, state, (i != (elementCount - 1L)) ? "," : ""); + } + state.decrementIndent(); + action.accept(state.indentSpaces() + "]" + suffix); + } + } + } + + static String renderValueLayout(ViewState state, + ValueLayout layout, + String value, + String suffix) { + return indentedLabel(state, layout) + "=" + value + suffix; + } + + static String indentedLabel(ViewState state, + MemoryLayout layout) { + return state.indentSpaces() + layout.name() + .orElseGet(layout::toString); + } + + static final class ViewState { + + private static final int SPACES_PER_INDENT = 4; + + // Holding a non-static indents allows simple thread-safe use + private final StringBuilder indents = new StringBuilder(); + + private int indent; + private long index; + + void incrementIndent() { + indent++; + } + + void decrementIndent() { + indent--; + } + + String indentSpaces() { + final int spaces = indent * SPACES_PER_INDENT; + while (indents.length() < spaces) { + // Expand as needed + indents.append(" "); + } + return indents.substring(0, spaces); + } + + long index() { + return index; + } + + void index(long index) { + this.index = index; + } + + long indexAndAdd(long delta) { + final long val = index; + index += delta; + return val; + } + + long indexAndAdd(MemoryLayout layout) { + return indexAndAdd(layout.byteSize()); + } + } + + private static final class StandardValueLayoutRenderer implements BiFunction { + + @Override + public String apply(ValueLayout layout, Object o) { + requireNonNull(layout); + requireNonNull(o); + + return switch (layout) { + case ValueLayout.OfBoolean __ when o instanceof Boolean b -> Boolean.toString(b); + case ValueLayout.OfByte __ when o instanceof Byte b -> Byte.toString(b); + case ValueLayout.OfShort __ when o instanceof Short s -> Short.toString(s); + case ValueLayout.OfChar __ when o instanceof Character c -> Character.toString(c); + case ValueLayout.OfInt __ when o instanceof Integer i -> Integer.toString(i); + case ValueLayout.OfLong __ when o instanceof Long l -> Long.toString(l); + case ValueLayout.OfFloat __ when o instanceof Float f -> Float.toString(f); + case ValueLayout.OfDouble __ when o instanceof Double d -> Double.toString(d); + case AddressLayout __ when o instanceof MemorySegment m -> + String.format("0x%0" + (ValueLayout.ADDRESS.byteSize() * 2) + "X", m.address()); + default -> + throw new UnsupportedOperationException("layout " + layout + " for " + o.getClass().getName() + " not supported"); + }; + } + + @Override + public String toString() { + return singletonToString(StandardValueLayoutRenderer.class); + } + } + + private static String singletonToString(Class implementingClass) { + return "The " + implementingClass.getName() + " singleton"; + } + +} diff a/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java b/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java --- a/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java @@ -31,10 +31,12 @@ import java.lang.foreign.MemorySegment.Scope; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.ref.Cleaner; import java.util.Objects; + +import jdk.internal.foreign.GlobalSession.HeapSession; import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.vm.annotation.ForceInline; /** * This class manages the temporal bounds associated with a memory segment as well @@ -57,14 +59,14 @@ static final int CLOSED = -2; static final VarHandle STATE; static final int MAX_FORKS = Integer.MAX_VALUE; - public static final MemorySessionImpl GLOBAL = new GlobalSession(null); - static final ScopedMemoryAccess.ScopedAccessError ALREADY_CLOSED = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::alreadyClosed); static final ScopedMemoryAccess.ScopedAccessError WRONG_THREAD = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::wrongThread); + // This is the session of all zero-length memory segments + static final GlobalSession NATIVE_SESSION = new GlobalSession(); final ResourceList resourceList; final Thread owner; int state = OPEN; @@ -75,25 +77,15 @@ throw new ExceptionInInitializerError(ex); } } public Arena asArena() { - return new Arena() { - @Override - public Scope scope() { - return MemorySessionImpl.this; - } - - @Override - public void close() { - MemorySessionImpl.this.close(); - } - }; + return new ArenaImpl(this); } @ForceInline - public static final MemorySessionImpl toMemorySession(Arena arena) { + public static MemorySessionImpl toMemorySession(Arena arena) { return (MemorySessionImpl) arena.scope(); } public final boolean isCloseableBy(Thread thread) { Objects.requireNonNull(thread); @@ -105,14 +97,14 @@ Objects.requireNonNull(runnable); addInternal(ResourceList.ResourceCleanup.ofRunnable(runnable)); } /** - * Add a cleanup action. If a failure occurred (because of a add vs. close race), call the cleanup action. + * Add a cleanup action. If a failure occurred (because of an add vs. close race), call the cleanup action. * This semantics is useful when allocating new memory segments, since we first do a malloc/mmap and _then_ * we register the cleanup (free/munmap) against the session; so, if registration fails, we still have to - * cleanup memory. From the perspective of the client, such a failure would manifest as a factory + * clean up memory. From the perspective of the client, such a failure would manifest as a factory * returning a segment that is already "closed" - which is always possible anyway (e.g. if the session * is closed _after_ the cleanup for the segment is registered but _before_ the factory returns the * new segment to the client). For this reason, it's not worth adding extra complexity to the segment * initialization logic here - and using an optimistic logic works well in practice. */ @@ -151,13 +143,16 @@ public static MemorySessionImpl createImplicit(Cleaner cleaner) { return new ImplicitSession(cleaner); } - public MemorySegment allocate(long byteSize, long byteAlignment) { - Utils.checkAllocationSizeAndAlign(byteSize, byteAlignment); - return NativeMemorySegmentImpl.makeNativeSegment(byteSize, byteAlignment, this); + public static MemorySessionImpl createGlobal() { + return new GlobalSession(); + } + + public static MemorySessionImpl createHeap(Object ref) { + return new HeapSession(ref); } public abstract void release0(); public abstract void acquire0(); @@ -208,21 +203,21 @@ } /** * Checks that this session is still alive (see {@link #isAlive()}). * @throws IllegalStateException if this session is already closed or if this is - * a confined session and this method is called outside of the owner thread. + * a confined session and this method is called outside the owner thread. */ public void checkValidState() { try { checkValidStateRaw(); } catch (ScopedMemoryAccess.ScopedAccessError error) { throw error.newRuntimeException(); } } - public static final void checkValidState(MemorySegment segment) { + public static void checkValidState(MemorySegment segment) { ((AbstractMemorySegmentImpl)segment).sessionImpl().checkValidState(); } @Override protected Object clone() throws CloneNotSupportedException { @@ -234,23 +229,19 @@ } /** * Closes this session, executing any cleanup action (where provided). * @throws IllegalStateException if this session is already closed or if this is - * a confined session and this method is called outside of the owner thread. + * a confined session and this method is called outside the owner thread. */ public void close() { justClose(); resourceList.cleanup(); } abstract void justClose(); - public static MemorySessionImpl heapSession(Object ref) { - return new GlobalSession(ref); - } - /** * A list of all cleanup actions associated with a memory session. Cleanup actions are modelled as instances * of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a session * is shared or confined, different implementations of this class will be used, see {@link ConfinedSession.ConfinedResourceList} * and {@link SharedSession.SharedResourceList}. diff a/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java --- a/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java @@ -31,11 +31,10 @@ import java.util.Optional; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; import jdk.internal.vm.annotation.ForceInline; -import sun.security.action.GetBooleanAction; /** * Implementation for native memory segments. A native memory segment is essentially a wrapper around * a native long address. */ @@ -44,11 +43,10 @@ private static final Unsafe UNSAFE = Unsafe.getUnsafe(); // The maximum alignment supported by malloc - typically 16 bytes on // 64-bit platforms and 8 bytes on 32-bit platforms. private static final long MAX_MALLOC_ALIGN = Unsafe.ADDRESS_SIZE == 4 ? 8 : 16; - private static final boolean SKIP_ZERO_MEMORY = GetBooleanAction.privilegedGetProperty("jdk.internal.foreign.skipZeroMemory"); final long min; @ForceInline NativeMemorySegmentImpl(long min, long length, boolean readOnly, MemorySessionImpl scope) { @@ -65,11 +63,11 @@ * segment class hierarchy, it is possible to end up in a situation where this constructor is called * when the static fields in this class are not yet initialized. */ @ForceInline public NativeMemorySegmentImpl() { - super(0L, false, new GlobalSession(null)); + super(0L, false, MemorySessionImpl.NATIVE_SESSION); this.min = 0L; } @Override public long address() { @@ -87,12 +85,11 @@ return new NativeMemorySegmentImpl(min + offset, size, readOnly, scope); } @Override ByteBuffer makeByteBuffer() { - return NIO_ACCESS.newDirectByteBuffer(min, (int) this.length, null, - scope == MemorySessionImpl.GLOBAL ? null : this); + return NIO_ACCESS.newDirectByteBuffer(min, (int) this.length, null, this); } @Override public boolean isNative() { return true; @@ -113,42 +110,52 @@ return 0; } // factories - public static MemorySegment makeNativeSegment(long byteSize, long byteAlignment, MemorySessionImpl sessionImpl) { + public static MemorySegment makeNativeSegment(long byteSize, long byteAlignment, MemorySessionImpl sessionImpl, + boolean shouldReserve) { sessionImpl.checkValidState(); if (VM.isDirectMemoryPageAligned()) { byteAlignment = Math.max(byteAlignment, NIO_ACCESS.pageSize()); } long alignedSize = Math.max(1L, byteAlignment > MAX_MALLOC_ALIGN ? byteSize + (byteAlignment - 1) : byteSize); - NIO_ACCESS.reserveMemory(alignedSize, byteSize); - - long buf = UNSAFE.allocateMemory(alignedSize); - if (!SKIP_ZERO_MEMORY) { - UNSAFE.setMemory(buf, alignedSize, (byte)0); + if (shouldReserve) { + NIO_ACCESS.reserveMemory(alignedSize, byteSize); } + + long buf = allocateMemoryWrapper(alignedSize); long alignedBuf = Utils.alignUp(buf, byteAlignment); AbstractMemorySegmentImpl segment = new NativeMemorySegmentImpl(buf, alignedSize, false, sessionImpl); sessionImpl.addOrCleanupIfFail(new MemorySessionImpl.ResourceList.ResourceCleanup() { @Override public void cleanup() { UNSAFE.freeMemory(buf); - NIO_ACCESS.unreserveMemory(alignedSize, byteSize); + if (shouldReserve) { + NIO_ACCESS.unreserveMemory(alignedSize, byteSize); + } } }); if (alignedSize != byteSize) { long delta = alignedBuf - buf; segment = segment.asSlice(delta, byteSize); } return segment; } + private static long allocateMemoryWrapper(long size) { + try { + return UNSAFE.allocateMemory(size); + } catch (IllegalArgumentException ex) { + throw new OutOfMemoryError(); + } + } + // Unsafe native segment factories. These are used by the implementation code, to skip the sanity checks // associated with MemorySegment::ofAddress. @ForceInline public static MemorySegment makeNativeSegmentUnchecked(long min, long byteSize, MemorySessionImpl sessionImpl, Runnable action) { @@ -166,8 +173,8 @@ return new NativeMemorySegmentImpl(min, byteSize, false, sessionImpl); } @ForceInline public static MemorySegment makeNativeSegmentUnchecked(long min, long byteSize) { - return new NativeMemorySegmentImpl(min, byteSize, false, new GlobalSession(null)); + return new NativeMemorySegmentImpl(min, byteSize, false, MemorySessionImpl.NATIVE_SESSION); } } diff a/src/java.base/share/classes/jdk/internal/foreign/SharedSession.java b/src/java.base/share/classes/jdk/internal/foreign/SharedSession.java --- a/src/java.base/share/classes/jdk/internal/foreign/SharedSession.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SharedSession.java @@ -25,11 +25,10 @@ package jdk.internal.foreign; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import java.lang.ref.Cleaner; import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.vm.annotation.ForceInline; /** * A shared session, which can be shared across multiple threads. Closing a shared session has to ensure that diff a/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java b/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java --- a/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java @@ -29,17 +29,15 @@ import java.lang.foreign.SegmentAllocator; public final class SlicingAllocator implements SegmentAllocator { private final MemorySegment segment; - private final long maxAlign; private long sp = 0L; public SlicingAllocator(MemorySegment segment) { this.segment = segment; - this.maxAlign = ((AbstractMemorySegmentImpl)segment).maxAlignMask(); } MemorySegment trySlice(long byteSize, long byteAlignment) { long min = segment.address(); long start = Utils.alignUp(min + sp, byteAlignment) - min; diff a/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java b/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java @@ -0,0 +1,331 @@ +/* + * 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. + */ + +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 java.lang.foreign.MemorySegment; +import java.nio.charset.Charset; + +import static java.lang.foreign.ValueLayout.*; + +/** + * Miscellaneous functions to read and write strings, in various charsets. + */ +public final 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 -> 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 -> writeByte(segment, offset, charset, string); + case DOUBLE_BYTE -> writeShort(segment, offset, charset, string); + case QUAD_BYTE -> writeInt(segment, offset, charset, string); + } + } + + 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 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 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 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 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 writeInt(MemorySegment segment, long offset, Charset charset, String string) { + int bytes = copyBytes(string, segment, charset, offset); + segment.set(JAVA_INT, offset + bytes, 0); + } + + /** + * {@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; + } + } + + // 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)); + } + + /* 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; + } + } + + // 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 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 newIaeStringTooLarge(); + } + + public enum CharsetKind { + SINGLE_BYTE(1), + DOUBLE_BYTE(2), + QUAD_BYTE(4); + + final int terminatorCharSize; + + CharsetKind(int terminatorCharSize) { + this.terminatorCharSize = terminatorCharSize; + } + + public int terminatorCharSize() { + return terminatorCharSize; + } + + public static CharsetKind of(Charset charset) { + // 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 UnsupportedOperationException("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 a/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java b/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java --- a/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SystemLookup.java @@ -73,22 +73,22 @@ } } private static SymbolLookup makeWindowsLookup() { @SuppressWarnings("removal") - String systemRoot = AccessController.doPrivileged(new PrivilegedAction() { + String systemRoot = AccessController.doPrivileged(new PrivilegedAction<>() { @Override public String run() { return System.getenv("SystemRoot"); } }); Path system32 = Path.of(systemRoot, "System32"); Path ucrtbase = system32.resolve("ucrtbase.dll"); Path msvcrt = system32.resolve("msvcrt.dll"); @SuppressWarnings("removal") - boolean useUCRT = AccessController.doPrivileged(new PrivilegedAction() { + boolean useUCRT = AccessController.doPrivileged(new PrivilegedAction<>() { @Override public Boolean run() { return Files.exists(ucrtbase); } }); diff a/src/java.base/share/classes/jdk/internal/foreign/Utils.java b/src/java.base/share/classes/jdk/internal/foreign/Utils.java --- a/src/java.base/share/classes/jdk/internal/foreign/Utils.java +++ b/src/java.base/share/classes/jdk/internal/foreign/Utils.java @@ -27,11 +27,10 @@ package jdk.internal.foreign; import java.lang.foreign.AddressLayout; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; import java.lang.foreign.StructLayout; import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -42,14 +41,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import jdk.internal.access.SharedSecrets; import jdk.internal.foreign.abi.SharedUtils; +import jdk.internal.misc.Unsafe; import jdk.internal.vm.annotation.ForceInline; import sun.invoke.util.Wrapper; -import static java.lang.foreign.ValueLayout.JAVA_BYTE; import static sun.security.action.GetPropertyAction.privilegedGetProperty; /** * This class contains misc helper functions to support creation of memory segments. */ @@ -96,11 +95,23 @@ static VarHandle put(ValueLayout layout, VarHandle handle) { VarHandle prev = HANDLE_MAP.putIfAbsent(layout, handle); return prev != null ? prev : handle; } + + static VarHandle get(ValueLayout layout) { + return HANDLE_MAP.get(layout); + } + } + layout = layout.withoutName(); // name doesn't matter + // keep the addressee layout as it's used below + + VarHandle handle = VarHandleCache.get(layout); + if (handle != null) { + return handle; } + Class baseCarrier = layout.carrier(); if (layout.carrier() == MemorySegment.class) { baseCarrier = switch ((int) ValueLayout.ADDRESS.byteSize()) { case Long.BYTES -> long.class; case Integer.BYTES -> int.class; @@ -108,11 +119,11 @@ }; } else if (layout.carrier() == boolean.class) { baseCarrier = byte.class; } - VarHandle handle = SharedSecrets.getJavaLangInvokeAccess().memorySegmentViewHandle(baseCarrier, + handle = SharedSecrets.getJavaLangInvokeAccess().memorySegmentViewHandle(baseCarrier, layout.byteAlignment() - 1, layout.order()); if (layout.carrier() == boolean.class) { handle = MethodHandles.filterValue(handle, BOOL_TO_BYTE, BYTE_TO_BOOL); } else if (layout instanceof AddressLayout addressLayout) { @@ -134,35 +145,23 @@ } @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); } - public static void copy(MemorySegment addr, byte[] bytes) { - var heapSegment = MemorySegment.ofArray(bytes); - addr.copyFrom(heapSegment); - addr.set(JAVA_BYTE, bytes.length, (byte)0); - } - - public static MemorySegment toCString(byte[] bytes, SegmentAllocator allocator) { - MemorySegment addr = allocator.allocate(bytes.length + 1); - copy(addr, bytes); - return addr; - } - @ForceInline public static boolean isAligned(long offset, long align) { return (offset & (align - 1)) == 0; } @@ -276,6 +275,38 @@ public static String toHexString(long value) { return "0x" + Long.toHexString(value); } + public record BaseAndScale(int base, long scale) { + + public static final BaseAndScale BYTE = + new BaseAndScale(Unsafe.ARRAY_BYTE_BASE_OFFSET, Unsafe.ARRAY_BYTE_INDEX_SCALE); + public static final BaseAndScale CHAR = + new BaseAndScale(Unsafe.ARRAY_CHAR_BASE_OFFSET, Unsafe.ARRAY_CHAR_INDEX_SCALE); + public static final BaseAndScale SHORT = + new BaseAndScale(Unsafe.ARRAY_SHORT_BASE_OFFSET, Unsafe.ARRAY_SHORT_INDEX_SCALE); + public static final BaseAndScale INT = + new BaseAndScale(Unsafe.ARRAY_INT_BASE_OFFSET, Unsafe.ARRAY_INT_INDEX_SCALE); + public static final BaseAndScale FLOAT = + new BaseAndScale(Unsafe.ARRAY_FLOAT_BASE_OFFSET, Unsafe.ARRAY_FLOAT_INDEX_SCALE); + public static final BaseAndScale LONG = + new BaseAndScale(Unsafe.ARRAY_LONG_BASE_OFFSET, Unsafe.ARRAY_LONG_INDEX_SCALE); + public static final BaseAndScale DOUBLE = + new BaseAndScale(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, Unsafe.ARRAY_DOUBLE_INDEX_SCALE); + + public static BaseAndScale of(Object array) { + return switch (array) { + case byte[] __ -> BaseAndScale.BYTE; + case char[] __ -> BaseAndScale.CHAR; + case short[] __ -> BaseAndScale.SHORT; + case int[] __ -> BaseAndScale.INT; + case float[] __ -> BaseAndScale.FLOAT; + case long[] __ -> BaseAndScale.LONG; + case double[] __ -> BaseAndScale.DOUBLE; + default -> throw new IllegalArgumentException("Not a supported array class: " + array.getClass().getSimpleName()); + }; + } + + } + } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java @@ -52,10 +52,11 @@ import java.lang.foreign.StructLayout; import java.lang.foreign.UnionLayout; import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.util.HashSet; import java.util.List; import java.nio.ByteOrder; import java.util.Objects; import java.util.Set; @@ -71,10 +72,11 @@ } private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) {} private final SoftReferenceCache DOWNCALL_CACHE = new SoftReferenceCache<>(); private final SoftReferenceCache UPCALL_CACHE = new SoftReferenceCache<>(); + private final Set CANONICAL_LAYOUTS_CACHE = new HashSet<>(canonicalLayouts().values()); @Override @CallerSensitive public final MethodHandle downcallHandle(MemorySegment symbol, FunctionDescriptor function, Option... options) { Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle"); @@ -87,11 +89,11 @@ public final MethodHandle downcallHandle(FunctionDescriptor function, Option... options) { Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle"); return downcallHandle0(function, options); } - private final MethodHandle downcallHandle0(FunctionDescriptor function, Option... options) { + private MethodHandle downcallHandle0(FunctionDescriptor function, Option... options) { Objects.requireNonNull(function); Objects.requireNonNull(options); checkLayouts(function); function = stripNames(function); LinkerOptions optionSet = LinkerOptions.forDowncall(function, options); @@ -211,40 +213,40 @@ checkLayoutRecursive(sl.elementLayout()); } } // check for trailing padding - private static void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) { + private void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) { long expectedSize = Utils.alignUp(maxUnpaddedOffset, gl.byteAlignment()); if (gl.byteSize() != expectedSize) { throw new IllegalArgumentException("Layout '" + gl + "' has unexpected size: " + gl.byteSize() + " != " + expectedSize); } } // checks both that there is no excess padding between 'memberLayout' and // the previous layout - private static void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout, + private void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout, long lastUnpaddedOffset, long offset) { long expectedOffset = Utils.alignUp(lastUnpaddedOffset, memberLayout.byteAlignment()); if (expectedOffset != offset) { throw new IllegalArgumentException("Member layout '" + memberLayout + "', of '" + parent + "'" + " found at unexpected offset: " + offset + " != " + expectedOffset); } } - private static void checkSupported(ValueLayout valueLayout) { + private void checkSupported(ValueLayout valueLayout) { valueLayout = valueLayout.withoutName(); if (valueLayout instanceof AddressLayout addressLayout) { valueLayout = addressLayout.withoutTargetLayout(); } - if (!SUPPORTED_LAYOUTS.contains(valueLayout.withoutName())) { + if (!CANONICAL_LAYOUTS_CACHE.contains(valueLayout.withoutName())) { throw new IllegalArgumentException("Unsupported layout: " + valueLayout); } } - private static void checkHasNaturalAlignment(MemoryLayout layout) { + private void checkHasNaturalAlignment(MemoryLayout layout) { if (!((AbstractLayout) layout).hasNaturalAlignment()) { throw new IllegalArgumentException("Layout alignment must be natural alignment: " + layout); } } @@ -271,18 +273,6 @@ private static FunctionDescriptor stripNames(FunctionDescriptor function) { return function.returnLayout() .map(rl -> FunctionDescriptor.of(stripNames(rl), stripNames(function.argumentLayouts()))) .orElseGet(() -> FunctionDescriptor.ofVoid(stripNames(function.argumentLayouts()))); } - - private static final Set SUPPORTED_LAYOUTS = Set.of( - ValueLayout.JAVA_BOOLEAN, - ValueLayout.JAVA_BYTE, - ValueLayout.JAVA_CHAR, - ValueLayout.JAVA_SHORT, - ValueLayout.JAVA_INT, - ValueLayout.JAVA_FLOAT, - ValueLayout.JAVA_LONG, - ValueLayout.JAVA_DOUBLE, - ValueLayout.ADDRESS - ); } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/Binding.java b/src/java.base/share/classes/jdk/internal/foreign/abi/Binding.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/Binding.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/Binding.java @@ -30,11 +30,10 @@ import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Deque; import java.util.List; import static java.lang.foreign.ValueLayout.JAVA_BYTE; @@ -55,11 +54,11 @@ * The binding interpreter stack starts off empty, and ends with the value to be returned as the only value on it. * A binding operator can be interpreted differently based on whether we are boxing or unboxing a value. For example, * the CONVERT_ADDRESS operator 'unboxes' a MemoryAddress to a long, but 'boxes' a long to a MemoryAddress. * * Here are some examples of binding recipes derived from C declarations, and according to the Windows ABI (recipes are - * ABI-specific). Note that each argument has it's own recipe, which is indicated by '[number]:' (though, the only + * ABI-specific). Note that each argument has its own recipe, which is indicated by '[number]:' (though, the only * example that has multiple arguments is the one using varargs). * * -------------------- * * void f(int i); diff a/src/java.base/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java b/src/java.base/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/BindingInterpreter.java @@ -47,14 +47,14 @@ } return stack.pop(); } @FunctionalInterface - interface StoreFunc { + public interface StoreFunc { void store(VMStorage storage, Class type, Object o); } @FunctionalInterface - interface LoadFunc { + public interface LoadFunc { Object load(VMStorage storage, Class type); } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/CallingSequence.java b/src/java.base/share/classes/jdk/internal/foreign/abi/CallingSequence.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/CallingSequence.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/CallingSequence.java @@ -190,11 +190,11 @@ .mapToInt(CapturableState::mask) .reduce(0, (a, b) -> a | b); } public boolean needsTransition() { - return !linkerOptions.isTrivial(); + return !linkerOptions.isCritical(); } public int numLeadingParams() { return 2 + (linkerOptions.hasCapturedCallState() ? 1 : 0); // 2 for addr, allocator } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/CapturableState.java b/src/java.base/share/classes/jdk/internal/foreign/abi/CapturableState.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/CapturableState.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/CapturableState.java @@ -31,11 +31,10 @@ import java.lang.foreign.ValueLayout; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.lang.foreign.ValueLayout.JAVA_INT; -import static sun.security.action.GetPropertyAction.privilegedGetProperty; public enum CapturableState { GET_LAST_ERROR ("GetLastError", JAVA_INT, 1 << 0, Utils.IS_WINDOWS), WSA_GET_LAST_ERROR("WSAGetLastError", JAVA_INT, 1 << 1, Utils.IS_WINDOWS), ERRNO ("errno", JAVA_INT, 1 << 2, true); diff a/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java b/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java @@ -61,11 +61,15 @@ LinkerOptionImpl opImpl = (LinkerOptionImpl) option; validator.accept(opImpl, desc); optionMap.put(option.getClass(), opImpl); } - return new LinkerOptions(optionMap); + LinkerOptions linkerOptions = new LinkerOptions(optionMap); + if (linkerOptions.hasCapturedCallState() && linkerOptions.isCritical()) { + throw new IllegalArgumentException("Incompatible linker options: captureCallState, critical"); + } + return linkerOptions; } public static LinkerOptions empty() { return EMPTY; } @@ -95,13 +99,13 @@ public int firstVariadicArgIndex() { return getOption(FirstVariadicArg.class).index(); } - public boolean isTrivial() { - IsTrivial it = getOption(IsTrivial.class); - return it != null; + public boolean isCritical() { + Critical c = getOption(Critical.class); + return c != null; } @Override public boolean equals(Object o) { if (this == o) return true; @@ -113,11 +117,11 @@ public int hashCode() { return Objects.hash(optionsMap); } public sealed interface LinkerOptionImpl extends Linker.Option - permits CaptureCallState, FirstVariadicArg, IsTrivial { + permits CaptureCallState, FirstVariadicArg, Critical { default void validateForDowncall(FunctionDescriptor descriptor) { throw new IllegalArgumentException("Not supported for downcall: " + this); } default void validateForUpcall(FunctionDescriptor descriptor) { @@ -139,12 +143,12 @@ public void validateForDowncall(FunctionDescriptor descriptor) { // done during construction } } - public record IsTrivial() implements LinkerOptionImpl { - public static IsTrivial INSTANCE = new IsTrivial(); + public record Critical() implements LinkerOptionImpl { + public static Critical INSTANCE = new Critical(); @Override public void validateForDowncall(FunctionDescriptor descriptor) { // always allowed } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java @@ -52,14 +52,12 @@ import java.lang.foreign.SegmentAllocator; 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; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -81,20 +79,20 @@ private static final MethodHandle MH_REACHABILITY_FENCE; public static final MethodHandle MH_CHECK_SYMBOL; private static final MethodHandle MH_CHECK_CAPTURE_SEGMENT; public static final AddressLayout C_POINTER = ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE)); + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, JAVA_BYTE)); public static final Arena DUMMY_ARENA = new Arena() { @Override public Scope scope() { throw new UnsupportedOperationException(); } @Override - public MemorySegment allocate(long byteSize) { + public MemorySegment allocate(long byteSize, long byteAlignment) { throw new UnsupportedOperationException(); } @Override public void close() { @@ -127,10 +125,14 @@ 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 * buffer, calling the target MethodHandle, and then returning the allocated MemorySegment. * @@ -174,11 +176,11 @@ target = collectArguments(MH_BUFFER_COPY, 1, target); // (MemorySegment, ...) MemorySegment if (dropReturn) { // no handling for return value, need to drop it target = dropReturn(target); } else { - // adjust return type so it matches the inferred type of the effective + // adjust return type so that it matches the inferred type of the effective // function descriptor target = target.asType(target.type().changeReturnType(MemorySegment.class)); } return target; @@ -249,28 +251,10 @@ case FALLBACK -> FallbackLinker.getInstance(); case UNSUPPORTED -> throw new UnsupportedOperationException("Platform does not support native linker"); }; } - public static String toJavaStringInternal(MemorySegment segment, long start) { - int len = strlen(segment, start); - byte[] bytes = new byte[len]; - MemorySegment.copy(segment, JAVA_BYTE, start, bytes, 0, len); - return new String(bytes, StandardCharsets.UTF_8); - } - - private static int strlen(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"); - } - static Map indexMap(Binding.Move[] moves) { return IntStream.range(0, moves.length) .boxed() .collect(Collectors.toMap(i -> moves[i].storage(), i -> i)); } @@ -432,24 +416,10 @@ throw new UnsupportedOperationException(); } }; } - public static final class SimpleVaArg { - public final MemoryLayout layout; - public final Object value; - - public SimpleVaArg(MemoryLayout layout, Object value) { - this.layout = layout; - this.value = value; - } - - public VarHandle varHandle() { - return layout.varHandle(); - } - } - static void writeOverSized(MemorySegment ptr, Class type, Object o) { // use VH_LONG for integers to zero out the whole register in the process if (type == long.class) { ptr.set(JAVA_LONG_UNALIGNED, 0, (long) o); } else if (type == int.class) { @@ -513,6 +483,37 @@ return ptr.get(JAVA_BOOLEAN, offset); } else { throw new IllegalArgumentException("Unsupported carrier: " + type); } } + + public static Map canonicalLayouts(ValueLayout longLayout, ValueLayout sizetLayout, ValueLayout wchartLayout) { + return Map.ofEntries( + // specified canonical layouts + Map.entry("bool", ValueLayout.JAVA_BOOLEAN), + Map.entry("char", ValueLayout.JAVA_BYTE), + Map.entry("short", ValueLayout.JAVA_SHORT), + Map.entry("int", ValueLayout.JAVA_INT), + Map.entry("float", ValueLayout.JAVA_FLOAT), + Map.entry("long", longLayout), + Map.entry("long long", ValueLayout.JAVA_LONG), + Map.entry("double", ValueLayout.JAVA_DOUBLE), + Map.entry("void*", ValueLayout.ADDRESS), + Map.entry("size_t", sizetLayout), + Map.entry("wchar_t", wchartLayout), + // unspecified size-dependent layouts + Map.entry("int8_t", ValueLayout.JAVA_BYTE), + Map.entry("int16_t", ValueLayout.JAVA_SHORT), + Map.entry("int32_t", ValueLayout.JAVA_INT), + Map.entry("int64_t", ValueLayout.JAVA_LONG), + // unspecified JNI layouts + Map.entry("jboolean", ValueLayout.JAVA_BOOLEAN), + Map.entry("jchar", ValueLayout.JAVA_CHAR), + Map.entry("jbyte", ValueLayout.JAVA_BYTE), + Map.entry("jshort", ValueLayout.JAVA_SHORT), + Map.entry("jint", ValueLayout.JAVA_INT), + Map.entry("jlong", ValueLayout.JAVA_LONG), + Map.entry("jfloat", ValueLayout.JAVA_FLOAT), + Map.entry("jdouble", ValueLayout.JAVA_DOUBLE) + ); + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java b/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/UpcallStubs.java @@ -26,19 +26,20 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.Arena; import jdk.internal.foreign.MemorySessionImpl; +import jdk.internal.foreign.Utils; public final class UpcallStubs { 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)); } } // natives diff a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/CallArranger.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/CallArranger.java @@ -35,11 +35,10 @@ import jdk.internal.foreign.abi.Binding; import jdk.internal.foreign.abi.CallingSequence; import jdk.internal.foreign.abi.CallingSequenceBuilder; import jdk.internal.foreign.abi.DowncallLinker; import jdk.internal.foreign.abi.LinkerOptions; -import jdk.internal.foreign.abi.UpcallLinker; import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.VMStorage; import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64CallArranger; import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64CallArranger; import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64CallArranger; @@ -244,22 +243,22 @@ fields and those fields are the chunks which are passed. For HFAs the rules are more complicated and ABI based: | enough registers | some registers, but not enough | no registers ----------------+------------------+---------------------------------+------------------------- Linux | FW in regs | CW on the stack | CW on the stack - MacOs, non-VA | FW in regs | FW on the stack | FW on the stack - MacOs, VA | FW in regs | CW on the stack | CW on the stack + macOS, non-VA | FW in regs | FW on the stack | FW on the stack + macOS, VA | FW in regs | CW on the stack | CW on the stack Windows, non-VF | FW in regs | CW on the stack | CW on the stack Windows, VF | FW in regs | CW split between regs and stack | CW on the stack (where FW = Field-wise copy, CW = Chunk-wise copy, VA is a variadic argument, and VF is a variadic function) For regular structs, the rules are as follows: | enough registers | some registers, but not enough | no registers ----------------+------------------+---------------------------------+------------------------- Linux | CW in regs | CW on the stack | CW on the stack - MacOs | CW in regs | CW on the stack | CW on the stack + macOS | CW in regs | CW on the stack | CW on the stack Windows, non-VF | CW in regs | CW on the stack | CW on the stack Windows, VF | CW in regs | CW split between regs and stack | CW on the stack */ StructStorage[] structStorages(GroupLayout layout, boolean forHFA) { int numChunks = (int)Utils.alignUp(layout.byteSize(), MAX_COPY_SIZE) / MAX_COPY_SIZE; diff a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java @@ -25,23 +25,30 @@ */ package jdk.internal.foreign.abi.aarch64.linux; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.aarch64.CallArranger; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; /** * ABI implementation based on ARM document "Procedure Call Standard for * the ARM 64-bit Architecture". */ public final class LinuxAArch64Linker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static LinuxAArch64Linker getInstance() { final class Holder { private static final LinuxAArch64Linker INSTANCE = new LinuxAArch64Linker(); } @@ -64,6 +71,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java @@ -25,23 +25,30 @@ */ package jdk.internal.foreign.abi.aarch64.macos; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.aarch64.CallArranger; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; /** - * ABI implementation for macOS on Apple silicon. Based on AAPCS with + * ABI implementation for macOS on Apple Silicon. Based on AAPCS with * changes to va_list and passing arguments on the stack. */ public final class MacOsAArch64Linker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static MacOsAArch64Linker getInstance() { final class Holder { private static final MacOsAArch64Linker INSTANCE = new MacOsAArch64Linker(); } @@ -64,6 +71,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64CallArranger.java @@ -50,11 +50,11 @@ // responsible for allocating storage and passing this into the // function. // // Although the AAPCS64 says r0-7 and v0-7 are all valid return // registers, it's not possible to generate a C function that uses - // r2-7 and v4-7 so they are omitted here. + // r2-7 and v4-7 so, they are omitted here. private static final ABIDescriptor WindowsAArch64AbiDescriptor = abiFor( new VMStorage[] { r0, r1, r2, r3, r4, r5, r6, r7, INDIRECT_RESULT}, new VMStorage[] { v0, v1, v2, v3, v4, v5, v6, v7 }, new VMStorage[] { r0, r1 }, new VMStorage[] { v0, v1, v2, v3 }, diff a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java @@ -26,22 +26,29 @@ */ package jdk.internal.foreign.abi.aarch64.windows; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.aarch64.CallArranger; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; /** * ABI implementation for Windows/AArch64. Based on AAPCS with * changes to va_list. */ public final class WindowsAArch64Linker extends AbstractLinker { + + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR); private static WindowsAArch64Linker instance; public static WindowsAArch64Linker getInstance() { if (instance == null) { instance = new WindowsAArch64Linker(); @@ -61,6 +68,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FFIType.java @@ -35,17 +35,17 @@ import java.lang.foreign.StructLayout; import java.lang.foreign.UnionLayout; import java.lang.foreign.ValueLayout; import java.lang.invoke.VarHandle; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Predicate; import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_BYTE; import static java.lang.foreign.ValueLayout.JAVA_INT; import static java.lang.foreign.ValueLayout.JAVA_LONG; import static java.lang.foreign.ValueLayout.JAVA_SHORT; /** @@ -56,22 +56,18 @@ * unsigned short type; * struct _ffi_type **elements; * } ffi_type; */ class FFIType { - private static final ValueLayout SIZE_T = switch ((int) ADDRESS.byteSize()) { - case 8 -> JAVA_LONG; - case 4 -> JAVA_INT; - default -> throw new IllegalStateException("Address size not supported: " + ADDRESS.byteSize()); - }; + static final ValueLayout SIZE_T = layoutFor((int)ADDRESS.byteSize()); private static final ValueLayout UNSIGNED_SHORT = JAVA_SHORT; private static final StructLayout LAYOUT = Utils.computePaddedStructLayout( SIZE_T, UNSIGNED_SHORT, UNSIGNED_SHORT.withName("type"), ADDRESS.withName("elements")); private static final VarHandle VH_TYPE = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("type")); private static final VarHandle VH_ELEMENTS = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("elements")); - private static final VarHandle VH_SIZE_T_ARRAY = SIZE_T.arrayElementVarHandle(); + private static final VarHandle VH_SIZE_T = SIZE_T.varHandle(); private static MemorySegment make(List elements, FFIABI abi, Arena scope) { MemorySegment elementsSeg = scope.allocate((elements.size() + 1) * ADDRESS.byteSize()); int i = 0; for (; i < elements.size(); i++) { @@ -81,12 +77,12 @@ } // elements array is null-terminated elementsSeg.setAtIndex(ADDRESS, i, MemorySegment.NULL); MemorySegment ffiType = scope.allocate(LAYOUT); - VH_TYPE.set(ffiType, LibFallback.structTag()); - VH_ELEMENTS.set(ffiType, elementsSeg); + VH_TYPE.set(ffiType, 0L, LibFallback.structTag()); + VH_ELEMENTS.set(ffiType, 0L, elementsSeg); return ffiType; } private static final Map, MemorySegment> CARRIER_TO_TYPE = Map.of( @@ -130,17 +126,36 @@ LibFallback.getStructOffsets(structType, offsetsOut, abi); long expectedOffset = 0; int offsetIdx = 0; for (MemoryLayout element : structLayout.memberLayouts()) { if (!(element instanceof PaddingLayout)) { - long ffiOffset = (long) VH_SIZE_T_ARRAY.get(offsetsOut, offsetIdx++); + long ffiOffset = sizeTAtIndex(offsetsOut, offsetIdx++); if (ffiOffset != expectedOffset) { throw new IllegalArgumentException("Invalid group layout." + " Offset of '" + element.name().orElse("") + "': " + expectedOffset + " != " + ffiOffset); } } expectedOffset += element.byteSize(); } } } + + static ValueLayout layoutFor(int byteSize) { + return switch (byteSize) { + case 1 -> JAVA_BYTE; + case 2 -> JAVA_SHORT; + case 4 -> JAVA_INT; + case 8 -> JAVA_LONG; + default -> throw new IllegalStateException("Unsupported size: " + byteSize); + }; + } + + private static long sizeTAtIndex(MemorySegment segment, int index) { + long offset = SIZE_T.scale(0, index); + if (VH_SIZE_T.varType() == long.class) { + return (long) VH_SIZE_T.get(segment, offset); + } else { + return (int) VH_SIZE_T.get(segment, offset); // 'erase' to long + } + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java @@ -43,14 +43,24 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.Reference; import java.nio.ByteOrder; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN; +import static java.lang.foreign.ValueLayout.JAVA_BYTE; +import static java.lang.foreign.ValueLayout.JAVA_CHAR; +import static java.lang.foreign.ValueLayout.JAVA_DOUBLE; +import static java.lang.foreign.ValueLayout.JAVA_FLOAT; +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; +import static java.lang.foreign.ValueLayout.JAVA_SHORT; import static java.lang.invoke.MethodHandles.foldArguments; public final class FallbackLinker extends AbstractLinker { private static final MethodHandle MH_DO_DOWNCALL; @@ -229,34 +239,29 @@ writeValue(arg, layout, argSeg, addr -> {}); } private static void writeValue(Object arg, MemoryLayout layout, MemorySegment argSeg, Consumer acquireCallback) { - if (layout instanceof ValueLayout.OfBoolean bl) { - argSeg.set(bl, 0, (Boolean) arg); - } else if (layout instanceof ValueLayout.OfByte bl) { - argSeg.set(bl, 0, (Byte) arg); - } else if (layout instanceof ValueLayout.OfShort sl) { - argSeg.set(sl, 0, (Short) arg); - } else if (layout instanceof ValueLayout.OfChar cl) { - argSeg.set(cl, 0, (Character) arg); - } else if (layout instanceof ValueLayout.OfInt il) { - argSeg.set(il, 0, (Integer) arg); - } else if (layout instanceof ValueLayout.OfLong ll) { - argSeg.set(ll, 0, (Long) arg); - } else if (layout instanceof ValueLayout.OfFloat fl) { - argSeg.set(fl, 0, (Float) arg); - } else if (layout instanceof ValueLayout.OfDouble dl) { - argSeg.set(dl, 0, (Double) arg); - } else if (layout instanceof AddressLayout al) { - MemorySegment addrArg = (MemorySegment) arg; - acquireCallback.accept(addrArg); - argSeg.set(al, 0, addrArg); - } else if (layout instanceof GroupLayout) { - MemorySegment.copy((MemorySegment) arg, 0, argSeg, 0, argSeg.byteSize()); // by-value struct - } else { - assert layout == null; + switch (layout) { + case ValueLayout.OfBoolean bl -> argSeg.set(bl, 0, (Boolean) arg); + case ValueLayout.OfByte bl -> argSeg.set(bl, 0, (Byte) arg); + case ValueLayout.OfShort sl -> argSeg.set(sl, 0, (Short) arg); + case ValueLayout.OfChar cl -> argSeg.set(cl, 0, (Character) arg); + case ValueLayout.OfInt il -> argSeg.set(il, 0, (Integer) arg); + case ValueLayout.OfLong ll -> argSeg.set(ll, 0, (Long) arg); + case ValueLayout.OfFloat fl -> argSeg.set(fl, 0, (Float) arg); + case ValueLayout.OfDouble dl -> argSeg.set(dl, 0, (Double) arg); + case AddressLayout al -> { + MemorySegment addrArg = (MemorySegment) arg; + acquireCallback.accept(addrArg); + argSeg.set(al, 0, addrArg); + } + case GroupLayout __ -> + MemorySegment.copy((MemorySegment) arg, 0, argSeg, 0, argSeg.byteSize()); // by-value struct + case null, default -> { + assert layout == null; + } } } private static Object readValue(MemorySegment seg, MemoryLayout layout) { if (layout instanceof ValueLayout.OfBoolean bl) { @@ -281,6 +286,43 @@ return seg; } assert layout == null; return null; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } + + static final Map CANONICAL_LAYOUTS = new HashMap<>(); + + static { + CANONICAL_LAYOUTS.put("bool", JAVA_BOOLEAN); + CANONICAL_LAYOUTS.put("char", JAVA_BYTE); + CANONICAL_LAYOUTS.put("float", JAVA_FLOAT); + CANONICAL_LAYOUTS.put("double", JAVA_DOUBLE); + CANONICAL_LAYOUTS.put("long long", JAVA_LONG); + CANONICAL_LAYOUTS.put("void*", ADDRESS); + // platform-dependent sizes + CANONICAL_LAYOUTS.put("size_t", FFIType.SIZE_T); + CANONICAL_LAYOUTS.put("short", FFIType.layoutFor(LibFallback.shortSize())); + CANONICAL_LAYOUTS.put("int", FFIType.layoutFor(LibFallback.intSize())); + CANONICAL_LAYOUTS.put("long", FFIType.layoutFor(LibFallback.longSize())); + int wchar_size = LibFallback.wcharSize(); + if (wchar_size == 2) { + // prefer JAVA_CHAR + CANONICAL_LAYOUTS.put("wchar_t", JAVA_CHAR); + } else { + CANONICAL_LAYOUTS.put("wchar_t", FFIType.layoutFor(wchar_size)); + } + // JNI types + CANONICAL_LAYOUTS.put("jboolean", JAVA_BOOLEAN); + CANONICAL_LAYOUTS.put("jchar", JAVA_CHAR); + CANONICAL_LAYOUTS.put("jbyte", JAVA_BYTE); + CANONICAL_LAYOUTS.put("jshort", JAVA_SHORT); + CANONICAL_LAYOUTS.put("jint", JAVA_INT); + CANONICAL_LAYOUTS.put("jlong", JAVA_LONG); + CANONICAL_LAYOUTS.put("jfloat", JAVA_FLOAT); + CANONICAL_LAYOUTS.put("jdouble", JAVA_DOUBLE); + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/LibFallback.java @@ -63,10 +63,16 @@ static MemorySegment floatType() { return NativeConstants.FLOAT_TYPE; } static MemorySegment doubleType() { return NativeConstants.DOUBLE_TYPE; } static MemorySegment pointerType() { return NativeConstants.POINTER_TYPE; } static MemorySegment voidType() { return NativeConstants.VOID_TYPE; } + // platform-dependent types + static int shortSize() { return NativeConstants.SIZEOF_SHORT; } + static int intSize() { return NativeConstants.SIZEOF_INT; } + static int longSize() {return NativeConstants.SIZEOF_LONG; } + static int wcharSize() {return NativeConstants.SIZEOF_WCHAR; } + static short structTag() { return NativeConstants.STRUCT_TAG; } private static final MethodType UPCALL_TARGET_TYPE = MethodType.methodType(void.class, MemorySegment.class, MemorySegment.class); /** @@ -220,10 +226,14 @@ private static native long ffi_type_uint64(); private static native long ffi_type_sint64(); private static native long ffi_type_float(); private static native long ffi_type_double(); private static native long ffi_type_pointer(); + private static native int ffi_sizeof_short(); + private static native int ffi_sizeof_int(); + private static native int ffi_sizeof_long(); + private static native int ffi_sizeof_wchar(); // put these in a separate class to avoid an UnsatisfiedLinkError // when LibFallback is initialized but the library is not present private static final class NativeConstants { private NativeConstants() {} @@ -237,10 +247,15 @@ static final MemorySegment SINT32_TYPE = MemorySegment.ofAddress(ffi_type_sint32()); static final MemorySegment SINT64_TYPE = MemorySegment.ofAddress(ffi_type_sint64()); static final MemorySegment FLOAT_TYPE = MemorySegment.ofAddress(ffi_type_float()); static final MemorySegment DOUBLE_TYPE = MemorySegment.ofAddress(ffi_type_double()); static final MemorySegment POINTER_TYPE = MemorySegment.ofAddress(ffi_type_pointer()); + static final int SIZEOF_SHORT = ffi_sizeof_short(); + static final int SIZEOF_INT = ffi_sizeof_int(); + static final int SIZEOF_LONG = ffi_sizeof_long(); + static final int SIZEOF_WCHAR = ffi_sizeof_wchar(); + static final MemorySegment VOID_TYPE = MemorySegment.ofAddress(ffi_type_void()); static final short STRUCT_TAG = ffi_type_struct(); static final long SIZEOF_CIF = sizeofCif(); } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64Linker.java @@ -25,19 +25,26 @@ */ package jdk.internal.foreign.abi.ppc64.linux; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.ppc64.CallArranger; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; public final class LinuxPPC64Linker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static LinuxPPC64Linker getInstance() { final class Holder { private static final LinuxPPC64Linker INSTANCE = new LinuxPPC64Linker(); } @@ -60,6 +67,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.BIG_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/ppc64/linux/LinuxPPC64leLinker.java @@ -25,19 +25,26 @@ */ package jdk.internal.foreign.abi.ppc64.linux; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.ppc64.CallArranger; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; public final class LinuxPPC64leLinker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static LinuxPPC64leLinker getInstance() { final class Holder { private static final LinuxPPC64leLinker INSTANCE = new LinuxPPC64leLinker(); } @@ -60,6 +67,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/RISCV64Architecture.java @@ -30,11 +30,10 @@ import jdk.internal.foreign.abi.ABIDescriptor; import jdk.internal.foreign.abi.Architecture; import jdk.internal.foreign.abi.StubLocations; import jdk.internal.foreign.abi.VMStorage; -import jdk.internal.foreign.abi.riscv64.linux.TypeClass; public final class RISCV64Architecture implements Architecture { public static final Architecture INSTANCE = new RISCV64Architecture(); private static final short REG64_MASK = 0b0000_0000_0000_0001; diff a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64CallArranger.java @@ -38,16 +38,14 @@ import jdk.internal.foreign.abi.Binding; import jdk.internal.foreign.abi.CallingSequence; import jdk.internal.foreign.abi.CallingSequenceBuilder; import jdk.internal.foreign.abi.DowncallLinker; import jdk.internal.foreign.abi.LinkerOptions; -import jdk.internal.foreign.abi.UpcallLinker; import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.VMStorage; import jdk.internal.foreign.Utils; -import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.util.List; import java.util.Map; import java.util.Optional; @@ -148,11 +146,11 @@ public StorageCalculator(boolean forArguments) { this.forArguments = forArguments; } - // Aggregates or scalars passed on the stack are aligned to the greater of + // Aggregates or scalars passed on the stack are aligned to the greatest of // the type alignment and XLEN bits, but never more than the stack alignment. void alignStack(long alignment) { alignment = Utils.alignUp(Math.clamp(alignment, STACK_SLOT_SIZE, 16), STACK_SLOT_SIZE); stackOffset = Utils.alignUp(stackOffset, alignment); } @@ -251,12 +249,12 @@ Map.ofEntries(Map.entry(FLOAT, INTEGER), Map.entry(STRUCT_REGISTER_F, STRUCT_REGISTER_X), Map.entry(STRUCT_REGISTER_XF, STRUCT_REGISTER_X)); } - static class UnboxBindingCalculator extends BindingCalculator { - boolean forArguments; + static final class UnboxBindingCalculator extends BindingCalculator { + final boolean forArguments; UnboxBindingCalculator(boolean forArguments) { super(forArguments); this.forArguments = forArguments; } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java @@ -28,18 +28,25 @@ package jdk.internal.foreign.abi.riscv64.linux; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; public final class LinuxRISCV64Linker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static LinuxRISCV64Linker getInstance() { final class Holder { private static final LinuxRISCV64Linker INSTANCE = new LinuxRISCV64Linker(); } @@ -62,6 +69,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/TypeClass.java @@ -76,11 +76,11 @@ /* * Struct will be flattened while classifying. That is, struct{struct{int, double}} will be treated * same as struct{int, double} and struct{int[2]} will be treated same as struct{int, int}. * */ - private static record FieldCounter(long integerCnt, long floatCnt, long pointerCnt) { + private record FieldCounter(long integerCnt, long floatCnt, long pointerCnt) { static final FieldCounter EMPTY = new FieldCounter(0, 0, 0); static final FieldCounter SINGLE_INTEGER = new FieldCounter(1, 0, 0); static final FieldCounter SINGLE_FLOAT = new FieldCounter(0, 1, 0); static final FieldCounter SINGLE_POINTER = new FieldCounter(0, 0, 1); @@ -126,43 +126,44 @@ floatCnt + other.floatCnt, pointerCnt + other.pointerCnt); } } - public static record FlattenedFieldDesc(TypeClass typeClass, long offset, ValueLayout layout) { - - } + public record FlattenedFieldDesc(TypeClass typeClass, long offset, ValueLayout layout) { } private static List getFlattenedFieldsInner(long offset, MemoryLayout layout) { - if (layout instanceof ValueLayout valueLayout) { - TypeClass typeClass = classifyValueType(valueLayout); - return List.of(switch (typeClass) { - case INTEGER, FLOAT -> new FlattenedFieldDesc(typeClass, offset, valueLayout); - default -> throw new IllegalStateException("Should not reach here."); - }); - } else if (layout instanceof GroupLayout groupLayout) { - List fields = new ArrayList<>(); - for (MemoryLayout memberLayout : groupLayout.memberLayouts()) { - if (memberLayout instanceof PaddingLayout) { + return switch (layout) { + case ValueLayout valueLayout -> { + TypeClass typeClass = classifyValueType(valueLayout); + yield List.of(switch (typeClass) { + case INTEGER, FLOAT -> new FlattenedFieldDesc(typeClass, offset, valueLayout); + default -> throw new IllegalStateException("Should not reach here."); + }); + } + case GroupLayout groupLayout -> { + List fields = new ArrayList<>(); + for (MemoryLayout memberLayout : groupLayout.memberLayouts()) { + if (memberLayout instanceof PaddingLayout) { + offset += memberLayout.byteSize(); + continue; + } + fields.addAll(getFlattenedFieldsInner(offset, memberLayout)); offset += memberLayout.byteSize(); - continue; } - fields.addAll(getFlattenedFieldsInner(offset, memberLayout)); - offset += memberLayout.byteSize(); + yield fields; } - return fields; - } else if (layout instanceof SequenceLayout sequenceLayout) { - List fields = new ArrayList<>(); - MemoryLayout elementLayout = sequenceLayout.elementLayout(); - for (long i = 0; i < sequenceLayout.elementCount(); i++) { - fields.addAll(getFlattenedFieldsInner(offset, elementLayout)); - offset += elementLayout.byteSize(); + case SequenceLayout sequenceLayout -> { + List fields = new ArrayList<>(); + MemoryLayout elementLayout = sequenceLayout.elementLayout(); + for (long i = 0; i < sequenceLayout.elementCount(); i++) { + fields.addAll(getFlattenedFieldsInner(offset, elementLayout)); + offset += elementLayout.byteSize(); + } + yield fields; } - return fields; - } else { - throw new IllegalStateException("Cannot get here: " + layout); - } + case null, default -> throw new IllegalStateException("Cannot get here: " + layout); + }; } public static List getFlattenedFields(GroupLayout layout) { return getFlattenedFieldsInner(0, layout); } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390CallArranger.java @@ -35,23 +35,19 @@ import jdk.internal.foreign.abi.Binding; import jdk.internal.foreign.abi.CallingSequence; import jdk.internal.foreign.abi.CallingSequenceBuilder; import jdk.internal.foreign.abi.DowncallLinker; import jdk.internal.foreign.abi.LinkerOptions; -import jdk.internal.foreign.abi.UpcallLinker; import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.VMStorage; import jdk.internal.foreign.Utils; -import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.util.List; -import java.util.Map; import java.util.Optional; -import static jdk.internal.foreign.abi.s390.linux.TypeClass.*; import static jdk.internal.foreign.abi.s390.S390Architecture.*; import static jdk.internal.foreign.abi.s390.S390Architecture.Regs.*; /** * For the S390 C ABI specifically, this class uses CallingSequenceBuilder diff a/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/LinuxS390Linker.java @@ -25,18 +25,25 @@ */ package jdk.internal.foreign.abi.s390.linux; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; public final class LinuxS390Linker extends AbstractLinker { + private static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static LinuxS390Linker getInstance() { final class Holder { private static final LinuxS390Linker INSTANCE = new LinuxS390Linker(); } @@ -59,6 +66,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.BIG_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/TypeClass.java b/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/TypeClass.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/TypeClass.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/s390/linux/TypeClass.java @@ -94,14 +94,11 @@ if (!(baseType instanceof ValueLayout)) return false; TypeClass baseArgClass = classifyValueType((ValueLayout) baseType); - if (baseArgClass != FLOAT) - return false; - - return true; + return baseArgClass == FLOAT; } private static TypeClass classifyStructType(MemoryLayout layout) { if (!isRegisterAggregate(layout)) { diff a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/CallArranger.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/CallArranger.java @@ -32,11 +32,10 @@ import jdk.internal.foreign.abi.CallingSequence; import jdk.internal.foreign.abi.CallingSequenceBuilder; import jdk.internal.foreign.abi.DowncallLinker; import jdk.internal.foreign.abi.LinkerOptions; import jdk.internal.foreign.abi.SharedUtils; -import jdk.internal.foreign.abi.UpcallLinker; import jdk.internal.foreign.abi.VMStorage; import jdk.internal.foreign.abi.x64.X86_64Architecture; import java.lang.foreign.AddressLayout; import java.lang.foreign.FunctionDescriptor; @@ -206,11 +205,11 @@ if (this.nVectorReg + nVectorReg > MAX_VECTOR_ARGUMENT_REGISTERS) { //not enough registers - pass on stack return typeClass.classes.stream().map(c -> stackAlloc()).toArray(VMStorage[]::new); } - //ok, let's pass pass on registers + //ok, let's pass on registers VMStorage[] storage = new VMStorage[(int)(nIntegerReg + nVectorReg)]; for (int i = 0 ; i < typeClass.classes.size() ; i++) { boolean sse = typeClass.classes.get(i) == ArgumentClassImpl.SSE; storage[i] = nextStorage(sse ? StorageType.VECTOR : StorageType.INTEGER); } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java @@ -25,21 +25,28 @@ package jdk.internal.foreign.abi.x64.sysv; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; /** * ABI implementation based on System V ABI AMD64 supplement v.0.99.6 */ public final class SysVx64Linker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + public static SysVx64Linker getInstance() { final class Holder { private static final SysVx64Linker INSTANCE = new SysVx64Linker(); } @@ -62,6 +69,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/TypeClass.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/TypeClass.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/TypeClass.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/TypeClass.java @@ -195,48 +195,53 @@ // alignUp can overflow the value, but it's okay since toIntExact still catches it nEightbytes = Math.toIntExact(Utils.alignUp(group.byteSize(), 8) / 8); } catch (ArithmeticException e) { throw new IllegalArgumentException("GroupLayout is too large: " + group, e); } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({"unchecked", "rawtypes"}) // rawtypes warnings need to be suppressed List[] groups = new List[nEightbytes]; for (MemoryLayout l : group.memberLayouts()) { groupByEightBytes(l, offset, groups); if (group instanceof StructLayout) { offset += l.byteSize(); } } return groups; } - private static void groupByEightBytes(MemoryLayout l, long offset, List[] groups) { - if (l instanceof GroupLayout group) { - for (MemoryLayout m : group.memberLayouts()) { - groupByEightBytes(m, offset, groups); - if (group instanceof StructLayout) { - offset += m.byteSize(); + private static void groupByEightBytes(MemoryLayout layout, + long offset, + List[] groups) { + switch (layout) { + case GroupLayout group -> { + for (MemoryLayout m : group.memberLayouts()) { + groupByEightBytes(m, offset, groups); + if (group instanceof StructLayout) { + offset += m.byteSize(); + } } } - } else if (l instanceof PaddingLayout) { - return; - } else if (l instanceof SequenceLayout seq) { - MemoryLayout elem = seq.elementLayout(); - for (long i = 0 ; i < seq.elementCount() ; i++) { - groupByEightBytes(elem, offset, groups); - offset += elem.byteSize(); + case PaddingLayout __ -> { } - } else if (l instanceof ValueLayout vl) { - List layouts = groups[(int)offset / 8]; - if (layouts == null) { - layouts = new ArrayList<>(); - groups[(int)offset / 8] = layouts; + case SequenceLayout seq -> { + MemoryLayout elem = seq.elementLayout(); + for (long i = 0; i < seq.elementCount(); i++) { + groupByEightBytes(elem, offset, groups); + offset += elem.byteSize(); + } } - // if the aggregate contains unaligned fields, it has class MEMORY - ArgumentClassImpl argumentClass = (offset % vl.byteAlignment()) == 0 ? - argumentClassFor(vl) : - ArgumentClassImpl.MEMORY; - layouts.add(argumentClass); - } else { - throw new IllegalStateException("Unexpected layout: " + l); + case ValueLayout vl -> { + List layouts = groups[(int) offset / 8]; + if (layouts == null) { + layouts = new ArrayList<>(); + groups[(int) offset / 8] = layouts; + } + // if the aggregate contains unaligned fields, it has class MEMORY + ArgumentClassImpl argumentClass = (offset % vl.byteAlignment()) == 0 ? + argumentClassFor(vl) : + ArgumentClassImpl.MEMORY; + layouts.add(argumentClass); + } + case null, default -> throw new IllegalStateException("Unexpected layout: " + layout); } } } diff a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/CallArranger.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/CallArranger.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/CallArranger.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/CallArranger.java @@ -31,20 +31,18 @@ import jdk.internal.foreign.abi.CallingSequence; import jdk.internal.foreign.abi.CallingSequenceBuilder; import jdk.internal.foreign.abi.DowncallLinker; import jdk.internal.foreign.abi.LinkerOptions; import jdk.internal.foreign.abi.SharedUtils; -import jdk.internal.foreign.abi.UpcallLinker; import jdk.internal.foreign.abi.VMStorage; import jdk.internal.foreign.abi.x64.X86_64Architecture; import java.lang.foreign.AddressLayout; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.GroupLayout; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.util.List; import java.util.Optional; diff a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/TypeClass.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/TypeClass.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/TypeClass.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/TypeClass.java @@ -36,11 +36,11 @@ INTEGER, FLOAT, VARARG_FLOAT; private static TypeClass classifyValueType(ValueLayout type, boolean isVararg) { - // No 128-bit integers in the Windows C ABI. There are __m128(i|d) intrinsic types but they act just + // No 128-bit integers in the Windows C ABI. There are __m128(i|d) intrinsic types but, they act just // like a struct when passing as an argument (passed by pointer). // https://docs.microsoft.com/en-us/cpp/cpp/m128?view=vs-2019 // x87 is ignored on Windows: // "The x87 register stack is unused, and may be used by the callee, diff a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java @@ -24,21 +24,28 @@ */ package jdk.internal.foreign.abi.x64.windows; import jdk.internal.foreign.abi.AbstractLinker; import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.SharedUtils; import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.nio.ByteOrder; +import java.util.Map; /** * ABI implementation based on Windows ABI AMD64 supplement v.0.99.6 */ public final class Windowsx64Linker extends AbstractLinker { + static final Map CANONICAL_LAYOUTS = + SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR); + public static Windowsx64Linker getInstance() { final class Holder { private static final Windowsx64Linker INSTANCE = new Windowsx64Linker(); } @@ -61,6 +68,11 @@ @Override protected ByteOrder linkerByteOrder() { return ByteOrder.LITTLE_ENDIAN; } + + @Override + public Map canonicalLayouts() { + return CANONICAL_LAYOUTS; + } } diff a/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java b/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java --- a/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java +++ b/src/java.base/share/classes/jdk/internal/foreign/layout/AbstractLayout.java @@ -51,12 +51,13 @@ public final L withName(String name) { return dup(byteAlignment(), Optional.of(name)); } + @SuppressWarnings("unchecked") public final L withoutName() { - return dup(byteAlignment(), Optional.empty()); + return name.isPresent() ? dup(byteAlignment(), Optional.empty()) : (L) this; } public final Optional name() { return name; } diff a/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java b/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java --- a/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java +++ b/src/java.base/share/classes/jdk/internal/foreign/layout/ValueLayouts.java @@ -29,20 +29,17 @@ import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import sun.invoke.util.Wrapper; +import java.lang.foreign.AddressLayout; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; -import java.lang.foreign.AddressLayout; import java.lang.foreign.ValueLayout; import java.lang.invoke.VarHandle; import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import java.util.Optional; /** * A value layout. A value layout is used to model the memory layout associated with values of basic data types, such as integral types @@ -113,28 +110,10 @@ super.equals(other) && carrier.equals(otherValue.carrier) && order.equals(otherValue.order); } - public final VarHandle arrayElementVarHandle(int... shape) { - Objects.requireNonNull(shape); - if (!Utils.isElementAligned((ValueLayout) this)) { - throw new UnsupportedOperationException("Layout alignment greater than its size"); - } - MemoryLayout layout = self(); - List path = new ArrayList<>(); - for (int i = shape.length; i > 0; i--) { - int size = shape[i - 1]; - if (size < 0) throw new IllegalArgumentException("Invalid shape size: " + size); - layout = MemoryLayout.sequenceLayout(size, layout); - path.add(MemoryLayout.PathElement.sequenceElement()); - } - layout = MemoryLayout.sequenceLayout(layout); - path.add(MemoryLayout.PathElement.sequenceElement()); - return layout.varHandle(path.toArray(new MemoryLayout.PathElement[0])); - } - /** * {@return the carrier associated with this value layout} */ public final Class carrier() { return carrier; @@ -175,11 +154,11 @@ || carrier == double.class || carrier == MemorySegment.class; } @ForceInline - public final VarHandle accessHandle() { + public final VarHandle varHandle() { if (handle == null) { // this store to stable field is safe, because return value of 'makeMemoryAccessVarHandle' has stable identity handle = Utils.makeSegmentViewVarHandle(self()); } return handle; @@ -391,11 +370,11 @@ *

  • {@link ValueLayout.OfChar}, for {@code char.class}
  • *
  • {@link ValueLayout.OfInt}, for {@code int.class}
  • *
  • {@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. * @return a value layout with the given Java carrier and byte-order. * @throws IllegalArgumentException if the carrier type is not supported. diff a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -62,14 +62,14 @@ /** * Enum of preview features in the current release. * Values should be annotated with the feature's {@code JEP}. */ public enum Feature { - // not used + // not used, but required for interim javac to not warn. VIRTUAL_THREADS, - @JEP(number=442, title="Foreign Function & Memory API", status="Third Preview") FOREIGN, + @JEP(number=430, title="String Templates") STRING_TEMPLATES, @JEP(number=443, title="Unnamed Patterns and Variables") UNNAMED, @JEP(number=445, title="Unnamed Classes and Instance Main Methods") diff a/src/java.base/share/classes/jdk/internal/misc/VM.java b/src/java.base/share/classes/jdk/internal/misc/VM.java --- a/src/java.base/share/classes/jdk/internal/misc/VM.java +++ b/src/java.base/share/classes/jdk/internal/misc/VM.java @@ -146,10 +146,11 @@ } // User-controllable flag that determines if direct buffers should be page // aligned. The "-XX:+PageAlignDirectMemory" option can be used to force // buffers, allocated by ByteBuffer.allocateDirect, to be page aligned. + @Stable private static boolean pageAlignDirectMemory; // Returns {@code true} if the direct buffers should be page aligned. This // variable is initialized by saveAndRemoveProperties. public static boolean isDirectMemoryPageAligned() { diff a/src/java.base/share/classes/jdk/internal/module/Modules.java b/src/java.base/share/classes/jdk/internal/module/Modules.java --- a/src/java.base/share/classes/jdk/internal/module/Modules.java +++ b/src/java.base/share/classes/jdk/internal/module/Modules.java @@ -132,10 +132,17 @@ */ public static void addOpensToAllUnnamed(Module m, String pn) { JLA.addOpensToAllUnnamed(m, pn); } + /** + * Adds native access to all unnamed modules. + */ + public static void addEnableNativeAccessToAllUnnamed() { + JLA.addEnableNativeAccessToAllUnnamed(); + } + /** * Updates module m to use a service. * Same as m2.addUses(service) but without a caller check. */ public static void addUses(Module m, Class service) { diff a/src/java.base/share/classes/jdk/internal/reflect/Reflection.java b/src/java.base/share/classes/jdk/internal/reflect/Reflection.java --- a/src/java.base/share/classes/jdk/internal/reflect/Reflection.java +++ b/src/java.base/share/classes/jdk/internal/reflect/Reflection.java @@ -29,10 +29,11 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Set; +import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.VM; import jdk.internal.module.ModuleBootstrap; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; @@ -113,11 +114,14 @@ public static void ensureNativeAccess(Class currentClass, Class owner, String methodName) { // if there is no caller class, act as if the call came from unnamed module of system class loader Module module = currentClass != null ? currentClass.getModule() : ClassLoader.getSystemClassLoader().getUnnamedModule(); - SharedSecrets.getJavaLangAccess().ensureNativeAccess(module, owner, methodName); + class Holder { + static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); + } + Holder.JLA.ensureNativeAccess(module, owner, methodName, currentClass); } /** * Verify access to a member and return {@code true} if it is granted. * diff a/src/java.base/share/classes/sun/launcher/LauncherHelper.java b/src/java.base/share/classes/sun/launcher/LauncherHelper.java --- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java +++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java @@ -100,10 +100,11 @@ "sun.launcher.LauncherHelper$FXHelper"; private static final String LAUNCHER_AGENT_CLASS = "Launcher-Agent-Class"; private static final String MAIN_CLASS = "Main-Class"; private static final String ADD_EXPORTS = "Add-Exports"; private static final String ADD_OPENS = "Add-Opens"; + private static final String ENABLE_NATIVE_ACCESS = "Enable-Native-Access"; private static StringBuilder outBuf = new StringBuilder(); private static final String INDENT = " "; private static final String VM_SETTINGS = "VM settings:"; @@ -630,10 +631,17 @@ } String opens = mainAttrs.getValue(ADD_OPENS); if (opens != null) { addExportsOrOpens(opens, true); } + String enableNativeAccess = mainAttrs.getValue(ENABLE_NATIVE_ACCESS); + if (enableNativeAccess != null) { + if (!enableNativeAccess.equals("ALL-UNNAMED")) { + throw new IllegalArgumentException("Only ALL-UNNAMED allowed as value for " + ENABLE_NATIVE_ACCESS); + } + Modules.addEnableNativeAccessToAllUnnamed(); + } /* * Hand off to FXHelper if it detects a JavaFX application * This must be done after ensuring a Main-Class entry * exists to enforce compliance with the jar specification diff a/src/java.base/share/native/libfallbackLinker/fallbackLinker.c b/src/java.base/share/native/libfallbackLinker/fallbackLinker.c --- a/src/java.base/share/native/libfallbackLinker/fallbackLinker.c +++ b/src/java.base/share/native/libfallbackLinker/fallbackLinker.c @@ -27,10 +27,11 @@ #include #include #include +#include #ifdef _WIN64 #include #include #endif @@ -204,5 +205,25 @@ } JNIEXPORT jlong JNICALL Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1type_1pointer(JNIEnv* env, jclass cls) { return ptr_to_jlong(&ffi_type_pointer); } + +JNIEXPORT jint JNICALL +Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1sizeof_1short(JNIEnv* env, jclass cls) { + return sizeof(short); +} + +JNIEXPORT jint JNICALL +Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1sizeof_1int(JNIEnv* env, jclass cls) { + return sizeof(int); +} + +JNIEXPORT jint JNICALL +Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1sizeof_1long(JNIEnv* env, jclass cls) { + return sizeof(long); +} + +JNIEXPORT jint JNICALL +Java_jdk_internal_foreign_abi_fallback_LibFallback_ffi_1sizeof_1wchar(JNIEnv* env, jclass cls) { + return sizeof(wchar_t); +} diff a/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java b/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java --- a/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java @@ -26,12 +26,12 @@ * @bug 8289996 * @summary Test range check hoisting for some scaled iv at array index * @library /test/lib / * @requires vm.debug & vm.compiler2.enabled & (os.simpleArch == "x64" | os.arch == "aarch64") * @modules jdk.incubator.vector - * @compile --enable-preview -source ${jdk.version} TestRangeCheckHoistingScaledIV.java - * @run main/othervm --enable-preview compiler.rangechecks.TestRangeCheckHoistingScaledIV + * @compile -source ${jdk.version} TestRangeCheckHoistingScaledIV.java + * @run main/othervm compiler.rangechecks.TestRangeCheckHoistingScaledIV */ package compiler.rangechecks; import java.lang.foreign.MemorySegment; @@ -81,11 +81,11 @@ } } public static void main(String[] args) throws Exception { ProcessBuilder pb = ProcessTools.createTestJvm( - "--enable-preview", "--add-modules", "jdk.incubator.vector", + "--add-modules", "jdk.incubator.vector", "-Xbatch", "-XX:+TraceLoopPredicate", Launcher.class.getName()); OutputAnalyzer analyzer = new OutputAnalyzer(pb.start()); analyzer.shouldHaveExitValue(0); analyzer.outputTo(System.out); diff a/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java b/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java --- a/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestIntrinsicBailOut.java @@ -27,11 +27,10 @@ import jdk.incubator.vector.*; import java.nio.ByteOrder; /* * @test - * @enablePreview * @bug 8262998 * @summary Vector API intrinsincs should not modify IR when bailing out * @modules jdk.incubator.vector * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseAVX=1 * -XX:-TieredCompilation compiler.vectorapi.TestIntrinsicBailOut diff a/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorErgonomics.java @@ -36,46 +36,46 @@ public class TestVectorErgonomics { public static void main(String[] args) throws Throwable { ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:+EnableVectorReboxing", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:+EnableVectorReboxing", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorReboxing=true"); ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:+EnableVectorAggressiveReboxing", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:+EnableVectorAggressiveReboxing", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorAggressiveReboxing=true"); ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:-EnableVectorReboxing", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:-EnableVectorReboxing", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorReboxing=false") .shouldContain("EnableVectorAggressiveReboxing=false"); ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:-EnableVectorAggressiveReboxing", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:-EnableVectorAggressiveReboxing", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorAggressiveReboxing=false"); ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:-EnableVectorSupport", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:-EnableVectorSupport", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorSupport=false") .shouldContain("EnableVectorReboxing=false") .shouldContain("EnableVectorAggressiveReboxing=false"); ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:-EnableVectorSupport", "-XX:+EnableVectorReboxing", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:-EnableVectorSupport", "-XX:+EnableVectorReboxing", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorSupport=false") .shouldContain("EnableVectorReboxing=false") .shouldContain("EnableVectorAggressiveReboxing=false"); ProcessTools.executeTestJvm("--add-modules=jdk.incubator.vector", "-XX:+UnlockExperimentalVMOptions", - "-XX:-EnableVectorSupport", "-XX:+EnableVectorAggressiveReboxing", "-Xlog:compilation", "-version", "--enable-preview") + "-XX:-EnableVectorSupport", "-XX:+EnableVectorAggressiveReboxing", "-Xlog:compilation", "-version") .shouldHaveExitValue(0) .shouldContain("EnableVectorSupport=false") .shouldContain("EnableVectorReboxing=false") .shouldContain("EnableVectorAggressiveReboxing=false"); } diff a/test/hotspot/jtreg/compiler/vectorapi/VectorMemoryAlias.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMemoryAlias.java --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMemoryAlias.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMemoryAlias.java @@ -24,11 +24,10 @@ * */ /* * @test - * @enablePreview * @summary Test if memory ordering is preserved * * @run main/othervm -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:+AbortVMOnCompilationFailure * -XX:CompileThreshold=100 -XX:CompileCommand=dontinline,compiler.vectorapi.VectorMemoryAlias::test * compiler.vectorapi.VectorMemoryAlias diff a/test/hotspot/jtreg/compiler/vectorapi/VectorRebracket128Test.java b/test/hotspot/jtreg/compiler/vectorapi/VectorRebracket128Test.java --- a/test/hotspot/jtreg/compiler/vectorapi/VectorRebracket128Test.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorRebracket128Test.java @@ -35,22 +35,20 @@ import jdk.internal.vm.annotation.ForceInline; /* * @test id=ZSinglegen * @bug 8260473 - * @enablePreview * @requires vm.gc.ZSinglegen * @modules jdk.incubator.vector * @modules java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:CompileCommand=compileonly,jdk/incubator/vector/ByteVector.fromMemorySegment * -XX:-TieredCompilation -XX:CICompilerCount=1 -XX:+UseZGC -XX:-ZGenerational -Xbatch -Xmx256m VectorRebracket128Test */ /* * @test id=ZGenerational * @bug 8260473 - * @enablePreview * @requires vm.gc.ZGenerational * @modules jdk.incubator.vector * @modules java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:CompileCommand=compileonly,jdk/incubator/vector/ByteVector.fromMemorySegment * -XX:-TieredCompilation -XX:CICompilerCount=1 -XX:+UseZGC -XX:+ZGenerational -Xbatch -Xmx256m VectorRebracket128Test diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX1.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX1.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX1.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX1.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8259610 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on avx1. * @requires vm.cpu.features ~= ".*avx.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX2.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX2.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX2.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX2.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8259610 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on avx2. * @requires vm.cpu.features ~= ".*avx2.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8259610 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on avx512. * @requires vm.cpu.features ~= ".*avx512.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512BW.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512BW.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512BW.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512BW.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8278623 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on avx512bw. * @requires vm.cpu.features ~= ".*avx512bw.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512DQ.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512DQ.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512DQ.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastAVX512DQ.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8259610 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on avx512dq. * @requires vm.cpu.features ~= ".*avx512dq.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastNeon.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastNeon.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastNeon.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastNeon.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8259610 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on neon. * @requires vm.cpu.features ~= ".*asimd.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastSVE.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastSVE.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastSVE.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorCastSVE.java @@ -28,11 +28,10 @@ import compiler.vectorapi.reshape.utils.VectorReshapeHelper; /* * @test * @bug 8259610 - * @enablePreview * @key randomness * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector cast intrinsics work as intended on sve. * @requires vm.cpu.features ~= ".*sve.*" diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorReinterpret.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorReinterpret.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorReinterpret.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/TestVectorReinterpret.java @@ -34,11 +34,10 @@ /* * @test * @bug 8259610 * @key randomness - * @enablePreview * @modules jdk.incubator.vector * @modules java.base/jdk.internal.misc * @summary Test that vector reinterpret intrinsics work as intended. * @library /test/lib / * @run main compiler.vectorapi.reshape.TestVectorReinterpret diff a/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/VectorReshapeHelper.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/VectorReshapeHelper.java --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/VectorReshapeHelper.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/VectorReshapeHelper.java @@ -75,11 +75,11 @@ public static void runMainHelper(Class testClass, Stream testMethods, String... flags) { var test = new TestFramework(testClass); test.setDefaultWarmup(1); test.addHelperClasses(VectorReshapeHelper.class); - test.addFlags("--add-modules=jdk.incubator.vector", "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED", "--enable-preview"); + test.addFlags("--add-modules=jdk.incubator.vector", "--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED"); test.addFlags(flags); String testMethodNames = testMethods .filter(p -> p.isp().length() <= VectorSpecies.ofLargestShape(p.isp().elementType()).length()) .filter(p -> p.osp().length() <= VectorSpecies.ofLargestShape(p.osp().elementType()).length()) .map(VectorSpeciesPair::format) diff a/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java b/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java --- a/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java +++ b/test/hotspot/jtreg/gc/shenandoah/compiler/TestLinkToNativeRBP.java @@ -21,11 +21,10 @@ * questions. */ /** * @test - * @enablePreview * @bug 8259937 * @summary guarantee(loc != NULL) failed: missing saved register with native invoke * * @requires vm.flavor == "server" * @requires ((os.arch == "amd64" | os.arch == "x86_64") & sun.arch.data.model == "64") | os.arch == "aarch64" | os.arch == "ppc64le" diff a/test/hotspot/jtreg/runtime/ClassFile/ClassFileVersionTest.java b/test/hotspot/jtreg/runtime/ClassFile/ClassFileVersionTest.java --- a/test/hotspot/jtreg/runtime/ClassFile/ClassFileVersionTest.java +++ b/test/hotspot/jtreg/runtime/ClassFile/ClassFileVersionTest.java @@ -40,11 +40,11 @@ * Include a use of a preview API so that the minor class file * version of the class file for this class gets set during * compilation. If a particular class becomes non-preview, any * currently preview class can be substituted in. */ - private static final Class PREVIEW_API = java.lang.foreign.MemorySegment.class; + private static final Class PREVIEW_API = java.lang.ScopedValue.class; static Method m; public static void testIt(String className, int expectedResult) throws Exception { testIt(Class.forName(className), expectedResult); } diff a/test/jdk/java/foreign/CompositeLookupTest.java b/test/jdk/java/foreign/CompositeLookupTest.java --- a/test/jdk/java/foreign/CompositeLookupTest.java +++ b/test/jdk/java/foreign/CompositeLookupTest.java @@ -32,11 +32,10 @@ import static org.testng.Assert.*; /* * @test - * @enablePreview * @run testng CompositeLookupTest */ public class CompositeLookupTest { @Test(dataProvider = "testCases") diff a/test/jdk/java/foreign/LibraryLookupTest.java b/test/jdk/java/foreign/LibraryLookupTest.java --- a/test/jdk/java/foreign/LibraryLookupTest.java +++ b/test/jdk/java/foreign/LibraryLookupTest.java @@ -33,12 +33,10 @@ import static org.testng.Assert.*; /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED LibraryLookupTest */ public class LibraryLookupTest { static final Path JAVA_LIBRARY_PATH = Path.of(System.getProperty("java.library.path")); diff a/test/jdk/java/foreign/MemoryLayoutPrincipalTotalityTest.java b/test/jdk/java/foreign/MemoryLayoutPrincipalTotalityTest.java --- a/test/jdk/java/foreign/MemoryLayoutPrincipalTotalityTest.java +++ b/test/jdk/java/foreign/MemoryLayoutPrincipalTotalityTest.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng/othervm MemoryLayoutPrincipalTotalityTest */ import org.testng.annotations.*; diff a/test/jdk/java/foreign/MemoryLayoutTypeRetentionTest.java b/test/jdk/java/foreign/MemoryLayoutTypeRetentionTest.java --- a/test/jdk/java/foreign/MemoryLayoutTypeRetentionTest.java +++ b/test/jdk/java/foreign/MemoryLayoutTypeRetentionTest.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng/othervm MemoryLayoutTypeRetentionTest */ import org.testng.annotations.*; diff a/test/jdk/java/foreign/NativeTestHelper.java b/test/jdk/java/foreign/NativeTestHelper.java --- a/test/jdk/java/foreign/NativeTestHelper.java +++ b/test/jdk/java/foreign/NativeTestHelper.java @@ -84,48 +84,67 @@ public static boolean isPointer(MemoryLayout layout) { return layout instanceof ValueLayout valueLayout && valueLayout.carrier() == MemorySegment.class; } + public static final Linker LINKER = Linker.nativeLinker(); + // the constants below are useful aliases for C types. The type/carrier association is only valid for 64-bit platforms. /** * The layout for the {@code bool} C type */ - public static final ValueLayout.OfBoolean C_BOOL = ValueLayout.JAVA_BOOLEAN; + public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) LINKER.canonicalLayouts().get("bool"); /** * The layout for the {@code char} C type */ - public static final ValueLayout.OfByte C_CHAR = ValueLayout.JAVA_BYTE; + public static final ValueLayout.OfByte C_CHAR = (ValueLayout.OfByte) LINKER.canonicalLayouts().get("char"); /** * The layout for the {@code short} C type */ - public static final ValueLayout.OfShort C_SHORT = ValueLayout.JAVA_SHORT; + public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) LINKER.canonicalLayouts().get("short"); /** * The layout for the {@code int} C type */ - public static final ValueLayout.OfInt C_INT = ValueLayout.JAVA_INT; + public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int"); /** * The layout for the {@code long long} C type. */ - public static final ValueLayout.OfLong C_LONG_LONG = ValueLayout.JAVA_LONG; + public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long long"); /** * The layout for the {@code float} C type */ - public static final ValueLayout.OfFloat C_FLOAT = ValueLayout.JAVA_FLOAT; + public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) LINKER.canonicalLayouts().get("float"); /** * The layout for the {@code double} C type */ - public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE; + public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) LINKER.canonicalLayouts().get("double"); /** * The {@code T*} native type. */ - public static final AddressLayout C_POINTER = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(C_CHAR)); - - public static final Linker LINKER = Linker.nativeLinker(); + public static final AddressLayout C_POINTER = ((AddressLayout) LINKER.canonicalLayouts().get("void*")) + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, C_CHAR)); + /** + * The layout for the {@code size_t} C type + */ + public static final ValueLayout C_SIZE_T = (ValueLayout) LINKER.canonicalLayouts().get("size_t"); + + // Common layout shared by some tests + // struct S_PDI { void* p0; double p1; int p2; }; + public static final MemoryLayout S_PDI_LAYOUT = switch ((int) ValueLayout.ADDRESS.byteSize()) { + case 8 -> MemoryLayout.structLayout( + C_POINTER.withName("p0"), + C_DOUBLE.withName("p1"), + C_INT.withName("p2"), + MemoryLayout.paddingLayout(4)); + case 4 -> MemoryLayout.structLayout( + C_POINTER.withName("p0"), + C_DOUBLE.withName("p1"), + C_INT.withName("p2")); + default -> throw new UnsupportedOperationException("Unsupported address size"); + }; private static final MethodHandle FREE = LINKER.downcallHandle( LINKER.defaultLookup().find("free").get(), FunctionDescriptor.ofVoid(C_POINTER)); private static final MethodHandle MALLOC = LINKER.downcallHandle( @@ -246,20 +265,20 @@ slice.copyFrom((MemorySegment) fieldValue.value()); return actual -> fieldCheck.accept(slicer.apply((MemorySegment) actual)); } else { VarHandle accessor = containerLayout.varHandle(fieldPath); //set value - accessor.set(container, fieldValue.value()); - return actual -> fieldCheck.accept(accessor.get((MemorySegment) actual)); + accessor.set(container, 0L, fieldValue.value()); + return actual -> fieldCheck.accept(accessor.get((MemorySegment) actual, 0L)); } } private static UnaryOperator slicer(MemoryLayout containerLayout, MemoryLayout.PathElement fieldPath) { MethodHandle slicer = containerLayout.sliceHandle(fieldPath); return container -> { try { - return (MemorySegment) slicer.invokeExact(container); + return (MemorySegment) slicer.invokeExact(container, 0L); } catch (Throwable e) { throw new IllegalStateException(e); } }; } diff a/test/jdk/java/foreign/SafeFunctionAccessTest.java b/test/jdk/java/foreign/SafeFunctionAccessTest.java --- a/test/jdk/java/foreign/SafeFunctionAccessTest.java +++ b/test/jdk/java/foreign/SafeFunctionAccessTest.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED SafeFunctionAccessTest */ import java.lang.foreign.Arena; import java.lang.foreign.Linker; diff a/test/jdk/java/foreign/StdLibTest.java b/test/jdk/java/foreign/StdLibTest.java --- a/test/jdk/java/foreign/StdLibTest.java +++ b/test/jdk/java/foreign/StdLibTest.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED StdLibTest */ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -49,11 +47,10 @@ import org.testng.annotations.*; import static org.testng.Assert.*; -@Test public class StdLibTest extends NativeTestHelper { final static Linker abi = Linker.nativeLinker(); private StdLibHelper stdLibHelper = new StdLibHelper(); @@ -119,20 +116,24 @@ fail("All values are the same! " + val); } @Test(dataProvider = "printfArgs") void test_printf(List args) throws Throwable { - String formatArgs = args.stream() - .map(a -> a.format) + String javaFormatArgs = args.stream() + .map(a -> a.javaFormat) + .collect(Collectors.joining(",")); + String nativeFormatArgs = args.stream() + .map(a -> a.nativeFormat) .collect(Collectors.joining(",")); - String formatString = "hello(" + formatArgs + ")\n"; + String javaFormatString = "hello(" + javaFormatArgs + ")\n"; + String nativeFormatString = "hello(" + nativeFormatArgs + ")\n"; - String expected = String.format(formatString, args.stream() + String expected = String.format(javaFormatString, args.stream() .map(a -> a.javaValue).toArray()); - int found = stdLibHelper.printf(formatString, args); + int found = stdLibHelper.printf(nativeFormatString, args); assertEquals(found, expected.length()); } @Test void testSystemLibraryBadLookupName() { @@ -154,12 +155,13 @@ FunctionDescriptor.of(C_INT, C_POINTER)); final static MethodHandle gmtime = abi.downcallHandle(abi.defaultLookup().find("gmtime").get(), FunctionDescriptor.of(C_POINTER.withTargetLayout(Tm.LAYOUT), C_POINTER)); + // void qsort( void *ptr, size_t count, size_t size, int (*comp)(const void *, const void *) ); final static MethodHandle qsort = abi.downcallHandle(abi.defaultLookup().find("qsort").get(), - FunctionDescriptor.ofVoid(C_POINTER, C_LONG_LONG, C_LONG_LONG, C_POINTER)); + FunctionDescriptor.ofVoid(C_POINTER, C_SIZE_T, C_SIZE_T, C_POINTER)); final static FunctionDescriptor qsortComparFunction = FunctionDescriptor.of(C_INT, C_POINTER.withTargetLayout(C_INT), C_POINTER.withTargetLayout(C_INT)); final static MethodHandle qsortCompar; @@ -185,34 +187,34 @@ } String strcat(String s1, String s2) throws Throwable { try (var arena = Arena.ofConfined()) { MemorySegment buf = arena.allocate(s1.length() + s2.length() + 1); - buf.setUtf8String(0, s1); - MemorySegment other = arena.allocateUtf8String(s2); - return ((MemorySegment)strcat.invokeExact(buf, other)).getUtf8String(0); + buf.setString(0, s1); + MemorySegment other = arena.allocateFrom(s2); + return ((MemorySegment)strcat.invokeExact(buf, other)).getString(0); } } int strcmp(String s1, String s2) throws Throwable { try (var arena = Arena.ofConfined()) { - MemorySegment ns1 = arena.allocateUtf8String(s1); - MemorySegment ns2 = arena.allocateUtf8String(s2); + MemorySegment ns1 = arena.allocateFrom(s1); + MemorySegment ns2 = arena.allocateFrom(s2); return (int)strcmp.invokeExact(ns1, ns2); } } int puts(String msg) throws Throwable { try (var arena = Arena.ofConfined()) { - MemorySegment s = arena.allocateUtf8String(msg); + MemorySegment s = arena.allocateFrom(msg); return (int)puts.invokeExact(s); } } int strlen(String msg) throws Throwable { try (var arena = Arena.ofConfined()) { - MemorySegment s = arena.allocateUtf8String(msg); + MemorySegment s = arena.allocateFrom(msg); return (int)strlen.invokeExact(s); } } Tm gmtime(long arg) throws Throwable { @@ -275,16 +277,20 @@ } int[] qsort(int[] arr) throws Throwable { //init native array try (var arena = Arena.ofConfined()) { - MemorySegment nativeArr = arena.allocateArray(C_INT, arr); + MemorySegment nativeArr = arena.allocateFrom(C_INT, arr); //call qsort MemorySegment qsortUpcallStub = abi.upcallStub(qsortCompar, qsortComparFunction, arena); - qsort.invokeExact(nativeArr, (long)arr.length, C_INT.byteSize(), qsortUpcallStub); + // both of these fit in an int + // automatically widen them to long on x64 + int count = arr.length; + int size = (int) C_INT.byteSize(); + qsort.invoke(nativeArr, count, size, qsortUpcallStub); //convert back to Java array return nativeArr.toArray(C_INT); } } @@ -298,11 +304,11 @@ return (int)rand.invokeExact(); } int printf(String format, List args) throws Throwable { try (var arena = Arena.ofConfined()) { - MemorySegment formatStr = arena.allocateUtf8String(format); + MemorySegment formatStr = arena.allocateFrom(format); return (int)specializedPrintf(args).invokeExact(formatStr, args.stream().map(a -> a.nativeValue(arena)).toArray()); } } @@ -376,25 +382,28 @@ .map(l -> new Object[] { l }) .toArray(Object[][]::new); } enum PrintfArg { - INT(int.class, C_INT, "%d", arena -> 42, 42), - LONG(long.class, C_LONG_LONG, "%d", arena -> 84L, 84L), - DOUBLE(double.class, C_DOUBLE, "%.4f", arena -> 1.2345d, 1.2345d), - STRING(MemorySegment.class, C_POINTER, "%s", arena -> arena.allocateUtf8String("str"), "str"); + INT(int.class, C_INT, "%d", "%d", arena -> 42, 42), + LONG(long.class, C_LONG_LONG, "%lld", "%d", arena -> 84L, 84L), + DOUBLE(double.class, C_DOUBLE, "%.4f", "%.4f", arena -> 1.2345d, 1.2345d), + STRING(MemorySegment.class, C_POINTER, "%s", "%s", arena -> arena.allocateFrom("str"), "str"); final Class carrier; final ValueLayout layout; - final String format; + final String nativeFormat; + final String javaFormat; final Function nativeValueFactory; final Object javaValue; - PrintfArg(Class carrier, L layout, String format, Function nativeValueFactory, Object javaValue) { + PrintfArg(Class carrier, L layout, String nativeFormat, String javaFormat, + Function nativeValueFactory, Object javaValue) { this.carrier = carrier; this.layout = layout; - this.format = format; + this.nativeFormat = nativeFormat; + this.javaFormat = javaFormat; this.nativeValueFactory = nativeValueFactory; this.javaValue = javaValue; } public Object nativeValue(Arena arena) { diff a/test/jdk/java/foreign/TestAccessModes.java b/test/jdk/java/foreign/TestAccessModes.java --- /dev/null +++ b/test/jdk/java/foreign/TestAccessModes.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2019, 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. + */ + +/* + * @test + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false -Xverify:all TestAccessModes + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true -Xverify:all TestAccessModes + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false -Xverify:all TestAccessModes + * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true -Xverify:all TestAccessModes + */ + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +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.invoke.VarHandle.AccessMode; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.testng.annotations.*; + +import static org.testng.Assert.*; +public class TestAccessModes { + + @Test(dataProvider = "segmentsAndLayoutsAndModes") + public void testAccessModes(MemorySegment segment, ValueLayout layout, AccessMode mode) throws Throwable { + VarHandle varHandle = layout.varHandle(); + MethodHandle methodHandle = varHandle.toMethodHandle(mode); + boolean compatible = AccessModeKind.supportedModes(layout).contains(AccessModeKind.of(mode)); + try { + Object o = methodHandle.invokeWithArguments(makeArgs(segment, varHandle.accessModeType(mode))); + assertTrue(compatible); + } catch (UnsupportedOperationException ex) { + assertFalse(compatible); + } catch (IllegalArgumentException ex) { + // access is unaligned, but access mode is supported + assertTrue(compatible); + } + } + + Object[] makeArgs(MemorySegment segment, MethodType type) throws Throwable { + List args = new ArrayList<>(); + args.add(segment); + for (Class argType : type.dropParameterTypes(0, 1).parameterList()) { + args.add(defaultValue(argType)); + } + return args.toArray(); + } + + Object defaultValue(Class clazz) throws Throwable { + if (clazz == MemorySegment.class) { + return MemorySegment.NULL; + } else if (clazz.isPrimitive()) { + return MethodHandles.zero(clazz).invoke(); + } else { + throw new UnsupportedOperationException(); + } + } + + /* + * See the javadoc of MemoryLayout::varHandle. + */ + enum AccessModeKind { + PLAIN, + READ_WRITE, + ATOMIC_UPDATE, + ATOMIC_NUMERIC_UPDATE, + ATOMIC_BITWISE_UPDATE; + + static AccessModeKind of(AccessMode mode) { + return switch (mode) { + case GET, SET -> PLAIN; + case GET_ACQUIRE, GET_OPAQUE, GET_VOLATILE, SET_VOLATILE, + SET_OPAQUE, SET_RELEASE -> READ_WRITE; + case GET_AND_SET, GET_AND_SET_ACQUIRE, GET_AND_SET_RELEASE, + WEAK_COMPARE_AND_SET, WEAK_COMPARE_AND_SET_RELEASE, + WEAK_COMPARE_AND_SET_ACQUIRE, WEAK_COMPARE_AND_SET_PLAIN, + COMPARE_AND_EXCHANGE, COMPARE_AND_EXCHANGE_ACQUIRE, + COMPARE_AND_EXCHANGE_RELEASE, COMPARE_AND_SET -> ATOMIC_UPDATE; + case GET_AND_ADD, GET_AND_ADD_ACQUIRE, GET_AND_ADD_RELEASE -> ATOMIC_NUMERIC_UPDATE; + default -> ATOMIC_BITWISE_UPDATE; + }; + } + + static Set supportedModes(ValueLayout layout) { + Set supportedModes = EnumSet.noneOf(AccessModeKind.class); + supportedModes.add(PLAIN); + if (layout.byteAlignment() >= layout.byteSize()) { + supportedModes.add(READ_WRITE); + if (layout instanceof ValueLayout.OfInt || layout instanceof ValueLayout.OfLong || + layout instanceof ValueLayout.OfFloat || layout instanceof ValueLayout.OfDouble || + layout instanceof AddressLayout) { + supportedModes.add(ATOMIC_UPDATE); + } + if (layout instanceof ValueLayout.OfInt || layout instanceof ValueLayout.OfLong || + layout instanceof AddressLayout) { + supportedModes.add(ATOMIC_NUMERIC_UPDATE); + supportedModes.add(ATOMIC_BITWISE_UPDATE); + } + } + return supportedModes; + } + } + + static MemoryLayout[] layouts() { + MemoryLayout[] valueLayouts = { + ValueLayout.JAVA_BOOLEAN, + ValueLayout.JAVA_CHAR, + ValueLayout.JAVA_BYTE, + ValueLayout.JAVA_SHORT, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_FLOAT, + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_DOUBLE, + ValueLayout.ADDRESS + }; + List layouts = new ArrayList<>(); + for (MemoryLayout layout : valueLayouts) { + for (int align : new int[] { 1, 2, 4, 8 }) { + layouts.add(layout.withByteAlignment(align)); + } + } + return layouts.toArray(new MemoryLayout[0]); + } + + static MemorySegment[] segments() { + return new MemorySegment[]{ + Arena.ofAuto().allocate(8), + MemorySegment.ofArray(new byte[8]), + MemorySegment.ofArray(new char[4]), + MemorySegment.ofArray(new short[4]), + MemorySegment.ofArray(new int[2]), + MemorySegment.ofArray(new float[2]), + MemorySegment.ofArray(new long[1]), + MemorySegment.ofArray(new double[1]) + }; + } + + @DataProvider(name = "segmentsAndLayoutsAndModes") + static Object[][] segmentsAndLayoutsAndModes() { + List segmentsAndLayouts = new ArrayList<>(); + for (MemorySegment segment : segments()) { + for (MemoryLayout layout : layouts()) { + for (AccessMode mode : AccessMode.values()) { + segmentsAndLayouts.add(new Object[]{segment, layout, mode}); + } + } + } + return segmentsAndLayouts.toArray(new Object[0][]); + } + +} diff a/test/jdk/java/foreign/TestAdaptVarHandles.java b/test/jdk/java/foreign/TestAdaptVarHandles.java --- a/test/jdk/java/foreign/TestAdaptVarHandles.java +++ b/test/jdk/java/foreign/TestAdaptVarHandles.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false -Xverify:all TestAdaptVarHandles * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true -Xverify:all TestAdaptVarHandles * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false -Xverify:all TestAdaptVarHandles * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true -Xverify:all TestAdaptVarHandles */ @@ -81,33 +80,34 @@ } catch (Throwable ex) { throw new ExceptionInInitializerError(); } } - static final VarHandle intHandleIndexed = ValueLayout.JAVA_INT.arrayElementVarHandle(); + static final VarHandle intHandleIndexed = MethodHandles.collectCoordinates(ValueLayout.JAVA_INT.varHandle(), + 1, MethodHandles.insertArguments(ValueLayout.JAVA_INT.scaleHandle(), 0, 0L)); - static final VarHandle intHandle = ValueLayout.JAVA_INT.varHandle(); + static final VarHandle intHandle = MethodHandles.insertCoordinates(ValueLayout.JAVA_INT.varHandle(), 1, 0L); - static final VarHandle floatHandle = ValueLayout.JAVA_FLOAT.varHandle(); + static final VarHandle floatHandle = MethodHandles.insertCoordinates(ValueLayout.JAVA_FLOAT.varHandle(), 1, 0L); @Test public void testFilterValue() throws Throwable { ValueLayout layout = ValueLayout.JAVA_INT; Arena scope = Arena.ofAuto(); MemorySegment segment = scope.allocate(layout); VarHandle intHandle = layout.varHandle(); VarHandle i2SHandle = MethodHandles.filterValue(intHandle, S2I, I2S); - i2SHandle.set(segment, "1"); - String oldValue = (String)i2SHandle.getAndAdd(segment, "42"); + i2SHandle.set(segment, 0L, "1"); + String oldValue = (String)i2SHandle.getAndAdd(segment, 0L, "42"); assertEquals(oldValue, "1"); - String value = (String)i2SHandle.get(segment); + String value = (String)i2SHandle.get(segment, 0L); assertEquals(value, "43"); - boolean swapped = (boolean)i2SHandle.compareAndSet(segment, "43", "12"); + boolean swapped = (boolean)i2SHandle.compareAndSet(segment, 0L, "43", "12"); assertTrue(swapped); - oldValue = (String)i2SHandle.compareAndExchange(segment, "12", "42"); + oldValue = (String)i2SHandle.compareAndExchange(segment, 0L, "12", "42"); assertEquals(oldValue, "12"); - value = (String)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment); + value = (String)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment, 0L); assertEquals(value, "42"); } @Test public void testFilterValueComposite() throws Throwable { @@ -115,41 +115,41 @@ Arena scope = Arena.ofAuto(); MemorySegment segment = scope.allocate(layout); VarHandle intHandle = layout.varHandle(); MethodHandle CTX_S2I = MethodHandles.dropArguments(S2I, 0, String.class, String.class); VarHandle i2SHandle = MethodHandles.filterValue(intHandle, CTX_S2I, CTX_I2S); - i2SHandle = MethodHandles.insertCoordinates(i2SHandle, 1, "a", "b"); - i2SHandle.set(segment, "1"); - String oldValue = (String)i2SHandle.getAndAdd(segment, "42"); + i2SHandle = MethodHandles.insertCoordinates(i2SHandle, 2, "a", "b"); + i2SHandle.set(segment, 0L, "1"); + String oldValue = (String)i2SHandle.getAndAdd(segment, 0L, "42"); assertEquals(oldValue, "ab1"); - String value = (String)i2SHandle.get(segment); + String value = (String)i2SHandle.get(segment, 0L); assertEquals(value, "ab43"); - boolean swapped = (boolean)i2SHandle.compareAndSet(segment, "43", "12"); + boolean swapped = (boolean)i2SHandle.compareAndSet(segment, 0L, "43", "12"); assertTrue(swapped); - oldValue = (String)i2SHandle.compareAndExchange(segment, "12", "42"); + oldValue = (String)i2SHandle.compareAndExchange(segment, 0L, "12", "42"); assertEquals(oldValue, "ab12"); - value = (String)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment); + value = (String)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment, 0L); assertEquals(value, "ab42"); } @Test public void testFilterValueLoose() throws Throwable { ValueLayout layout = ValueLayout.JAVA_INT; Arena scope = Arena.ofAuto(); MemorySegment segment = scope.allocate(layout); VarHandle intHandle = layout.varHandle(); VarHandle i2SHandle = MethodHandles.filterValue(intHandle, O2I, I2O); - i2SHandle.set(segment, "1"); - String oldValue = (String)i2SHandle.getAndAdd(segment, "42"); + i2SHandle.set(segment, 0L, "1"); + String oldValue = (String)i2SHandle.getAndAdd(segment, 0L, "42"); assertEquals(oldValue, "1"); - String value = (String)i2SHandle.get(segment); + String value = (String)i2SHandle.get(segment, 0L); assertEquals(value, "43"); - boolean swapped = (boolean)i2SHandle.compareAndSet(segment, "43", "12"); + boolean swapped = (boolean)i2SHandle.compareAndSet(segment, 0L, "43", "12"); assertTrue(swapped); - oldValue = (String)i2SHandle.compareAndExchange(segment, "12", "42"); + oldValue = (String)i2SHandle.compareAndExchange(segment, 0L, "12", "42"); assertEquals(oldValue, "12"); - value = (String)(Object)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment); + value = (String)(Object)i2SHandle.toMethodHandle(VarHandle.AccessMode.GET).invokeExact(segment, 0L); assertEquals(value, "42"); } @Test(expectedExceptions = IllegalArgumentException.class) public void testBadFilterCarrier() { @@ -192,22 +192,22 @@ public void testBadFilterBoxHandleException() { VarHandle intHandle = ValueLayout.JAVA_INT.varHandle(); VarHandle vh = MethodHandles.filterValue(intHandle, S2I, I2S_EX); try (Arena arena = Arena.ofConfined()) { MemorySegment seg = arena.allocate(ValueLayout.JAVA_INT); - vh.set(seg, "42"); - String x = (String) vh.get(seg); // should throw + vh.set(seg, 0L, "42"); + String x = (String) vh.get(seg, 0L); // should throw } } @Test(expectedExceptions = IllegalStateException.class) public void testBadFilterUnboxHandleException() { VarHandle intHandle = ValueLayout.JAVA_INT.varHandle(); VarHandle vh = MethodHandles.filterValue(intHandle, S2I_EX, I2S); try (Arena arena = Arena.ofConfined()) { MemorySegment seg = arena.allocate(ValueLayout.JAVA_INT); - vh.set(seg, "42"); // should throw + vh.set(seg, 0L, "42"); // should throw } } @Test public void testFilterCoordinates() throws Throwable { diff a/test/jdk/java/foreign/TestAddressDereference.java b/test/jdk/java/foreign/TestAddressDereference.java --- a/test/jdk/java/foreign/TestAddressDereference.java +++ b/test/jdk/java/foreign/TestAddressDereference.java @@ -21,13 +21,11 @@ * questions. */ /* * @test - * @enablePreview * @library ../ /test/lib - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestAddressDereference */ import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; @@ -38,10 +36,12 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.testng.annotations.*; import static org.testng.Assert.*; @@ -182,20 +182,25 @@ LayoutKind(ValueLayout segment) { this.layout = segment; } + private static final Pattern LAYOUT_PATTERN = Pattern.compile("^(?\\d+%)?(?[azcsifjdAZCSIFJD])\\d+$"); + static LayoutKind parse(String layoutString) { - return switch (layoutString.charAt(0)) { - case 'A','a' -> ADDRESS; - case 'z','Z' -> BOOL; - case 'c','C' -> CHAR; - case 's','S' -> SHORT; - case 'i','I' -> INT; - case 'f','F' -> FLOAT; - case 'j','J' -> LONG; - case 'd','D' -> DOUBLE; - default -> throw new AssertionError("Invalid layout string: " + layoutString); - }; + Matcher matcher = LAYOUT_PATTERN.matcher(layoutString); + if (matcher.matches()) { + switch (matcher.group("char")) { + case "A","a": return ADDRESS; + case "z","Z": return BOOL; + case "c","C": return CHAR; + case "s","S": return SHORT; + case "i","I": return INT; + case "f","F": return FLOAT; + case "j","J": return LONG; + case "d","D": return DOUBLE; + }; + } + throw new AssertionError("Invalid layout string: " + layoutString); } } } diff a/test/jdk/java/foreign/TestArrayCopy.java b/test/jdk/java/foreign/TestArrayCopy.java --- a/test/jdk/java/foreign/TestArrayCopy.java +++ b/test/jdk/java/foreign/TestArrayCopy.java @@ -21,16 +21,19 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestArrayCopy */ +import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; 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.nio.ByteOrder; import java.util.ArrayList; import java.util.List; import org.testng.annotations.DataProvider; @@ -286,13 +289,18 @@ arr[i] = (byte)i; } return MemorySegment.ofArray(arr); } + private static VarHandle arrayVarHandle(ValueLayout layout) { + return MethodHandles.collectCoordinates(layout.varHandle(), + 1, MethodHandles.insertArguments(layout.scaleHandle(), 0, 0L)); + } + public static MemorySegment truthSegment(MemorySegment srcSeg, CopyHelper helper, int indexShifts, CopyMode mode) { - VarHandle indexedHandleNO = helper.elementLayout.withOrder(NATIVE_ORDER).arrayElementVarHandle(); - VarHandle indexedHandleNNO = helper.elementLayout.withOrder(NON_NATIVE_ORDER).arrayElementVarHandle(); + VarHandle indexedHandleNO = arrayVarHandle(helper.elementLayout.withOrder(NATIVE_ORDER)); + VarHandle indexedHandleNNO = arrayVarHandle(helper.elementLayout.withOrder(NON_NATIVE_ORDER)); MemorySegment dstSeg = MemorySegment.ofArray(srcSeg.toArray(JAVA_BYTE)); int indexLength = (int) dstSeg.byteSize() / (int)helper.elementLayout.byteSize(); if (mode.direction) { if (mode.swap) { for (int i = indexLength - 1; i >= indexShifts; i--) { diff a/test/jdk/java/foreign/TestArrays.java b/test/jdk/java/foreign/TestArrays.java --- a/test/jdk/java/foreign/TestArrays.java +++ b/test/jdk/java/foreign/TestArrays.java @@ -22,17 +22,19 @@ * */ /* * @test - * @enablePreview * @run testng/othervm --enable-native-access=ALL-UNNAMED TestArrays */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -142,38 +144,38 @@ } @DataProvider(name = "arrays") public Object[][] nativeAccessOps() { Consumer byteInitializer = - (base) -> initBytes(base, bytes, (addr, pos) -> byteHandle.set(addr, pos, (byte)(long)pos)); + (base) -> initBytes(base, bytes, (addr, pos) -> byteHandle.set(addr, 0L, pos, (byte)(long)pos)); Consumer charInitializer = - (base) -> initBytes(base, chars, (addr, pos) -> charHandle.set(addr, pos, (char)(long)pos)); + (base) -> initBytes(base, chars, (addr, pos) -> charHandle.set(addr, 0L, pos, (char)(long)pos)); Consumer shortInitializer = - (base) -> initBytes(base, shorts, (addr, pos) -> shortHandle.set(addr, pos, (short)(long)pos)); + (base) -> initBytes(base, shorts, (addr, pos) -> shortHandle.set(addr, 0L, pos, (short)(long)pos)); Consumer intInitializer = - (base) -> initBytes(base, ints, (addr, pos) -> intHandle.set(addr, pos, (int)(long)pos)); + (base) -> initBytes(base, ints, (addr, pos) -> intHandle.set(addr, 0L, pos, (int)(long)pos)); Consumer floatInitializer = - (base) -> initBytes(base, floats, (addr, pos) -> floatHandle.set(addr, pos, (float)(long)pos)); + (base) -> initBytes(base, floats, (addr, pos) -> floatHandle.set(addr, 0L, pos, (float)(long)pos)); Consumer longInitializer = - (base) -> initBytes(base, longs, (addr, pos) -> longHandle.set(addr, pos, (long)pos)); + (base) -> initBytes(base, longs, (addr, pos) -> longHandle.set(addr, 0L, pos, (long)pos)); Consumer doubleInitializer = - (base) -> initBytes(base, doubles, (addr, pos) -> doubleHandle.set(addr, pos, (double)(long)pos)); + (base) -> initBytes(base, doubles, (addr, pos) -> doubleHandle.set(addr, 0L, pos, (double)(long)pos)); Consumer byteChecker = - (base) -> checkBytes(base, bytes, s -> s.toArray(JAVA_BYTE), (addr, pos) -> (byte)byteHandle.get(addr, pos)); + (base) -> checkBytes(base, bytes, s -> s.toArray(JAVA_BYTE), (addr, pos) -> (byte)byteHandle.get(addr, 0L, pos)); Consumer shortChecker = - (base) -> checkBytes(base, shorts, s -> s.toArray(JAVA_SHORT), (addr, pos) -> (short)shortHandle.get(addr, pos)); + (base) -> checkBytes(base, shorts, s -> s.toArray(JAVA_SHORT), (addr, pos) -> (short)shortHandle.get(addr, 0L, pos)); Consumer charChecker = - (base) -> checkBytes(base, chars, s -> s.toArray(JAVA_CHAR), (addr, pos) -> (char)charHandle.get(addr, pos)); + (base) -> checkBytes(base, chars, s -> s.toArray(JAVA_CHAR), (addr, pos) -> (char)charHandle.get(addr, 0L, pos)); Consumer intChecker = - (base) -> checkBytes(base, ints, s -> s.toArray(JAVA_INT), (addr, pos) -> (int)intHandle.get(addr, pos)); + (base) -> checkBytes(base, ints, s -> s.toArray(JAVA_INT), (addr, pos) -> (int)intHandle.get(addr, 0L, pos)); Consumer floatChecker = - (base) -> checkBytes(base, floats, s -> s.toArray(JAVA_FLOAT), (addr, pos) -> (float)floatHandle.get(addr, pos)); + (base) -> checkBytes(base, floats, s -> s.toArray(JAVA_FLOAT), (addr, pos) -> (float)floatHandle.get(addr, 0L, pos)); Consumer longChecker = - (base) -> checkBytes(base, longs, s -> s.toArray(JAVA_LONG), (addr, pos) -> (long)longHandle.get(addr, pos)); + (base) -> checkBytes(base, longs, s -> s.toArray(JAVA_LONG), (addr, pos) -> (long)longHandle.get(addr, 0L, pos)); Consumer doubleChecker = - (base) -> checkBytes(base, doubles, s -> s.toArray(JAVA_DOUBLE), (addr, pos) -> (double)doubleHandle.get(addr, pos)); + (base) -> checkBytes(base, doubles, s -> s.toArray(JAVA_DOUBLE), (addr, pos) -> (double)doubleHandle.get(addr, 0L, pos)); return new Object[][]{ {byteInitializer, byteChecker, bytes}, {charInitializer, charChecker, chars}, {shortInitializer, shortChecker, shorts}, diff a/test/jdk/java/foreign/TestByteBuffer.java b/test/jdk/java/foreign/TestByteBuffer.java --- a/test/jdk/java/foreign/TestByteBuffer.java +++ b/test/jdk/java/foreign/TestByteBuffer.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules java.base/sun.nio.ch java.base/jdk.internal.foreign * @run testng/othervm/timeout=600 --enable-native-access=ALL-UNNAMED TestByteBuffer */ import java.lang.foreign.*; @@ -120,21 +119,21 @@ static final VarHandle indexHandle = tuples.varHandle(PathElement.sequenceElement(), PathElement.groupElement("index")); static final VarHandle valueHandle = tuples.varHandle(PathElement.sequenceElement(), PathElement.groupElement("value")); static void initTuples(MemorySegment base, long count) { for (long i = 0; i < count ; i++) { - indexHandle.set(base, i, (int)i); - valueHandle.set(base, i, (float)(i / 500f)); + indexHandle.set(base, 0L, i, (int)i); + valueHandle.set(base, 0L, i, (float)(i / 500f)); } } static void checkTuples(MemorySegment base, ByteBuffer bb, long count) { for (long i = 0; i < count ; i++) { int index; float value; - assertEquals(index = bb.getInt(), (int)indexHandle.get(base, i)); - assertEquals(value = bb.getFloat(), (float)valueHandle.get(base, i)); + assertEquals(index = bb.getInt(), (int)indexHandle.get(base, 0L, i)); + assertEquals(value = bb.getFloat(), (float)valueHandle.get(base, 0L, i)); assertEquals(value, index / 500f); } } static void initBytes(MemorySegment base, SequenceLayout seq, BiConsumer handleSetter) { diff a/test/jdk/java/foreign/TestClassLoaderFindNative.java b/test/jdk/java/foreign/TestClassLoaderFindNative.java --- a/test/jdk/java/foreign/TestClassLoaderFindNative.java +++ b/test/jdk/java/foreign/TestClassLoaderFindNative.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestClassLoaderFindNative */ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; diff a/test/jdk/java/foreign/TestDereferencePath.java b/test/jdk/java/foreign/TestDereferencePath.java --- a/test/jdk/java/foreign/TestDereferencePath.java +++ b/test/jdk/java/foreign/TestDereferencePath.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @run testng TestDereferencePath */ import java.lang.foreign.Arena; import java.lang.foreign.MemoryLayout; @@ -71,11 +70,11 @@ // init struct fields a.set(ValueLayout.ADDRESS, 0, b); b.set(ValueLayout.ADDRESS, 0, c); c.set(ValueLayout.JAVA_INT, 0, 42); // dereference - int val = (int) abcx.get(a); + int val = (int) abcx.get(a, 0L); assertEquals(val, 42); } } static final MemoryLayout B_MULTI = MemoryLayout.structLayout( @@ -96,28 +95,28 @@ @Test public void testMulti() { try (Arena arena = Arena.ofConfined()) { // init structs MemorySegment a = arena.allocate(A); - MemorySegment b = arena.allocateArray(B, 2); - MemorySegment c = arena.allocateArray(C, 4); + MemorySegment b = arena.allocate(B, 2); + MemorySegment c = arena.allocate(C, 4); // init struct fields a.set(ValueLayout.ADDRESS, 0, b); b.set(ValueLayout.ADDRESS, 0, c); b.setAtIndex(ValueLayout.ADDRESS, 1, c.asSlice(C.byteSize() * 2)); c.setAtIndex(ValueLayout.JAVA_INT, 0, 1); c.setAtIndex(ValueLayout.JAVA_INT, 1, 2); c.setAtIndex(ValueLayout.JAVA_INT, 2, 3); c.setAtIndex(ValueLayout.JAVA_INT, 3, 4); // dereference - int val00 = (int) abcx_multi.get(a, 0, 0); // a->b[0]->c[0] = 1 + int val00 = (int) abcx_multi.get(a, 0L, 0, 0); // a->b[0]->c[0] = 1 assertEquals(val00, 1); - int val10 = (int) abcx_multi.get(a, 1, 0); // a->b[1]->c[0] = 3 + int val10 = (int) abcx_multi.get(a, 0L, 1, 0); // a->b[1]->c[0] = 3 assertEquals(val10, 3); - int val01 = (int) abcx_multi.get(a, 0, 1); // a->b[0]->c[1] = 2 + int val01 = (int) abcx_multi.get(a, 0L, 0, 1); // a->b[0]->c[1] = 2 assertEquals(val01, 2); - int val11 = (int) abcx_multi.get(a, 1, 1); // a->b[1]->c[1] = 4 + int val11 = (int) abcx_multi.get(a, 0L, 1, 1); // a->b[1]->c[1] = 4 assertEquals(val11, 4); } } @Test(expectedExceptions = IllegalArgumentException.class) @@ -150,9 +149,9 @@ ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT).withName("x")); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(struct.byteSize() + 1).asSlice(1); VarHandle vhX = struct.varHandle(PathElement.groupElement("x"), PathElement.dereferenceElement()); - vhX.set(segment, 42); // should throw + vhX.set(segment, 0L, 42); // should throw } } } diff a/test/jdk/java/foreign/TestDowncallScope.java b/test/jdk/java/foreign/TestDowncallScope.java --- a/test/jdk/java/foreign/TestDowncallScope.java +++ b/test/jdk/java/foreign/TestDowncallScope.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestDowncallBase * * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies * --enable-native-access=ALL-UNNAMED -Dgenerator.sample.factor=17 diff a/test/jdk/java/foreign/TestDowncallStack.java b/test/jdk/java/foreign/TestDowncallStack.java --- a/test/jdk/java/foreign/TestDowncallStack.java +++ b/test/jdk/java/foreign/TestDowncallStack.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestDowncallBase * * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies * --enable-native-access=ALL-UNNAMED -Dgenerator.sample.factor=17 diff a/test/jdk/java/foreign/TestFallbackLookup.java b/test/jdk/java/foreign/TestFallbackLookup.java --- a/test/jdk/java/foreign/TestFallbackLookup.java +++ b/test/jdk/java/foreign/TestFallbackLookup.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm -Dos.name=Windows --enable-native-access=ALL-UNNAMED TestFallbackLookup */ import org.testng.annotations.*; import static org.testng.Assert.*; diff a/test/jdk/java/foreign/TestFree.java b/test/jdk/java/foreign/TestFree.java --- a/test/jdk/java/foreign/TestFree.java +++ b/test/jdk/java/foreign/TestFree.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @bug 8248421 * @summary SystemCLinker should have a way to free memory allocated outside Java * @run testng/othervm --enable-native-access=ALL-UNNAMED TestFree */ @@ -37,9 +36,9 @@ public void test() throws Throwable { String str = "hello world"; MemorySegment addr = allocateMemory(str.length() + 1); addr.copyFrom(MemorySegment.ofArray(str.getBytes())); addr.set(C_CHAR, str.length(), (byte)0); - assertEquals(str, addr.getUtf8String(0)); + assertEquals(str, addr.getString(0)); freeMemory(addr); } } diff a/test/jdk/java/foreign/TestFunctionDescriptor.java b/test/jdk/java/foreign/TestFunctionDescriptor.java --- a/test/jdk/java/foreign/TestFunctionDescriptor.java +++ b/test/jdk/java/foreign/TestFunctionDescriptor.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestFunctionDescriptor */ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.MemoryLayout; diff a/test/jdk/java/foreign/TestHFA.java b/test/jdk/java/foreign/TestHFA.java --- a/test/jdk/java/foreign/TestHFA.java +++ b/test/jdk/java/foreign/TestHFA.java @@ -23,12 +23,10 @@ */ /* * @test * @summary Test passing of Homogeneous Float Aggregates. - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * * @run testng/othervm --enable-native-access=ALL-UNNAMED TestHFA */ import java.lang.foreign.*; diff a/test/jdk/java/foreign/TestHandshake.java b/test/jdk/java/foreign/TestHandshake.java --- a/test/jdk/java/foreign/TestHandshake.java +++ b/test/jdk/java/foreign/TestHandshake.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.misc * @key randomness * @run testng/othervm TestHandshake * @run testng/othervm -Xint TestHandshake * @run testng/othervm -XX:TieredStopAtLevel=1 TestHandshake diff a/test/jdk/java/foreign/TestHeapAlignment.java b/test/jdk/java/foreign/TestHeapAlignment.java --- a/test/jdk/java/foreign/TestHeapAlignment.java +++ b/test/jdk/java/foreign/TestHeapAlignment.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestHeapAlignment */ import java.lang.foreign.AddressLayout; import java.lang.foreign.MemoryLayout; @@ -43,15 +41,15 @@ public class TestHeapAlignment { @Test(dataProvider = "layouts") public void testHeapAlignment(MemorySegment segment, int align, Object val, Object arr, ValueLayout layout, Function segmentFactory) { - assertAligned(align, layout, () -> layout.varHandle().get(segment)); - assertAligned(align, layout, () -> layout.varHandle().set(segment, val)); + assertAligned(align, layout, () -> layout.varHandle().get(segment, 0L)); + assertAligned(align, layout, () -> layout.varHandle().set(segment, 0L, val)); MemoryLayout seq = MemoryLayout.sequenceLayout(10, layout); - assertAligned(align, layout, () -> seq.varHandle(MemoryLayout.PathElement.sequenceElement()).get(segment, 0L)); - assertAligned(align, layout, () -> seq.varHandle(MemoryLayout.PathElement.sequenceElement()).set(segment, 0L, val)); + assertAligned(align, layout, () -> seq.varHandle(MemoryLayout.PathElement.sequenceElement()).get(segment, 0L, 0L)); + assertAligned(align, layout, () -> seq.varHandle(MemoryLayout.PathElement.sequenceElement()).set(segment, 0L, 0L, val)); assertAligned(align, layout, () -> segment.spliterator(layout)); if (arr != null) { assertAligned(align, layout, () -> MemorySegment.copy(arr, 0, segment, layout, 0, 1)); assertAligned(align, layout, () -> MemorySegment.copy(segment, layout, 0, arr, 0, 1)); assertAligned(align, layout, () -> { diff a/test/jdk/java/foreign/TestIllegalLink.java b/test/jdk/java/foreign/TestIllegalLink.java --- a/test/jdk/java/foreign/TestIllegalLink.java +++ b/test/jdk/java/foreign/TestIllegalLink.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @run testng/othervm --enable-native-access=ALL-UNNAMED TestIllegalLink */ import java.lang.foreign.Arena; @@ -45,10 +43,12 @@ import jdk.internal.foreign.CABI; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import static java.lang.foreign.ValueLayout.*; + import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; public class TestIllegalLink extends NativeTestHelper { @@ -100,11 +100,11 @@ @DataProvider public static Object[][] downcallOnlyOptions() { return new Object[][]{ { Linker.Option.firstVariadicArg(0) }, { Linker.Option.captureCallState("errno") }, - { Linker.Option.isTrivial() }, + { Linker.Option.critical() }, }; } @DataProvider public static Object[][] types() { @@ -126,11 +126,11 @@ IS_LE ? "Unsupported layout: 2%i4" : "Unsupported layout: 2%I4" }, { FunctionDescriptor.ofVoid(C_POINTER.withByteAlignment(2)), NO_OPTIONS, - IS_LE ? "Unsupported layout: 2%a8" : "Unsupported layout: 2%A8" + (IS_LE ? "Unsupported layout: 2%a" : "Unsupported layout: 2%A") + ADDRESS.byteSize() }, { FunctionDescriptor.ofVoid(ValueLayout.JAVA_CHAR.withByteAlignment(4)), NO_OPTIONS, IS_LE ? "Unsupported layout: 4%c2" : "Unsupported layout: 4%C2" @@ -154,11 +154,11 @@ NO_OPTIONS, IS_LE ? "Unsupported layout: 1%s2" : "Unsupported layout: 1%S2" }, { FunctionDescriptor.ofVoid(MemoryLayout.structLayout( - MemoryLayout.sequenceLayout( + MemoryLayout.sequenceLayout(1, C_INT.withByteAlignment(1) ))), NO_OPTIONS, IS_LE ? "Unsupported layout: 1%i4" : "Unsupported layout: 1%I4" }, @@ -179,28 +179,26 @@ FunctionDescriptor.of(MemoryLayout.structLayout(C_INT.withOrder(nonNativeOrder()))), NO_OPTIONS, IS_LE ? "Unsupported layout: I4" : "Unsupported layout: i4" }, { - FunctionDescriptor.of(MemoryLayout.structLayout(MemoryLayout.sequenceLayout(C_INT.withOrder(nonNativeOrder())))), + FunctionDescriptor.of(MemoryLayout.structLayout(MemoryLayout.sequenceLayout(1, C_INT.withOrder(nonNativeOrder())))), NO_OPTIONS, IS_LE ? "Unsupported layout: I4" : "Unsupported layout: i4" }, - { - FunctionDescriptor.ofVoid(MemoryLayout.structLayout( - ValueLayout.JAVA_LONG, - ValueLayout.JAVA_INT)), // missing trailing padding - NO_OPTIONS, - "has unexpected size" - }, { FunctionDescriptor.ofVoid(MemoryLayout.structLayout( ValueLayout.JAVA_INT, MemoryLayout.paddingLayout(4))), // too much trailing padding NO_OPTIONS, "has unexpected size" }, + { + FunctionDescriptor.ofVoid(), + new Linker.Option[]{Linker.Option.critical(), Linker.Option.captureCallState("errno")}, + "Incompatible linker options: captureCallState, critical" + }, })); for (ValueLayout illegalLayout : List.of(C_CHAR, ValueLayout.JAVA_CHAR, C_BOOL, C_SHORT, C_FLOAT)) { cases.add(new Object[]{ FunctionDescriptor.ofVoid(C_INT, illegalLayout), @@ -210,17 +208,26 @@ } if (IS_SYSV) { cases.add(new Object[] { FunctionDescriptor.ofVoid(MemoryLayout.structLayout( - MemoryLayout.sequenceLayout( + MemoryLayout.sequenceLayout(Long.MAX_VALUE / C_INT.byteSize(), C_INT ))), NO_OPTIONS, "GroupLayout is too large" }); } + if (ValueLayout.JAVA_LONG.byteAlignment() == 8) { + cases.add(new Object[]{ + FunctionDescriptor.ofVoid(MemoryLayout.structLayout( + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_INT)), // missing trailing padding + NO_OPTIONS, + "has unexpected size" + }); + } return cases.toArray(Object[][]::new); } private static ByteOrder nonNativeOrder() { return ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN diff a/test/jdk/java/foreign/TestIntrinsics.java b/test/jdk/java/foreign/TestIntrinsics.java --- a/test/jdk/java/foreign/TestIntrinsics.java +++ b/test/jdk/java/foreign/TestIntrinsics.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=true * --enable-native-access=ALL-UNNAMED * -Xbatch * TestIntrinsics diff a/test/jdk/java/foreign/TestLargeSegmentCopy.java b/test/jdk/java/foreign/TestLargeSegmentCopy.java --- a/test/jdk/java/foreign/TestLargeSegmentCopy.java +++ b/test/jdk/java/foreign/TestLargeSegmentCopy.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @requires sun.arch.data.model == "64" * @bug 8292851 * @run testng/othervm -Xmx4G TestLargeSegmentCopy */ diff a/test/jdk/java/foreign/TestLayoutPaths.java b/test/jdk/java/foreign/TestLayoutPaths.java --- a/test/jdk/java/foreign/TestLayoutPaths.java +++ b/test/jdk/java/foreign/TestLayoutPaths.java @@ -22,28 +22,29 @@ * */ /* * @test - * @enablePreview * @run testng TestLayoutPaths */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; 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; import static java.lang.foreign.MemoryLayout.PathElement.groupElement; import static java.lang.foreign.MemoryLayout.PathElement.sequenceElement; import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_LONG; import static java.lang.foreign.ValueLayout.JAVA_SHORT; import static org.testng.Assert.*; public class TestLayoutPaths { @@ -134,37 +135,73 @@ SequenceLayout seq = MemoryLayout.sequenceLayout(5, MemoryLayout.structLayout(JAVA_INT)); seq.byteOffsetHandle(sequenceElement(5, 1)); // invalid range (starting position is outside the sequence) } @Test - public void testBadAlignmentOfRoot() throws Throwable { + public void testBadAlignmentOfRoot() { MemoryLayout struct = MemoryLayout.structLayout( JAVA_INT, JAVA_SHORT.withName("x")); assertEquals(struct.byteAlignment(), 4); try (Arena arena = Arena.ofConfined()) { MemorySegment seg = arena.allocate(struct.byteSize() + 2, struct.byteAlignment()).asSlice(2); 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, (short) 42); + 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); + 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); // bad path elements for (PathElement e : List.of( sequenceElement(), sequenceElement(0, 2) )) { @@ -269,11 +306,11 @@ @Test(dataProvider = "testLayouts") public void testOffsetHandle(MemoryLayout layout, PathElement[] pathElements, long[] indexes, long expectedByteOffset) throws Throwable { MethodHandle byteOffsetHandle = layout.byteOffsetHandle(pathElements); byteOffsetHandle = byteOffsetHandle.asSpreader(long[].class, indexes.length); - long actualByteOffset = (long) byteOffsetHandle.invokeExact(indexes); + long actualByteOffset = (long) byteOffsetHandle.invokeExact(0L, indexes); assertEquals(actualByteOffset, expectedByteOffset); } @DataProvider public static Object[][] testLayouts() { @@ -358,11 +395,11 @@ MethodHandle sliceHandle = layout.sliceHandle(pathElements); sliceHandle = sliceHandle.asSpreader(long[].class, indexes.length); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(layout); - MemorySegment slice = (MemorySegment) sliceHandle.invokeExact(segment, indexes); + MemorySegment slice = (MemorySegment) sliceHandle.invokeExact(segment, 0L, indexes); assertEquals(slice.address() - segment.address(), expectedByteOffset); assertEquals(slice.byteSize(), selected.byteSize()); } } diff a/test/jdk/java/foreign/TestLayouts.java b/test/jdk/java/foreign/TestLayouts.java --- a/test/jdk/java/foreign/TestLayouts.java +++ b/test/jdk/java/foreign/TestLayouts.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestLayouts */ import java.lang.foreign.*; @@ -96,17 +95,17 @@ try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(seq);; VarHandle indexHandle = seq.varHandle(MemoryLayout.PathElement.sequenceElement()); // init segment for (int i = 0 ; i < 10 ; i++) { - indexHandle.set(segment, (long)i, i); + indexHandle.set(segment, 0L, (long)i, i); } //check statically indexed handles for (int i = 0 ; i < 10 ; i++) { VarHandle preindexHandle = seq.varHandle(MemoryLayout.PathElement.sequenceElement(i)); - int expected = (int)indexHandle.get(segment, (long)i); - int found = (int)preindexHandle.get(segment); + int expected = (int)indexHandle.get(segment, 0L, (long)i); + int found = (int)preindexHandle.get(segment, 0L); assertEquals(expected, found); } } } @@ -199,16 +198,11 @@ public void testSequenceBadCount() { assertThrows(IllegalArgumentException.class, // negative () -> MemoryLayout.sequenceLayout(-2, JAVA_SHORT)); } - @Test(dataProvider = "basicLayouts") - public void testSequenceInferredCount(MemoryLayout layout) { - assertEquals(MemoryLayout.sequenceLayout(layout), - MemoryLayout.sequenceLayout(Long.MAX_VALUE / layout.byteSize(), layout)); - } - + @Test public void testSequenceNegativeElementCount() { assertThrows(IllegalArgumentException.class, // negative () -> MemoryLayout.sequenceLayout(-1, JAVA_SHORT)); } @@ -298,18 +292,18 @@ } @Test(dataProvider="layoutsAndAlignments", expectedExceptions = IllegalArgumentException.class) public void testBadSequenceElementAlignmentTooBig(MemoryLayout layout, long byteAlign) { layout = layout.withByteAlignment(layout.byteSize() * 2); // hyper-align - MemoryLayout.sequenceLayout(layout); + MemoryLayout.sequenceLayout(1, layout); } @Test(dataProvider="layoutsAndAlignments") public void testBadSequenceElementSizeNotMultipleOfAlignment(MemoryLayout layout, long byteAlign) { boolean shouldFail = layout.byteSize() % layout.byteAlignment() != 0; try { - MemoryLayout.sequenceLayout(layout); + MemoryLayout.sequenceLayout(1, layout); assertFalse(shouldFail); } catch (IllegalArgumentException ex) { assertTrue(shouldFail); } } @@ -336,18 +330,10 @@ } catch (IllegalArgumentException ex) { assertTrue(shouldFail); } } - @Test(dataProvider="layoutsAndAlignments") - public void testArrayElementVarHandleBadAlignment(MemoryLayout layout, long byteAlign) { - if (layout instanceof ValueLayout) { - assertThrows(UnsupportedOperationException.class, () -> - ((ValueLayout) layout).withByteAlignment(byteAlign * 2).arrayElementVarHandle()); - } - } - @Test(dataProvider="layoutsAndAlignments", expectedExceptions = IllegalArgumentException.class) public void testBadStruct(MemoryLayout layout, long byteAlign) { layout = layout.withByteAlignment(layout.byteSize() * 2); // hyper-align MemoryLayout.structLayout(layout, layout); } @@ -357,10 +343,41 @@ SequenceLayout layout = MemoryLayout.sequenceLayout(10, JAVA_INT); // Step must be != 0 PathElement.sequenceElement(3, 0); } + @Test + public void testVarHandleCaching() { + assertSame(JAVA_INT.varHandle(), JAVA_INT.varHandle()); + assertSame(JAVA_INT.withName("foo").varHandle(), JAVA_INT.varHandle()); + + assertNotSame(JAVA_INT_UNALIGNED.varHandle(), JAVA_INT.varHandle()); + assertNotSame(ADDRESS.withTargetLayout(JAVA_INT).varHandle(), ADDRESS.varHandle()); + } + + @Test(expectedExceptions=IllegalArgumentException.class, + expectedExceptionsMessageRegExp=".*Negative offset.*") + public void testScaleNegativeOffset() { + JAVA_INT.scale(-1, 0); + } + + @Test(expectedExceptions=IllegalArgumentException.class, + expectedExceptionsMessageRegExp=".*Negative index.*") + public void testScaleNegativeIndex() { + JAVA_INT.scale(0, -1); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void testScaleAddOverflow() { + JAVA_INT.scale(Long.MAX_VALUE, 1); + } + + @Test(expectedExceptions=ArithmeticException.class) + public void testScaleMultiplyOverflow() { + JAVA_INT.scale(0, Long.MAX_VALUE); + } + @DataProvider(name = "badAlignments") public Object[][] layoutsAndBadAlignments() { LayoutKind[] layoutKinds = LayoutKind.values(); Object[][] values = new Object[layoutKinds.length * 2][2]; for (int i = 0; i < layoutKinds.length ; i++) { @@ -494,11 +511,10 @@ } static Stream groupLayoutStream() { return Stream.of( MemoryLayout.sequenceLayout(10, JAVA_INT), - MemoryLayout.sequenceLayout(JAVA_INT), MemoryLayout.structLayout(JAVA_INT, MemoryLayout.paddingLayout(4), JAVA_LONG), MemoryLayout.unionLayout(JAVA_LONG, JAVA_DOUBLE) ); } diff a/test/jdk/java/foreign/TestLinker.java b/test/jdk/java/foreign/TestLinker.java --- a/test/jdk/java/foreign/TestLinker.java +++ b/test/jdk/java/foreign/TestLinker.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @run testng TestLinker * @run testng/othervm/policy=security.policy * -Djava.security.manager=default TestLinker */ @@ -35,20 +33,24 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static java.lang.foreign.MemoryLayout.*; import static java.lang.foreign.ValueLayout.JAVA_CHAR; import static java.lang.foreign.ValueLayout.JAVA_SHORT; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertNotSame; +import static org.testng.Assert.assertTrue; public class TestLinker extends NativeTestHelper { static final boolean IS_FALLBACK_LINKER = CABI.current() == CABI.FALLBACK; @@ -89,14 +91,10 @@ FunctionDescriptor.ofVoid(C_INT.withName("x")) }, { FunctionDescriptor.ofVoid(structLayout(C_INT)), FunctionDescriptor.ofVoid(structLayout(C_INT).withName("x")) }, { FunctionDescriptor.ofVoid(structLayout(C_INT)), FunctionDescriptor.ofVoid(structLayout(C_INT.withName("x"))) }, - { FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG)), - FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG.withName("x"))) }, - { FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG)), - FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4).withName("x"), C_LONG_LONG)) }, { FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT))), FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT).withName("x"))) }, { FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT))), FunctionDescriptor.ofVoid(structLayout(sequenceLayout(1, C_INT.withName("x")))) }, { FunctionDescriptor.ofVoid(C_POINTER), @@ -111,10 +109,16 @@ cases.add(new Object[]{ FunctionDescriptor.ofVoid(unionLayout(C_INT)), FunctionDescriptor.ofVoid(unionLayout(C_INT).withName("x")) }); cases.add(new Object[]{ FunctionDescriptor.ofVoid(unionLayout(C_INT)), FunctionDescriptor.ofVoid(unionLayout(C_INT.withName("x"))) }); } + if (C_LONG_LONG.byteAlignment() == 8) { + cases.add(new Object[]{ FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG)), + FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG.withName("x"))) }); + cases.add(new Object[]{ FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4), C_LONG_LONG)), + FunctionDescriptor.ofVoid(structLayout(C_INT, paddingLayout(4).withName("x"), C_LONG_LONG)) }); + } return cases.toArray(Object[][]::new); } @DataProvider @@ -138,6 +142,29 @@ expectedExceptionsMessageRegExp = ".*Unknown name.*") public void testInvalidPreservedValueName() { Linker.Option.captureCallState("foo"); // throws } + @Test(dataProvider = "canonicalTypeNames") + public void testCanonicalLayouts(String typeName) { + MemoryLayout layout = LINKER.canonicalLayouts().get(typeName); + assertNotNull(layout); + assertTrue(layout instanceof ValueLayout); + } + + @DataProvider + public static Object[][] canonicalTypeNames() { + return new Object[][]{ + { "bool" }, + { "char" }, + { "short" }, + { "int" }, + { "long" }, + { "long long" }, + { "float" }, + { "double" }, + { "void*" }, + { "size_t" }, + { "wchar_t" }, + }; + } } diff a/test/jdk/java/foreign/TestMatrix.java b/test/jdk/java/foreign/TestMatrix.java --- a/test/jdk/java/foreign/TestMatrix.java +++ b/test/jdk/java/foreign/TestMatrix.java @@ -30,12 +30,10 @@ * -concurrency:auto \ * ./test/jdk/java/foreign/TestMatrix.java */ /* @test id=UpcallHighArity-FF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallHighArity * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -43,12 +41,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallHighArity */ /* @test id=UpcallHighArity-TF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallHighArity * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -56,12 +52,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallHighArity */ /* @test id=UpcallHighArity-FT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallHighArity * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -69,12 +63,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallHighArity */ /* @test id=UpcallHighArity-TT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallHighArity * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -82,60 +74,50 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallHighArity */ /* @test id=DowncallScope-F - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestDowncallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=false * TestDowncallScope */ /* @test id=DowncallScope-T - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestDowncallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=true * TestDowncallScope */ /* @test id=DowncallStack-F - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestDowncallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=false * TestDowncallStack */ /* @test id=DowncallStack-T - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestDowncallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=true * TestDowncallStack */ /* @test id=UpcallScope-FF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -143,12 +125,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallScope */ /* @test id=UpcallScope-TF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -156,12 +136,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallScope */ /* @test id=UpcallScope-FT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -169,12 +147,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallScope */ /* @test id=UpcallScope-TT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -182,12 +158,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallScope */ /* @test id=UpcallAsync-FF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -195,12 +169,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallAsync */ /* @test id=UpcallAsync-TF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -208,12 +180,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallAsync */ /* @test id=UpcallAsync-FT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -221,12 +191,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallAsync */ /* @test id=UpcallAsync-TT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -234,12 +202,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallAsync */ /* @test id=UpcallStack-FF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -247,12 +213,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallStack */ /* @test id=UpcallStack-TF - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -260,12 +224,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=false * TestUpcallStack */ /* @test id=UpcallStack-FT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -273,12 +235,10 @@ * -Djdk.internal.foreign.UpcallLinker.USE_SPEC=true * TestUpcallStack */ /* @test id=UpcallStack-TT - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED @@ -287,12 +247,10 @@ * TestUpcallStack */ /* * @test id=VarArgs - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper * * @run testng/othervm/native/manual * --enable-native-access=ALL-UNNAMED diff a/test/jdk/java/foreign/TestMemoryAccess.java b/test/jdk/java/foreign/TestMemoryAccess.java --- a/test/jdk/java/foreign/TestMemoryAccess.java +++ b/test/jdk/java/foreign/TestMemoryAccess.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false -Xverify:all TestMemoryAccess * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=true -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true -Xverify:all TestMemoryAccess * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=false -Xverify:all TestMemoryAccess * @run testng/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true -Xverify:all TestMemoryAccess */ @@ -64,16 +63,10 @@ public void testArrayAccess(Function viewFactory, MemoryLayout elemLayout, ArrayChecker checker) { SequenceLayout seq = MemoryLayout.sequenceLayout(10, elemLayout.withName("elem")); testArrayAccessInternal(viewFactory, seq, seq.varHandle(PathElement.sequenceElement()), checker); } - @Test(dataProvider = "arrayElements") - public void testArrayAccessAlt(Function viewFactory, ValueLayout elemLayout, ArrayChecker checker) { - SequenceLayout seq = MemoryLayout.sequenceLayout(10, elemLayout.withName("elem")); - testArrayAccessInternal(viewFactory, seq, elemLayout.arrayElementVarHandle(), checker); - } - @Test(dataProvider = "arrayElements") public void testPaddedArrayAccessByName(Function viewFactory, MemoryLayout elemLayout, ArrayChecker checker) { SequenceLayout seq = MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(MemoryLayout.paddingLayout(elemLayout.byteSize()), elemLayout.withName("elem"))); testArrayAccessInternal(viewFactory, seq, seq.varHandle(MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.groupElement("elem")), checker); } @@ -156,17 +149,10 @@ MemoryLayout.sequenceLayout(10, elemLayout.withName("elem"))); testMatrixAccessInternal(viewFactory, seq, seq.varHandle( PathElement.sequenceElement(), PathElement.sequenceElement()), checker); } - @Test(dataProvider = "matrixElements") - public void testMatrixAccessAlt(Function viewFactory, ValueLayout elemLayout, MatrixChecker checker) { - SequenceLayout seq = MemoryLayout.sequenceLayout(20, - MemoryLayout.sequenceLayout(10, elemLayout.withName("elem"))); - testMatrixAccessInternal(viewFactory, seq, elemLayout.arrayElementVarHandle(10), checker); - } - @Test(dataProvider = "matrixElements") public void testPaddedMatrixAccessByName(Function viewFactory, MemoryLayout elemLayout, MatrixChecker checker) { SequenceLayout seq = MemoryLayout.sequenceLayout(20, MemoryLayout.sequenceLayout(10, MemoryLayout.structLayout(MemoryLayout.paddingLayout(elemLayout.byteSize()), elemLayout.withName("elem")))); testMatrixAccessInternal(viewFactory, seq, @@ -265,42 +251,42 @@ interface Checker { void check(VarHandle handle, MemorySegment segment); Checker BYTE = (handle, segment) -> { - handle.set(segment, (byte)42); - assertEquals(42, (byte)handle.get(segment)); + handle.set(segment, 0L, (byte)42); + assertEquals(42, (byte)handle.get(segment, 0L)); }; Checker SHORT = (handle, segment) -> { - handle.set(segment, (short)42); - assertEquals(42, (short)handle.get(segment)); + handle.set(segment, 0L, (short)42); + assertEquals(42, (short)handle.get(segment, 0L)); }; Checker CHAR = (handle, segment) -> { - handle.set(segment, (char)42); - assertEquals(42, (char)handle.get(segment)); + handle.set(segment, 0L, (char)42); + assertEquals(42, (char)handle.get(segment, 0L)); }; Checker INT = (handle, segment) -> { - handle.set(segment, 42); - assertEquals(42, (int)handle.get(segment)); + handle.set(segment, 0L, 42); + assertEquals(42, (int)handle.get(segment, 0L)); }; Checker LONG = (handle, segment) -> { - handle.set(segment, (long)42); - assertEquals(42, (long)handle.get(segment)); + handle.set(segment, 0L, (long)42); + assertEquals(42, (long)handle.get(segment, 0L)); }; Checker FLOAT = (handle, segment) -> { - handle.set(segment, (float)42); - assertEquals((float)42, (float)handle.get(segment)); + handle.set(segment, 0L, (float)42); + assertEquals((float)42, (float)handle.get(segment, 0L)); }; Checker DOUBLE = (handle, segment) -> { - handle.set(segment, (double)42); - assertEquals((double)42, (double)handle.get(segment)); + handle.set(segment, 0L, (double)42); + assertEquals((double)42, (double)handle.get(segment, 0L)); }; } @DataProvider(name = "arrayElements") public Object[][] createArrayData() { @@ -342,42 +328,42 @@ interface ArrayChecker { void check(VarHandle handle, MemorySegment segment, long index); ArrayChecker BYTE = (handle, segment, i) -> { - handle.set(segment, i, (byte)i); - assertEquals(i, (byte)handle.get(segment, i)); + handle.set(segment, 0L, i, (byte)i); + assertEquals(i, (byte)handle.get(segment, 0L, i)); }; ArrayChecker SHORT = (handle, segment, i) -> { - handle.set(segment, i, (short)i); - assertEquals(i, (short)handle.get(segment, i)); + handle.set(segment, 0L, i, (short)i); + assertEquals(i, (short)handle.get(segment, 0L, i)); }; ArrayChecker CHAR = (handle, segment, i) -> { - handle.set(segment, i, (char)i); - assertEquals(i, (char)handle.get(segment, i)); + handle.set(segment, 0L, i, (char)i); + assertEquals(i, (char)handle.get(segment, 0L, i)); }; ArrayChecker INT = (handle, segment, i) -> { - handle.set(segment, i, (int)i); - assertEquals(i, (int)handle.get(segment, i)); + handle.set(segment, 0L, i, (int)i); + assertEquals(i, (int)handle.get(segment, 0L, i)); }; ArrayChecker LONG = (handle, segment, i) -> { - handle.set(segment, i, (long)i); - assertEquals(i, (long)handle.get(segment, i)); + handle.set(segment, 0L, i, (long)i); + assertEquals(i, (long)handle.get(segment, 0L, i)); }; ArrayChecker FLOAT = (handle, segment, i) -> { - handle.set(segment, i, (float)i); - assertEquals((float)i, (float)handle.get(segment, i)); + handle.set(segment, 0L, i, (float)i); + assertEquals((float)i, (float)handle.get(segment, 0L, i)); }; ArrayChecker DOUBLE = (handle, segment, i) -> { - handle.set(segment, i, (double)i); - assertEquals((double)i, (double)handle.get(segment, i)); + handle.set(segment, 0L, i, (double)i); + assertEquals((double)i, (double)handle.get(segment, 0L, i)); }; } @DataProvider(name = "matrixElements") public Object[][] createMatrixData() { @@ -427,50 +413,50 @@ interface MatrixChecker { void check(VarHandle handle, MemorySegment segment, long row, long col); MatrixChecker BYTE = (handle, segment, r, c) -> { - handle.set(segment, r, c, (byte)(r + c)); - assertEquals(r + c, (byte)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (byte)(r + c)); + assertEquals(r + c, (byte)handle.get(segment, 0L, r, c)); }; MatrixChecker BOOLEAN = (handle, segment, r, c) -> { - handle.set(segment, r, c, (r + c) != 0); - assertEquals((r + c) != 0, (boolean)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (r + c) != 0); + assertEquals((r + c) != 0, (boolean)handle.get(segment, 0L, r, c)); }; MatrixChecker SHORT = (handle, segment, r, c) -> { - handle.set(segment, r, c, (short)(r + c)); - assertEquals(r + c, (short)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (short)(r + c)); + assertEquals(r + c, (short)handle.get(segment, 0L, r, c)); }; MatrixChecker CHAR = (handle, segment, r, c) -> { - handle.set(segment, r, c, (char)(r + c)); - assertEquals(r + c, (char)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (char)(r + c)); + assertEquals(r + c, (char)handle.get(segment, 0L, r, c)); }; MatrixChecker INT = (handle, segment, r, c) -> { - handle.set(segment, r, c, (int)(r + c)); - assertEquals(r + c, (int)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (int)(r + c)); + assertEquals(r + c, (int)handle.get(segment, 0L, r, c)); }; MatrixChecker LONG = (handle, segment, r, c) -> { - handle.set(segment, r, c, r + c); - assertEquals(r + c, (long)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, r + c); + assertEquals(r + c, (long)handle.get(segment, 0L, r, c)); }; MatrixChecker ADDR = (handle, segment, r, c) -> { - handle.set(segment, r, c, MemorySegment.ofAddress(r + c)); - assertEquals(MemorySegment.ofAddress(r + c), (MemorySegment) handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, MemorySegment.ofAddress(r + c)); + assertEquals(MemorySegment.ofAddress(r + c), (MemorySegment) handle.get(segment, 0L, r, c)); }; MatrixChecker FLOAT = (handle, segment, r, c) -> { - handle.set(segment, r, c, (float)(r + c)); - assertEquals((float)(r + c), (float)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (float)(r + c)); + assertEquals((float)(r + c), (float)handle.get(segment, 0L, r, c)); }; MatrixChecker DOUBLE = (handle, segment, r, c) -> { - handle.set(segment, r, c, (double)(r + c)); - assertEquals((double)(r + c), (double)handle.get(segment, r, c)); + handle.set(segment, 0L, r, c, (double)(r + c)); + assertEquals((double)(r + c), (double)handle.get(segment, 0L, r, c)); }; } } diff a/test/jdk/java/foreign/TestMemoryAccessInstance.java b/test/jdk/java/foreign/TestMemoryAccessInstance.java --- a/test/jdk/java/foreign/TestMemoryAccessInstance.java +++ b/test/jdk/java/foreign/TestMemoryAccessInstance.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng/othervm --enable-native-access=ALL-UNNAMED TestMemoryAccessInstance */ import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; diff a/test/jdk/java/foreign/TestMemoryAlignment.java b/test/jdk/java/foreign/TestMemoryAlignment.java --- a/test/jdk/java/foreign/TestMemoryAlignment.java +++ b/test/jdk/java/foreign/TestMemoryAlignment.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestMemoryAlignment */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; @@ -46,12 +45,12 @@ ValueLayout aligned = layout.withByteAlignment(align); assertEquals(aligned.byteAlignment(), align); //unreasonable alignment here, to make sure access throws VarHandle vh = aligned.varHandle(); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(aligned);; - vh.set(segment, -42); - int val = (int)vh.get(segment); + vh.set(segment, 0L, -42); + int val = (int)vh.get(segment, 0L); assertEquals(val, -42); } } @Test(dataProvider = "alignments") @@ -63,11 +62,11 @@ try (Arena arena = Arena.ofConfined()) { MemoryLayout alignedGroup = MemoryLayout.structLayout(MemoryLayout.paddingLayout(1), aligned); assertEquals(alignedGroup.byteAlignment(), align); VarHandle vh = aligned.varHandle(); MemorySegment segment = arena.allocate(alignedGroup);; - vh.set(segment.asSlice(1L), -42); + vh.set(segment.asSlice(1L), 0L, -42); assertEquals(align, 8); //this is the only case where access is aligned } catch (IllegalArgumentException ex) { assertNotEquals(align, 8); //if align != 8, access is always unaligned } } @@ -91,11 +90,11 @@ SequenceLayout layout = MemoryLayout.sequenceLayout(5, ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN).withByteAlignment(align)); VarHandle vh = layout.varHandle(PathElement.sequenceElement()); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(layout);; for (long i = 0 ; i < 5 ; i++) { - vh.set(segment, i, -42); + vh.set(segment, 0L, i, -42); } } } catch (IllegalArgumentException ex) { assertTrue(align > 4); //if align > 4, access is always unaligned (for some elements) } @@ -114,16 +113,16 @@ VarHandle vh_c = g.varHandle(PathElement.groupElement("a")); VarHandle vh_s = g.varHandle(PathElement.groupElement("b")); VarHandle vh_i = g.varHandle(PathElement.groupElement("c")); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(g);; - vh_c.set(segment, Byte.MIN_VALUE); - assertEquals(vh_c.get(segment), Byte.MIN_VALUE); - vh_s.set(segment, Short.MIN_VALUE); - assertEquals(vh_s.get(segment), Short.MIN_VALUE); - vh_i.set(segment, Integer.MIN_VALUE); - assertEquals(vh_i.get(segment), Integer.MIN_VALUE); + vh_c.set(segment, 0L, Byte.MIN_VALUE); + assertEquals(vh_c.get(segment, 0L), Byte.MIN_VALUE); + vh_s.set(segment, 0L, Short.MIN_VALUE); + assertEquals(vh_s.get(segment, 0L), Short.MIN_VALUE); + vh_i.set(segment, 0L, Integer.MIN_VALUE); + assertEquals(vh_i.get(segment, 0L), Integer.MIN_VALUE); } } @DataProvider(name = "alignments") public Object[][] createAlignments() { diff a/test/jdk/java/foreign/TestMemoryDereference.java b/test/jdk/java/foreign/TestMemoryDereference.java --- a/test/jdk/java/foreign/TestMemoryDereference.java +++ b/test/jdk/java/foreign/TestMemoryDereference.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestMemoryDereference */ import java.lang.foreign.MemorySegment; diff a/test/jdk/java/foreign/TestMemoryInspection.java b/test/jdk/java/foreign/TestMemoryInspection.java --- /dev/null +++ b/test/jdk/java/foreign/TestMemoryInspection.java @@ -0,0 +1,301 @@ + +/* + * Copyright (c) 2022, 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. + */ + +/* + * @test + * @modules java.base/jdk.internal.foreign + * @run testng/othervm --enable-native-access=ALL-UNNAMED TestMemoryInspection + */ + +import java.lang.foreign.*; +import java.lang.invoke.VarHandle; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Stream; + +import jdk.internal.foreign.MemoryInspection; +import org.testng.annotations.*; + +import static java.lang.foreign.ValueLayout.*; +import static java.util.stream.Collectors.joining; +import static org.testng.Assert.*; +import static java.util.Objects.requireNonNull; + +@Test +public class TestMemoryInspection { + + private static final String EXPECT_ADDRESS = "0x" + "00".repeat((int) ValueLayout.ADDRESS.byteSize()); + + @Test + public void valueLayouts() { + + record TestInput(ValueLayout layout, String stringValue) { + } + + List.of( + new TestInput(ValueLayout.JAVA_BYTE, "0"), + new TestInput(ValueLayout.JAVA_SHORT, "0"), + new TestInput(ValueLayout.JAVA_INT, "0"), + new TestInput(ValueLayout.JAVA_LONG, "0"), + new TestInput(ValueLayout.JAVA_FLOAT, "0.0"), + new TestInput(ValueLayout.JAVA_DOUBLE, "0.0"), + new TestInput(ValueLayout.JAVA_CHAR, "" + (char) 0), + new TestInput(JAVA_BOOLEAN, "false"), + new TestInput(ValueLayout.ADDRESS, EXPECT_ADDRESS) + ).forEach(ti -> { + var expect = ti.layout() + "=" + ti.stringValue(); + var actual = testWithFreshMemorySegment(ti.layout().byteSize(), s -> jdk.internal.foreign.MemoryInspection.inspect(s, ti.layout(), jdk.internal.foreign.MemoryInspection.standardRenderer())) + .collect(joining(System.lineSeparator())); + assertEquals(actual, expect); + }); + } + + @Test + public void point() { + + var expect = platformLineSeparated(""" + Point { + x=1, + y=2 + }"""); + + var actual = testWithFreshMemorySegment(Integer.BYTES * 2, segment -> { + final Point point = new Point(segment); + point.x(1); + point.y(2); + return jdk.internal.foreign.MemoryInspection.inspect(segment, Point.LAYOUT, jdk.internal.foreign.MemoryInspection.standardRenderer()) + .collect(joining(System.lineSeparator())); + }); + + assertEquals(actual, expect); + } + + @Test + public void pointCustomRenderer() { + + var expect = platformLineSeparated(""" + Point { + x=0x0001, + y=0x0002 + }"""); + + var actual = testWithFreshMemorySegment(Integer.BYTES * 2, segment -> { + final Point point = new Point(segment); + point.x(1); + point.y(2); + return MemoryInspection.inspect(segment, Point.LAYOUT, new BiFunction() { + + @Override + public String apply(ValueLayout layout, Object o) { + return String.format("0x%04x", (int)o); + } + }) + .collect(joining(System.lineSeparator())); + }); + + assertEquals(actual, expect); + } + + @Test + public void standardCustomRenderer() { + + MemoryLayout layout = MemoryLayout.structLayout( + // These are in bit alignment order (descending) for all platforms + // in order for each element to be aligned to its type's bit alignment. + Stream.of( + JAVA_LONG, + JAVA_DOUBLE, + ADDRESS, + JAVA_INT, + JAVA_FLOAT, + JAVA_SHORT, + JAVA_CHAR, + JAVA_BOOLEAN, + JAVA_BYTE + ) + .map(vl -> vl.withName(vl.carrier().getSimpleName())) + .toArray(MemoryLayout[]::new) + ).withName("struct"); + + System.out.println("layout = " + layout); + var expect = platformLineSeparated(""" + struct { + long=0, + double=0.0, + MemorySegment=$1, + int=0, + float=0.0, + short=0, + char=\u0000, + boolean=false, + byte=0 + }""").replace("$1", EXPECT_ADDRESS); + + + var actual = testWithFreshMemorySegment(layout.byteSize(), segment -> + jdk.internal.foreign.MemoryInspection.inspect(segment, layout, jdk.internal.foreign.MemoryInspection.standardRenderer())) + .collect(joining(System.lineSeparator())); + + assertEquals(actual, expect); + } + + + @Test + public void sequence() { + final int arraySize = 4; + var sequenceLayout = MemoryLayout.sequenceLayout(arraySize, + MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("x"), + ValueLayout.JAVA_INT.withName("y") + ).withName("Point") + ).withName("PointArrayOfElements"); + + var xh = sequenceLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("x")); + var yh = sequenceLayout.varHandle(PathElement.sequenceElement(), PathElement.groupElement("y")); + + var expect = platformLineSeparated(""" + PointArrayOfElements [ + Point { + x=1, + y=0 + }, + Point { + x=1, + y=1 + }, + Point { + x=1, + y=2 + }, + Point { + x=1, + y=3 + } + ]"""); + var actual = testWithFreshMemorySegment(Integer.BYTES * 2 * arraySize, segment -> { + for (long i = 0; i < sequenceLayout.elementCount(); i++) { + xh.set(segment, 0L, i, 1); + yh.set(segment, 0L, i, (int) i); + } + + return jdk.internal.foreign.MemoryInspection.inspect(segment, sequenceLayout, jdk.internal.foreign.MemoryInspection.standardRenderer()) + .collect(joining(System.lineSeparator()));} + ); + assertEquals(actual, expect); + } + + + @Test + public void union() { + var u0 = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("x"), + ValueLayout.JAVA_INT.withName("y"), + MemoryLayout.paddingLayout(Integer.BYTES) + ).withName("Point"); + + var u1 = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("x"), + ValueLayout.JAVA_INT.withName("y"), + ValueLayout.JAVA_INT.withName("z") + ).withName("3D-Point"); + + var union = MemoryLayout.unionLayout(u0, u1).withName("Union"); + + var expect = platformLineSeparated(""" + Union { + Point { + x=1, + y=2, + 4 padding bytes + }| + 3D-Point { + x=1, + y=2, + z=3 + } + }"""); + var actual = testWithFreshMemorySegment(Integer.BYTES * 3, segment -> { + u0.varHandle(PathElement.groupElement("x")).set(segment, 0L, 1); + u1.varHandle(PathElement.groupElement("y")).set(segment, 0L, 2); + u1.varHandle(PathElement.groupElement("z")).set(segment, 0L, 3); + return jdk.internal.foreign.MemoryInspection.inspect(segment, union, jdk.internal.foreign.MemoryInspection.standardRenderer()) + .collect(joining(System.lineSeparator())); + }); + + assertEquals(actual, expect); + } + + static final class Point { + + static final MemoryLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("x"), + ValueLayout.JAVA_INT.withName("y") + ).withName("Point"); + + static final VarHandle xVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("x")); + static final VarHandle yVH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("y")); + + private final MemorySegment memorySegment; + + Point(MemorySegment memorySegment) { + this.memorySegment = requireNonNull(memorySegment); + } + + int x() { + return (int) xVH.get(memorySegment, 0L); + } + + int y() { + return (int) yVH.get(memorySegment, 0L); + } + + void x(int x) { + xVH.set(memorySegment, 0L, x); + } + + void y(int y) { + yVH.set(memorySegment, 0L, y); + } + + @Override + public String toString() { + return "Point {x=" + x() + ", y=" + y() + "}"; + } + } + + private static String platformLineSeparated(String s) { + return s.lines() + .collect(joining(System.lineSeparator())); + } + + private static T testWithFreshMemorySegment(long size, + Function mapper) { + try (final Arena arena = Arena.ofConfined()) { + var segment = arena.allocate(size); + return mapper.apply(segment); + } + } + +} diff a/test/jdk/java/foreign/TestMemorySession.java b/test/jdk/java/foreign/TestMemorySession.java --- a/test/jdk/java/foreign/TestMemorySession.java +++ b/test/jdk/java/foreign/TestMemorySession.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules java.base/jdk.internal.foreign * @run testng/othervm TestMemorySession */ import java.lang.foreign.Arena; diff a/test/jdk/java/foreign/TestMismatch.java b/test/jdk/java/foreign/TestMismatch.java --- a/test/jdk/java/foreign/TestMismatch.java +++ b/test/jdk/java/foreign/TestMismatch.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestMismatch */ import java.lang.foreign.Arena; import java.util.ArrayList; diff a/test/jdk/java/foreign/TestNULLAddress.java b/test/jdk/java/foreign/TestNULLAddress.java --- a/test/jdk/java/foreign/TestNULLAddress.java +++ b/test/jdk/java/foreign/TestNULLAddress.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm * --enable-native-access=ALL-UNNAMED * TestNULLAddress */ diff a/test/jdk/java/foreign/TestNative.java b/test/jdk/java/foreign/TestNative.java --- a/test/jdk/java/foreign/TestNative.java +++ b/test/jdk/java/foreign/TestNative.java @@ -22,12 +22,10 @@ * */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestNative */ import java.lang.foreign.*; import java.lang.foreign.MemoryLayout.PathElement; @@ -206,38 +204,38 @@ } @DataProvider(name = "nativeAccessOps") public Object[][] nativeAccessOps() { Consumer byteInitializer = - (base) -> initBytes(base, bytes, (addr, pos) -> byteHandle.set(addr, pos, (byte)(long)pos)); + (base) -> initBytes(base, bytes, (addr, pos) -> byteHandle.set(addr, 0L, pos, (byte)(long)pos)); Consumer charInitializer = - (base) -> initBytes(base, chars, (addr, pos) -> charHandle.set(addr, pos, (char)(long)pos)); + (base) -> initBytes(base, chars, (addr, pos) -> charHandle.set(addr, 0L, pos, (char)(long)pos)); Consumer shortInitializer = - (base) -> initBytes(base, shorts, (addr, pos) -> shortHandle.set(addr, pos, (short)(long)pos)); + (base) -> initBytes(base, shorts, (addr, pos) -> shortHandle.set(addr, 0L, pos, (short)(long)pos)); Consumer intInitializer = - (base) -> initBytes(base, ints, (addr, pos) -> intHandle.set(addr, pos, (int)(long)pos)); + (base) -> initBytes(base, ints, (addr, pos) -> intHandle.set(addr, 0L, pos, (int)(long)pos)); Consumer floatInitializer = - (base) -> initBytes(base, floats, (addr, pos) -> floatHandle.set(addr, pos, (float)(long)pos)); + (base) -> initBytes(base, floats, (addr, pos) -> floatHandle.set(addr, 0L, pos, (float)(long)pos)); Consumer longInitializer = - (base) -> initBytes(base, longs, (addr, pos) -> longHandle.set(addr, pos, (long)pos)); + (base) -> initBytes(base, longs, (addr, pos) -> longHandle.set(addr, 0L, pos, (long)pos)); Consumer doubleInitializer = - (base) -> initBytes(base, doubles, (addr, pos) -> doubleHandle.set(addr, pos, (double)(long)pos)); + (base) -> initBytes(base, doubles, (addr, pos) -> doubleHandle.set(addr, 0L, pos, (double)(long)pos)); Consumer byteChecker = - (base) -> checkBytes(base, bytes, byteHandle::get, bb -> bb, TestNative::getByteBuffer, TestNative::getByteRaw); + (base) -> checkBytes(base, bytes, (addr, pos) -> byteHandle.get(addr, 0L, pos), bb -> bb, TestNative::getByteBuffer, TestNative::getByteRaw); Consumer charChecker = - (base) -> checkBytes(base, chars, charHandle::get, ByteBuffer::asCharBuffer, TestNative::getCharBuffer, TestNative::getCharRaw); + (base) -> checkBytes(base, chars, (addr, pos) -> charHandle.get(addr, 0L, pos), ByteBuffer::asCharBuffer, TestNative::getCharBuffer, TestNative::getCharRaw); Consumer shortChecker = - (base) -> checkBytes(base, shorts, shortHandle::get, ByteBuffer::asShortBuffer, TestNative::getShortBuffer, TestNative::getShortRaw); + (base) -> checkBytes(base, shorts, (addr, pos) -> shortHandle.get(addr, 0L, pos), ByteBuffer::asShortBuffer, TestNative::getShortBuffer, TestNative::getShortRaw); Consumer intChecker = - (base) -> checkBytes(base, ints, intHandle::get, ByteBuffer::asIntBuffer, TestNative::getIntBuffer, TestNative::getIntRaw); + (base) -> checkBytes(base, ints, (addr, pos) -> intHandle.get(addr, 0L, pos), ByteBuffer::asIntBuffer, TestNative::getIntBuffer, TestNative::getIntRaw); Consumer floatChecker = - (base) -> checkBytes(base, floats, floatHandle::get, ByteBuffer::asFloatBuffer, TestNative::getFloatBuffer, TestNative::getFloatRaw); + (base) -> checkBytes(base, floats, (addr, pos) -> floatHandle.get(addr, 0L, pos), ByteBuffer::asFloatBuffer, TestNative::getFloatBuffer, TestNative::getFloatRaw); Consumer longChecker = - (base) -> checkBytes(base, longs, longHandle::get, ByteBuffer::asLongBuffer, TestNative::getLongBuffer, TestNative::getLongRaw); + (base) -> checkBytes(base, longs, (addr, pos) -> longHandle.get(addr, 0L, pos), ByteBuffer::asLongBuffer, TestNative::getLongBuffer, TestNative::getLongRaw); Consumer doubleChecker = - (base) -> checkBytes(base, doubles, doubleHandle::get, ByteBuffer::asDoubleBuffer, TestNative::getDoubleBuffer, TestNative::getDoubleRaw); + (base) -> checkBytes(base, doubles, (addr, pos) -> doubleHandle.get(addr, 0L, pos), ByteBuffer::asDoubleBuffer, TestNative::getDoubleBuffer, TestNative::getDoubleRaw); return new Object[][]{ {byteChecker, byteInitializer, bytes}, {charChecker, charInitializer, chars}, {shortChecker, shortInitializer, shorts}, diff a/test/jdk/java/foreign/TestNulls.java b/test/jdk/java/foreign/TestNulls.java --- a/test/jdk/java/foreign/TestNulls.java +++ b/test/jdk/java/foreign/TestNulls.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.ref * @run testng/othervm * --enable-native-access=ALL-UNNAMED * TestNulls */ @@ -142,11 +140,11 @@ addDefaultMapping(String.class, "Hello!"); addDefaultMapping(Constable.class, "Hello!"); addDefaultMapping(Class.class, String.class); addDefaultMapping(Runnable.class, () -> {}); addDefaultMapping(Object.class, new Object()); - addDefaultMapping(VarHandle.class, MethodHandles.memorySegmentViewVarHandle(JAVA_INT)); + addDefaultMapping(VarHandle.class, JAVA_INT.varHandle()); addDefaultMapping(MethodHandle.class, MethodHandles.identity(int.class)); addDefaultMapping(List.class, List.of()); addDefaultMapping(Charset.class, Charset.defaultCharset()); addDefaultMapping(Consumer.class, x -> {}); addDefaultMapping(MethodType.class, MethodType.methodType(void.class)); diff a/test/jdk/java/foreign/TestOfBufferIssue.java b/test/jdk/java/foreign/TestOfBufferIssue.java --- a/test/jdk/java/foreign/TestOfBufferIssue.java +++ b/test/jdk/java/foreign/TestOfBufferIssue.java @@ -31,11 +31,10 @@ /* * @test * @bug 8294621 * @summary test that StringCharBuffer is not accepted by MemorySegment::ofBuffer - * @enablePreview * @run testng TestOfBufferIssue */ public class TestOfBufferIssue { diff a/test/jdk/java/foreign/TestReshape.java b/test/jdk/java/foreign/TestReshape.java --- a/test/jdk/java/foreign/TestReshape.java +++ b/test/jdk/java/foreign/TestReshape.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestReshape */ import java.lang.foreign.MemoryLayout; import java.lang.foreign.SequenceLayout; diff a/test/jdk/java/foreign/TestScope.java b/test/jdk/java/foreign/TestScope.java --- /dev/null +++ b/test/jdk/java/foreign/TestScope.java @@ -0,0 +1,122 @@ +/* + * 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. + */ + +/* + * @test + * @run testng/othervm --enable-native-access=ALL-UNNAMED TestScope + */ + +import org.testng.annotations.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import static org.testng.Assert.*; + +public class TestScope { + + static { + System.loadLibrary("LookupTest"); + } + + @Test + public void testDifferentArrayScope() { + MemorySegment.Scope scope1 = MemorySegment.ofArray(new byte[10]).scope(); + MemorySegment.Scope scope2 = MemorySegment.ofArray(new byte[10]).scope(); + assertNotEquals(scope1, scope2); + } + + @Test + public void testDifferentBufferScope() { + MemorySegment.Scope scope1 = MemorySegment.ofBuffer(ByteBuffer.allocateDirect(10)).scope(); + MemorySegment.Scope scope2 = MemorySegment.ofBuffer(ByteBuffer.allocateDirect(10)).scope(); + assertNotEquals(scope1, scope2); + } + + @Test + public void testDifferentArenaScope() { + MemorySegment.Scope scope1 = Arena.ofAuto().allocate(10).scope(); + MemorySegment.Scope scope2 = Arena.ofAuto().allocate(10).scope(); + assertNotEquals(scope1, scope2); + } + + @Test + public void testSameArrayScope() { + byte[] arr = new byte[10]; + assertEquals(MemorySegment.ofArray(arr).scope(), MemorySegment.ofArray(arr).scope()); + ByteBuffer buf = ByteBuffer.wrap(arr); + assertEquals(MemorySegment.ofArray(arr).scope(), MemorySegment.ofBuffer(buf).scope()); + testDerivedBufferScope(MemorySegment.ofArray(arr)); + } + + @Test + public void testSameBufferScope() { + ByteBuffer buf = ByteBuffer.allocateDirect(10); + assertEquals(MemorySegment.ofBuffer(buf).scope(), MemorySegment.ofBuffer(buf).scope()); + testDerivedBufferScope(MemorySegment.ofBuffer(buf)); + } + + @Test + public void testSameArenaScope() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment segment1 = arena.allocate(10); + MemorySegment segment2 = arena.allocate(10); + assertEquals(segment1.scope(), segment2.scope()); + testDerivedBufferScope(segment1); + } + } + + @Test + public void testSameNativeScope() { + MemorySegment segment1 = MemorySegment.ofAddress(42); + MemorySegment segment2 = MemorySegment.ofAddress(43); + assertEquals(segment1.scope(), segment2.scope()); + assertEquals(segment1.scope(), segment2.reinterpret(10).scope()); + assertNotEquals(segment1.scope(), Arena.global().scope()); + testDerivedBufferScope(segment1.reinterpret(10)); + } + + @Test + public void testSameLookupScope() { + SymbolLookup loaderLookup = SymbolLookup.loaderLookup(); + MemorySegment segment1 = loaderLookup.find("f").get(); + MemorySegment segment2 = loaderLookup.find("c").get(); + assertEquals(segment1.scope(), segment2.scope()); + testDerivedBufferScope(segment1.reinterpret(10)); + } + + void testDerivedBufferScope(MemorySegment segment) { + ByteBuffer buffer = segment.asByteBuffer(); + MemorySegment.Scope expectedScope = segment.scope(); + assertEquals(MemorySegment.ofBuffer(buffer).scope(), expectedScope); + // buffer slices should have same scope + ByteBuffer slice = buffer.slice(0, 2); + assertEquals(expectedScope, MemorySegment.ofBuffer(slice).scope()); + // buffer views should have same scope + IntBuffer view = buffer.asIntBuffer(); + assertEquals(expectedScope, MemorySegment.ofBuffer(view).scope()); + } +} diff a/test/jdk/java/foreign/TestScopedOperations.java b/test/jdk/java/foreign/TestScopedOperations.java --- a/test/jdk/java/foreign/TestScopedOperations.java +++ b/test/jdk/java/foreign/TestScopedOperations.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestScopedOperations */ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -120,25 +118,25 @@ ScopedOperation.ofSegment(s -> s.fill((byte) 0), "MemorySegment::fill"); // allocator operations ScopedOperation.ofScope(a -> a.allocate(1), "Arena::allocate/size"); ScopedOperation.ofScope(a -> a.allocate(1, 1), "Arena::allocate/size/align"); ScopedOperation.ofScope(a -> a.allocate(JAVA_BYTE), "Arena::allocate/layout"); - ScopedOperation.ofScope(a -> a.allocate(JAVA_BYTE, (byte) 0), "Arena::allocate/byte"); - ScopedOperation.ofScope(a -> a.allocate(ValueLayout.JAVA_CHAR, (char) 0), "Arena::allocate/char"); - ScopedOperation.ofScope(a -> a.allocate(ValueLayout.JAVA_SHORT, (short) 0), "Arena::allocate/short"); - ScopedOperation.ofScope(a -> a.allocate(ValueLayout.JAVA_INT, 0), "Arena::allocate/int"); - ScopedOperation.ofScope(a -> a.allocate(ValueLayout.JAVA_FLOAT, 0f), "Arena::allocate/float"); - ScopedOperation.ofScope(a -> a.allocate(ValueLayout.JAVA_LONG, 0L), "Arena::allocate/long"); - ScopedOperation.ofScope(a -> a.allocate(ValueLayout.JAVA_DOUBLE, 0d), "Arena::allocate/double"); - ScopedOperation.ofScope(a -> a.allocateArray(JAVA_BYTE, 1L), "Arena::allocateArray/size"); - ScopedOperation.ofScope(a -> a.allocateArray(JAVA_BYTE, new byte[]{0}), "Arena::allocateArray/byte"); - ScopedOperation.ofScope(a -> a.allocateArray(ValueLayout.JAVA_CHAR, new char[]{0}), "Arena::allocateArray/char"); - ScopedOperation.ofScope(a -> a.allocateArray(ValueLayout.JAVA_SHORT, new short[]{0}), "Arena::allocateArray/short"); - ScopedOperation.ofScope(a -> a.allocateArray(ValueLayout.JAVA_INT, new int[]{0}), "Arena::allocateArray/int"); - ScopedOperation.ofScope(a -> a.allocateArray(ValueLayout.JAVA_FLOAT, new float[]{0}), "Arena::allocateArray/float"); - ScopedOperation.ofScope(a -> a.allocateArray(ValueLayout.JAVA_LONG, new long[]{0}), "Arena::allocateArray/long"); - ScopedOperation.ofScope(a -> a.allocateArray(ValueLayout.JAVA_DOUBLE, new double[]{0}), "Arena::allocateArray/double"); + ScopedOperation.ofScope(a -> a.allocateFrom(JAVA_BYTE, (byte) 0), "Arena::allocate/byte"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_CHAR, (char) 0), "Arena::allocate/char"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_SHORT, (short) 0), "Arena::allocate/short"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_INT, 0), "Arena::allocate/int"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_FLOAT, 0f), "Arena::allocate/float"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_LONG, 0L), "Arena::allocate/long"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_DOUBLE, 0d), "Arena::allocate/double"); + ScopedOperation.ofScope(a -> a.allocate(JAVA_BYTE, 1L), "Arena::allocate/size"); + ScopedOperation.ofScope(a -> a.allocateFrom(JAVA_BYTE, new byte[]{0}), "Arena::allocateFrom/byte"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_CHAR, new char[]{0}), "Arena::allocateFrom/char"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_SHORT, new short[]{0}), "Arena::allocateFrom/short"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_INT, new int[]{0}), "Arena::allocateFrom/int"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_FLOAT, new float[]{0}), "Arena::allocateFrom/float"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_LONG, new long[]{0}), "Arena::allocateFrom/long"); + ScopedOperation.ofScope(a -> a.allocateFrom(ValueLayout.JAVA_DOUBLE, new double[]{0}), "Arena::allocateFrom/double"); }; @DataProvider(name = "scopedOperations") static Object[][] scopedOperations() { return scopedOperations.stream().map(op -> new Object[] { op.name, op }).toArray(Object[][]::new); diff a/test/jdk/java/foreign/TestSegmentAllocators.java b/test/jdk/java/foreign/TestSegmentAllocators.java --- a/test/jdk/java/foreign/TestSegmentAllocators.java +++ b/test/jdk/java/foreign/TestSegmentAllocators.java @@ -22,18 +22,16 @@ * */ /* * @test - * @enablePreview * @modules java.base/jdk.internal.foreign * @run testng/othervm TestSegmentAllocators */ import java.lang.foreign.*; -import jdk.internal.foreign.MappedMemorySegmentImpl; import jdk.internal.foreign.NativeMemorySegmentImpl; import org.testng.annotations.*; import java.lang.foreign.Arena; import java.lang.invoke.VarHandle; @@ -80,11 +78,11 @@ for (int i = 0; i < elems; i++) { MemorySegment address = allocationFunction.allocate(allocator, alignedLayout, value); assertEquals(address.byteSize(), alignedLayout.byteSize()); addressList.add(address); VarHandle handle = handleFactory.apply(alignedLayout); - assertEquals(value, handle.get(address)); + assertEquals(value, handle.get(address, 0L)); } boolean isBound = allocationFactory.isBound(); try { allocationFunction.allocate(allocator, alignedLayout, value); assertFalse(isBound); @@ -146,16 +144,16 @@ allocator.allocate(1, 3); } @Test(dataProvider = "allocators", expectedExceptions = IllegalArgumentException.class) public void testBadAllocationArrayNegSize(SegmentAllocator allocator) { - allocator.allocateArray(ValueLayout.JAVA_BYTE, -1); + allocator.allocate(ValueLayout.JAVA_BYTE, -1); } @Test(dataProvider = "allocators", expectedExceptions = IllegalArgumentException.class) public void testBadAllocationArrayOverflow(SegmentAllocator allocator) { - allocator.allocateArray(ValueLayout.JAVA_LONG, Long.MAX_VALUE); + allocator.allocate(ValueLayout.JAVA_LONG, Long.MAX_VALUE); } @Test(expectedExceptions = OutOfMemoryError.class) public void testBadArenaNullReturn() { try (Arena arena = Arena.ofConfined()) { @@ -167,46 +165,45 @@ public void testArrayAllocateDelegation() { AtomicInteger calls = new AtomicInteger(); SegmentAllocator allocator = new SegmentAllocator() { @Override public MemorySegment allocate(long bytesSize, long byteAlignment) { - return null; + return MemorySegment.NULL; } @Override - public MemorySegment allocateArray(MemoryLayout elementLayout, long count) { + public MemorySegment allocateFrom(ValueLayout elementLayout, MemorySegment source, ValueLayout sourceElementLayout, long sourceOffset, long elementCount) { calls.incrementAndGet(); - return null; - }; + return MemorySegment.NULL; + } }; - allocator.allocateArray(ValueLayout.JAVA_BYTE); - allocator.allocateArray(ValueLayout.JAVA_SHORT); - allocator.allocateArray(ValueLayout.JAVA_CHAR); - allocator.allocateArray(ValueLayout.JAVA_INT); - allocator.allocateArray(ValueLayout.JAVA_FLOAT); - allocator.allocateArray(ValueLayout.JAVA_LONG); - allocator.allocateArray(ValueLayout.JAVA_DOUBLE); + allocator.allocateFrom(ValueLayout.JAVA_BYTE); + allocator.allocateFrom(ValueLayout.JAVA_SHORT); + allocator.allocateFrom(ValueLayout.JAVA_CHAR); + allocator.allocateFrom(ValueLayout.JAVA_INT); + allocator.allocateFrom(ValueLayout.JAVA_FLOAT); + allocator.allocateFrom(ValueLayout.JAVA_LONG); + allocator.allocateFrom(ValueLayout.JAVA_DOUBLE); assertEquals(calls.get(), 7); } @Test public void testStringAllocateDelegation() { AtomicInteger calls = new AtomicInteger(); SegmentAllocator allocator = new SegmentAllocator() { @Override - public MemorySegment allocate(long byteSize, long byteAlignment) { return Arena.ofAuto().allocate(byteSize, byteAlignment); } @Override public MemorySegment allocate(long size) { calls.incrementAndGet(); return allocate(size, 1); }; }; - allocator.allocateUtf8String("Hello"); + allocator.allocateFrom("Hello"); assertEquals(calls.get(), 1); } @Test(dataProvider = "arrayAllocations") @@ -250,106 +247,106 @@ @DataProvider(name = "scalarAllocations") static Object[][] scalarAllocations() { List scalarAllocations = new ArrayList<>(); for (AllocationFactory factory : AllocationFactory.values()) { scalarAllocations.add(new Object[] { (byte)42, factory, ValueLayout.JAVA_BYTE, - (AllocationFunction.OfByte) SegmentAllocator::allocate, + (AllocationFunction.OfByte) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { (short)42, factory, ValueLayout.JAVA_SHORT.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfShort) SegmentAllocator::allocate, + (AllocationFunction.OfShort) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { (char)42, factory, ValueLayout.JAVA_CHAR.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfChar) SegmentAllocator::allocate, + (AllocationFunction.OfChar) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42, factory, ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfInt) SegmentAllocator::allocate, + (AllocationFunction.OfInt) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42f, factory, ValueLayout.JAVA_FLOAT.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfFloat) SegmentAllocator::allocate, + (AllocationFunction.OfFloat) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42L, factory, ValueLayout.JAVA_LONG.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfLong) SegmentAllocator::allocate, + (AllocationFunction.OfLong) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42d, factory, ValueLayout.JAVA_DOUBLE.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfDouble) SegmentAllocator::allocate, + (AllocationFunction.OfDouble) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { MemorySegment.ofAddress(42), factory, ValueLayout.ADDRESS.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfAddress) SegmentAllocator::allocate, + (AllocationFunction.OfAddress) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { (short)42, factory, ValueLayout.JAVA_SHORT.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfShort) SegmentAllocator::allocate, + (AllocationFunction.OfShort) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { (char)42, factory, ValueLayout.JAVA_CHAR.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfChar) SegmentAllocator::allocate, + (AllocationFunction.OfChar) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42, factory, ValueLayout.JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfInt) SegmentAllocator::allocate, + (AllocationFunction.OfInt) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42f, factory, ValueLayout.JAVA_FLOAT.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfFloat) SegmentAllocator::allocate, + (AllocationFunction.OfFloat) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42L, factory, ValueLayout.JAVA_LONG.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfLong) SegmentAllocator::allocate, + (AllocationFunction.OfLong) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { 42d, factory, ValueLayout.JAVA_DOUBLE.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfDouble) SegmentAllocator::allocate, + (AllocationFunction.OfDouble) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); scalarAllocations.add(new Object[] { MemorySegment.ofAddress(42), factory, ValueLayout.ADDRESS.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfAddress) SegmentAllocator::allocate, + (AllocationFunction.OfAddress) SegmentAllocator::allocateFrom, (Function)l -> l.varHandle() }); } return scalarAllocations.toArray(Object[][]::new); } @DataProvider(name = "arrayAllocations") static Object[][] arrayAllocations() { List arrayAllocations = new ArrayList<>(); for (AllocationFactory factory : AllocationFactory.values()) { arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_BYTE, - (AllocationFunction.OfByteArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfByteArray) SegmentAllocator::allocateFrom, ToArrayHelper.toByteArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_CHAR.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfCharArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfCharArray) SegmentAllocator::allocateFrom, ToArrayHelper.toCharArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_SHORT.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfShortArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfShortArray) SegmentAllocator::allocateFrom, ToArrayHelper.toShortArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_INT.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfIntArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfIntArray) SegmentAllocator::allocateFrom, ToArrayHelper.toIntArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_FLOAT.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfFloatArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfFloatArray) SegmentAllocator::allocateFrom, ToArrayHelper.toFloatArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_LONG.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfLongArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfLongArray) SegmentAllocator::allocateFrom, ToArrayHelper.toLongArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_DOUBLE.withOrder(ByteOrder.LITTLE_ENDIAN), - (AllocationFunction.OfDoubleArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfDoubleArray) SegmentAllocator::allocateFrom, ToArrayHelper.toDoubleArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_CHAR.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfCharArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfCharArray) SegmentAllocator::allocateFrom, ToArrayHelper.toCharArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_SHORT.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfShortArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfShortArray) SegmentAllocator::allocateFrom, ToArrayHelper.toShortArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_INT.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfIntArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfIntArray) SegmentAllocator::allocateFrom, ToArrayHelper.toIntArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_FLOAT.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfFloatArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfFloatArray) SegmentAllocator::allocateFrom, ToArrayHelper.toFloatArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_LONG.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfLongArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfLongArray) SegmentAllocator::allocateFrom, ToArrayHelper.toLongArray }); arrayAllocations.add(new Object[] { factory, ValueLayout.JAVA_DOUBLE.withOrder(ByteOrder.BIG_ENDIAN), - (AllocationFunction.OfDoubleArray) SegmentAllocator::allocateArray, + (AllocationFunction.OfDoubleArray) SegmentAllocator::allocateFrom, ToArrayHelper.toDoubleArray }); }; return arrayAllocations.toArray(Object[][]::new); } diff a/test/jdk/java/foreign/TestSegmentCopy.java b/test/jdk/java/foreign/TestSegmentCopy.java --- a/test/jdk/java/foreign/TestSegmentCopy.java +++ b/test/jdk/java/foreign/TestSegmentCopy.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @run testng TestSegmentCopy */ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -169,11 +168,11 @@ long size() { return layout.byteSize(); } VarHandle handle() { - return MethodHandles.memorySegmentViewVarHandle(layout); + return layout.varHandle(); } void set(MemorySegment segment, long offset, int index, int val) { handle().set(segment, offset + (index * size()), valueConverter.apply(val)); } diff a/test/jdk/java/foreign/TestSegmentOffset.java b/test/jdk/java/foreign/TestSegmentOffset.java --- a/test/jdk/java/foreign/TestSegmentOffset.java +++ b/test/jdk/java/foreign/TestSegmentOffset.java @@ -21,17 +21,17 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestSegmentOffset */ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import org.testng.SkipException; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.List; @@ -42,31 +42,25 @@ public class TestSegmentOffset { @Test(dataProvider = "slices") public void testOffset(SegmentSlice s1, SegmentSlice s2) { + if (s1.kind != s2.kind) { + throw new SkipException("Slices of different segment kinds"); + } if (s1.contains(s2)) { // check that a segment and its overlapping segment point to same elements - long offset = s1.segment.segmentOffset(s2.segment); + long offset = s1.offset(s2); for (int i = 0; i < s2.size(); i++) { out.format("testOffset s1:%s, s2:%s, offset:%d, i:%s\n", s1, s2, offset, i); byte expected = s2.segment.get(JAVA_BYTE, i); byte found = s1.segment.get(JAVA_BYTE, i + offset); assertEquals(found, expected); } - } else if (s1.kind != s2.kind) { - // check that offset from s1 to s2 fails - try { - long offset = s1.segment.segmentOffset(s2.segment); - out.format("testOffset s1:%s, s2:%s, offset:%d\n", s1, s2, offset); - fail("offset unexpectedly passed!"); - } catch (UnsupportedOperationException ex) { - assertTrue(ex.getMessage().contains("Cannot compute offset from native to heap (or vice versa).")); - } } else if (!s2.contains(s1)) { // disjoint segments - check that offset is out of bounds - long offset = s1.segment.segmentOffset(s2.segment); + long offset = s1.offset(s2); for (int i = 0; i < s2.size(); i++) { out.format("testOffset s1:%s, s2:%s, offset:%d, i:%s\n", s1, s2, offset, i); s2.segment.get(JAVA_BYTE, i); try { s1.segment.get(JAVA_BYTE, i + offset); @@ -114,10 +108,14 @@ } int size() { return last - first + 1; } + + long offset(SegmentSlice that) { + return that.segment.address() - segment.address(); + } } @DataProvider(name = "slices") static Object[][] slices() { int[] sizes = { 16, 8, 4, 2, 1 }; diff a/test/jdk/java/foreign/TestSegmentOverlap.java b/test/jdk/java/foreign/TestSegmentOverlap.java --- a/test/jdk/java/foreign/TestSegmentOverlap.java +++ b/test/jdk/java/foreign/TestSegmentOverlap.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng/othervm TestSegmentOverlap */ import java.io.File; import java.io.IOException; diff a/test/jdk/java/foreign/TestSegments.java b/test/jdk/java/foreign/TestSegments.java --- a/test/jdk/java/foreign/TestSegments.java +++ b/test/jdk/java/foreign/TestSegments.java @@ -21,12 +21,11 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" + * @requires vm.bits == 64 * @run testng/othervm -Xmx4G -XX:MaxDirectMemorySize=1M --enable-native-access=ALL-UNNAMED TestSegments */ import java.lang.foreign.*; @@ -82,22 +81,22 @@ MemorySegment segment = scope.allocate(1024L * 1024 * 8 * 2, 1); // 2M } @Test public void testNativeSegmentIsZeroed() { - VarHandle byteHandle = ValueLayout.JAVA_BYTE.arrayElementVarHandle(); + VarHandle byteHandle = ValueLayout.JAVA_BYTE.varHandle(); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(1000, 1); for (long i = 0 ; i < segment.byteSize() ; i++) { assertEquals(0, (byte)byteHandle.get(segment, i)); } } } @Test public void testSlices() { - VarHandle byteHandle = ValueLayout.JAVA_BYTE.arrayElementVarHandle(); + VarHandle byteHandle = ValueLayout.JAVA_BYTE.varHandle(); try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(10, 1); //init for (byte i = 0 ; i < segment.byteSize() ; i++) { byteHandle.set(segment, (long)i, i); @@ -261,11 +260,11 @@ return l.stream().map(s -> new Object[] { s }).toArray(Object[][]::new); } @Test(dataProvider = "segmentFactories") public void testFill(Supplier segmentSupplier) { - VarHandle byteHandle = ValueLayout.JAVA_BYTE.arrayElementVarHandle(); + VarHandle byteHandle = ValueLayout.JAVA_BYTE.varHandle(); for (byte value : new byte[] {(byte) 0xFF, (byte) 0x00, (byte) 0x45}) { MemorySegment segment = segmentSupplier.get(); segment.fill(value); for (long l = 0; l < segment.byteSize(); l++) { diff a/test/jdk/java/foreign/TestSharedAccess.java b/test/jdk/java/foreign/TestSharedAccess.java --- a/test/jdk/java/foreign/TestSharedAccess.java +++ b/test/jdk/java/foreign/TestSharedAccess.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @run testng/othervm --enable-native-access=ALL-UNNAMED TestSharedAccess */ import java.lang.foreign.*; import java.lang.invoke.VarHandle; @@ -149,12 +148,12 @@ b.countDown(); r.get(); } static int getInt(MemorySegment base) { - return (int)intHandle.getVolatile(base); + return (int)intHandle.getVolatile(base, 0L); } static void setInt(MemorySegment base, int value) { - intHandle.setVolatile(base, value); + intHandle.setVolatile(base, 0L, value); } } diff a/test/jdk/java/foreign/TestSlices.java b/test/jdk/java/foreign/TestSlices.java --- a/test/jdk/java/foreign/TestSlices.java +++ b/test/jdk/java/foreign/TestSlices.java @@ -33,11 +33,10 @@ import org.testng.annotations.*; import static org.testng.Assert.*; /* * @test - * @enablePreview * @run testng/othervm -Xverify:all TestSlices */ public class TestSlices { static MemoryLayout LAYOUT = MemoryLayout.sequenceLayout(2, @@ -51,11 +50,11 @@ try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(LAYOUT);; //init for (long i = 0 ; i < 2 ; i++) { for (long j = 0 ; j < 5 ; j++) { - VH_ALL.set(segment, i, j, (int)j + 1 + ((int)i * 5)); + VH_ALL.set(segment, 0L, i, j, (int)j + 1 + ((int)i * 5)); } } checkSlice(segment, handle, lo, hi, values); } @@ -63,20 +62,20 @@ @Test(dataProvider = "slices") public void testSliceBadIndex(VarHandle handle, int lo, int hi, int[] values) { try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(LAYOUT);; - assertThrows(() -> handle.get(segment, lo, 0)); - assertThrows(() -> handle.get(segment, 0, hi)); + assertThrows(() -> handle.get(segment, 0L, lo, 0)); + assertThrows(() -> handle.get(segment, 0L, 0, hi)); } } static void checkSlice(MemorySegment segment, VarHandle handle, long i_max, long j_max, int... values) { int index = 0; for (long i = 0 ; i < i_max ; i++) { for (long j = 0 ; j < j_max ; j++) { - int x = (int) handle.get(segment, i, j); + int x = (int) handle.get(segment, 0L, i, j); assertEquals(x, values[index++]); } } assertEquals(index, values.length); } diff a/test/jdk/java/foreign/TestSpliterator.java b/test/jdk/java/foreign/TestSpliterator.java --- a/test/jdk/java/foreign/TestSpliterator.java +++ b/test/jdk/java/foreign/TestSpliterator.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @run testng TestSpliterator */ import java.lang.foreign.*; @@ -42,23 +41,21 @@ import static org.testng.Assert.*; public class TestSpliterator { - static final VarHandle INT_HANDLE = ValueLayout.JAVA_INT.arrayElementVarHandle(); - final static int CARRIER_SIZE = 4; @Test(dataProvider = "splits") public void testSum(int size, int threshold) { SequenceLayout layout = MemoryLayout.sequenceLayout(size, ValueLayout.JAVA_INT); //setup try (Arena arena = Arena.ofShared()) { MemorySegment segment = arena.allocate(layout);; for (int i = 0; i < layout.elementCount(); i++) { - INT_HANDLE.set(segment, (long) i, i); + segment.setAtIndex(ValueLayout.JAVA_INT, i, i); } long expected = LongStream.range(0, layout.elementCount()).sum(); //serial long serial = sum(0, segment); assertEquals(serial, expected); @@ -81,11 +78,11 @@ //setup Arena scope = Arena.ofAuto(); MemorySegment segment = scope.allocate(layout); for (int i = 0; i < layout.elementCount(); i++) { - INT_HANDLE.set(segment, (long) i, i); + segment.setAtIndex(ValueLayout.JAVA_INT, i, i); } long expected = LongStream.range(0, layout.elementCount()).sum(); //check that a segment w/o ACQUIRE access mode can still be used from same thread AtomicLong spliteratorSum = new AtomicLong(); @@ -158,18 +155,18 @@ long bigByteAlign = Long.lowestOneBit(segment.address()) << 1; segment.elements(MemoryLayout.sequenceLayout(2, ValueLayout.JAVA_INT.withByteAlignment(bigByteAlign))); } static long sumSingle(long acc, MemorySegment segment) { - return acc + (int)INT_HANDLE.get(segment, 0L); + return acc + segment.getAtIndex(ValueLayout.JAVA_INT, 0); } static long sum(long start, MemorySegment segment) { long sum = start; int length = (int)segment.byteSize(); for (int i = 0 ; i < length / CARRIER_SIZE ; i++) { - sum += (int)INT_HANDLE.get(segment, (long)i); + sum += segment.getAtIndex(ValueLayout.JAVA_INT, i); } return sum; } static class SumSegmentCounted extends CountedCompleter { diff a/test/jdk/java/foreign/TestStringEncoding.java b/test/jdk/java/foreign/TestStringEncoding.java --- a/test/jdk/java/foreign/TestStringEncoding.java +++ b/test/jdk/java/foreign/TestStringEncoding.java @@ -20,45 +20,491 @@ * or visit www.oracle.com if you need additional information or have any * questions. * */ +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 - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" + * @modules java.base/jdk.internal.foreign * @run testng TestStringEncoding */ public class TestStringEncoding { @Test(dataProvider = "strings") - public void testStrings(String testString, int expectedByteLength) { - try (Arena arena = Arena.ofConfined()) { - MemorySegment text = arena.allocateUtf8String(testString); + public void testStrings(String testString) { + for (Charset charset : Charset.availableCharsets().values()) { + if (isStandard(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 expectedByteLength = + testString.getBytes(charset).length + + terminatorSize; + + assertEquals(text.byteSize(), expectedByteLength); + + String roundTrip = text.getString(0, charset); + if (charset.newEncoder().canEncode(testString)) { + assertEquals(roundTrip, testString); + } + } + } + } else { + assertThrows(UnsupportedOperationException.class, () -> Arena.global().allocateFrom(testString, charset)); + } + } + } + + + @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)); + } + } + } + } + } + + @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); + }); + } + + @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); + } + } + + 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); + } - assertEquals(text.byteSize(), expectedByteLength); + @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); + } + } + } + } + } - String roundTrip = text.getUtf8String(0); - assertEquals(roundTrip, testString); + @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", 8 }, - { "", 1 }, - { "X", 2 }, - { "12345", 6 }, - { "yen \u00A5", 7 }, // in UTF-8 2 bytes: 0xC2 0xA5 - { "snowman \u26C4", 12 }, // in UTF-8 three bytes: 0xE2 0x9B 0x84 - { "rainbow \uD83C\uDF08", 13 } // in UTF-8 four bytes: 0xF0 0x9F 0x8C 0x88 + return new Object[][]{ + {"testing"}, + {""}, + {"X"}, + {"12345"}, + {"yen \u00A5"}, + {"snowman \u26C4"}, + {"rainbow \uD83C\uDF08"}, + {"0"}, + {"01"}, + {"012"}, + {"0123"}, + {"01234"}, + {"012345"}, + {"0123456"}, + {"01234567"}, + {"012345678"}, + {"0123456789"} }; } + + 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()) { + 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 a/test/jdk/java/foreign/TestTypeAccess.java b/test/jdk/java/foreign/TestTypeAccess.java --- a/test/jdk/java/foreign/TestTypeAccess.java +++ b/test/jdk/java/foreign/TestTypeAccess.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @run testng TestTypeAccess */ import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -41,39 +40,39 @@ static final VarHandle INT_HANDLE = ValueLayout.JAVA_INT.varHandle(); static final VarHandle ADDR_HANDLE = ValueLayout.ADDRESS.varHandle(); @Test(expectedExceptions=ClassCastException.class) public void testMemoryAddressCoordinateAsString() { - int v = (int)INT_HANDLE.get("string"); + int v = (int)INT_HANDLE.get("string", 0L); } @Test(expectedExceptions=WrongMethodTypeException.class) public void testMemoryCoordinatePrimitive() { int v = (int)INT_HANDLE.get(1); } @Test(expectedExceptions=ClassCastException.class) public void testMemoryAddressValueGetAsString() { try (Arena arena = Arena.ofConfined()) { - MemorySegment s = arena.allocate(8, 8);; - String address = (String)ADDR_HANDLE.get(s); + MemorySegment s = arena.allocate(8, 8); + String address = (String)ADDR_HANDLE.get(s, 0L); } } @Test(expectedExceptions=ClassCastException.class) public void testMemoryAddressValueSetAsString() { try (Arena arena = Arena.ofConfined()) { MemorySegment s = arena.allocate(8, 8);; - ADDR_HANDLE.set(s, "string"); + ADDR_HANDLE.set(s, 0L, "string"); } } @Test(expectedExceptions=WrongMethodTypeException.class) public void testMemoryAddressValueGetAsPrimitive() { try (Arena arena = Arena.ofConfined()) { - MemorySegment s = arena.allocate(8, 8);; - int address = (int)ADDR_HANDLE.get(s); + MemorySegment s = arena.allocate(8, 8); + int address = (int)ADDR_HANDLE.get(s, 0L); } } @Test(expectedExceptions=WrongMethodTypeException.class) public void testMemoryAddressValueSetAsPrimitive() { diff a/test/jdk/java/foreign/TestUnsupportedLinker.java b/test/jdk/java/foreign/TestUnsupportedLinker.java --- a/test/jdk/java/foreign/TestUnsupportedLinker.java +++ b/test/jdk/java/foreign/TestUnsupportedLinker.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * * @run testng/othervm -Djdk.internal.foreign.CABI=UNSUPPORTED --enable-native-access=ALL-UNNAMED TestUnsupportedLinker */ import java.lang.foreign.Linker; diff a/test/jdk/java/foreign/TestUpcallAsync.java b/test/jdk/java/foreign/TestUpcallAsync.java --- a/test/jdk/java/foreign/TestUpcallAsync.java +++ b/test/jdk/java/foreign/TestUpcallAsync.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires !vm.musl * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies diff a/test/jdk/java/foreign/TestUpcallException.java b/test/jdk/java/foreign/TestUpcallException.java --- a/test/jdk/java/foreign/TestUpcallException.java +++ b/test/jdk/java/foreign/TestUpcallException.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @library /test/lib * @build TestUpcallException * * @run testng/othervm/native * --enable-native-access=ALL-UNNAMED diff a/test/jdk/java/foreign/TestUpcallHighArity.java b/test/jdk/java/foreign/TestUpcallHighArity.java --- a/test/jdk/java/foreign/TestUpcallHighArity.java +++ b/test/jdk/java/foreign/TestUpcallHighArity.java @@ -22,12 +22,10 @@ * */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallHighArity * * @run testng/othervm/native * --enable-native-access=ALL-UNNAMED @@ -46,19 +44,10 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; public class TestUpcallHighArity extends CallGeneratorHelper { static final MethodHandle MH_do_upcall; - static final Linker LINKER = Linker.nativeLinker(); - - // struct S_PDI { void* p0; double p1; int p2; }; - static final MemoryLayout S_PDI_LAYOUT = MemoryLayout.structLayout( - C_POINTER.withName("p0"), - C_DOUBLE.withName("p1"), - C_INT.withName("p2"), - MemoryLayout.paddingLayout(4) - ); static { System.loadLibrary("TestUpcallHighArity"); MH_do_upcall = LINKER.downcallHandle( findNativeOrThrow("do_upcall"), diff a/test/jdk/java/foreign/TestUpcallScope.java b/test/jdk/java/foreign/TestUpcallScope.java --- a/test/jdk/java/foreign/TestUpcallScope.java +++ b/test/jdk/java/foreign/TestUpcallScope.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies * --enable-native-access=ALL-UNNAMED -Dgenerator.sample.factor=17 diff a/test/jdk/java/foreign/TestUpcallStack.java b/test/jdk/java/foreign/TestUpcallStack.java --- a/test/jdk/java/foreign/TestUpcallStack.java +++ b/test/jdk/java/foreign/TestUpcallStack.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires (!(os.name == "Mac OS X" & os.arch == "aarch64") | jdk.foreign.linker != "FALLBACK") * @modules java.base/jdk.internal.foreign * @build NativeTestHelper CallGeneratorHelper TestUpcallBase * * @run testng/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-VerifyDependencies diff a/test/jdk/java/foreign/TestUpcallStructScope.java b/test/jdk/java/foreign/TestUpcallStructScope.java --- a/test/jdk/java/foreign/TestUpcallStructScope.java +++ b/test/jdk/java/foreign/TestUpcallStructScope.java @@ -22,12 +22,10 @@ * */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * * @run testng/othervm/native * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=false * TestUpcallStructScope @@ -49,21 +47,12 @@ import static org.testng.Assert.assertFalse; public class TestUpcallStructScope extends NativeTestHelper { static final MethodHandle MH_do_upcall; - static final Linker LINKER = Linker.nativeLinker(); static final MethodHandle MH_Consumer_accept; - // struct S_PDI { void* p0; double p1; int p2; }; - static final MemoryLayout S_PDI_LAYOUT = MemoryLayout.structLayout( - C_POINTER.withName("p0"), - C_DOUBLE.withName("p1"), - C_INT.withName("p2"), - MemoryLayout.paddingLayout(4) - ); - static { System.loadLibrary("TestUpcallStructScope"); MH_do_upcall = LINKER.downcallHandle( findNativeOrThrow("do_upcall"), FunctionDescriptor.ofVoid(C_POINTER, S_PDI_LAYOUT) diff a/test/jdk/java/foreign/TestValueLayouts.java b/test/jdk/java/foreign/TestValueLayouts.java --- a/test/jdk/java/foreign/TestValueLayouts.java +++ b/test/jdk/java/foreign/TestValueLayouts.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules java.base/jdk.internal.misc * @run testng TestValueLayouts */ import org.testng.annotations.*; diff a/test/jdk/java/foreign/TestVarArgs.java b/test/jdk/java/foreign/TestVarArgs.java --- a/test/jdk/java/foreign/TestVarArgs.java +++ b/test/jdk/java/foreign/TestVarArgs.java @@ -22,12 +22,10 @@ * */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @run testng/othervm --enable-native-access=ALL-UNNAMED -Dgenerator.sample.factor=17 TestVarArgs */ import java.lang.foreign.Arena; @@ -38,10 +36,11 @@ import java.lang.foreign.MemorySegment; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +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.util.ArrayList; @@ -49,11 +48,10 @@ import static java.lang.foreign.MemoryLayout.PathElement.*; public class TestVarArgs extends CallGeneratorHelper { - static final VarHandle VH_IntArray = C_INT.arrayElementVarHandle(); static final MethodHandle MH_CHECK; static final Linker LINKER = Linker.nativeLinker(); static { System.loadLibrary("VarArgs"); @@ -80,11 +78,11 @@ CallInfo.writeback(callInfo, writeBack); CallInfo.argIDs(callInfo, argIDs); for (int i = 0; i < args.size(); i++) { - VH_IntArray.set(argIDs, (long) i, args.get(i).id.ordinal()); + argIDs.setAtIndex(ValueLayout.JAVA_INT, i, args.get(i).id.ordinal()); } List argLayouts = new ArrayList<>(); argLayouts.add(C_POINTER); // call info argLayouts.add(C_INT); // size @@ -195,14 +193,14 @@ static final VarHandle VH_writeback = LAYOUT.varHandle(groupElement("writeback")); static final VarHandle VH_argIDs = LAYOUT.varHandle(groupElement("argIDs")); static void writeback(MemorySegment seg, MemorySegment addr) { - VH_writeback.set(seg, addr); + VH_writeback.set(seg, 0L, addr); } static void argIDs(MemorySegment seg, MemorySegment addr) { - VH_argIDs.set(seg, addr); + VH_argIDs.set(seg, 0L, addr); } } private static final class Arg { private final TestValue value; @@ -217,11 +215,13 @@ this.value = value; this.getter = getter; } private static Arg primitiveArg(NativeType id, MemoryLayout layout, TestValue value) { - return new Arg(id, layout, value, layout.varHandle().toMethodHandle(VarHandle.AccessMode.GET)); + MethodHandle getterHandle = layout.varHandle().toMethodHandle(VarHandle.AccessMode.GET); + getterHandle = MethodHandles.insertArguments(getterHandle, 1, 0L); // align signature with getter for structs + return new Arg(id, layout, value, getterHandle); } private static Arg structArg(NativeType id, MemoryLayout layout, TestValue value) { return new Arg(id, layout, value, MethodHandles.identity(MemorySegment.class)); } diff a/test/jdk/java/foreign/TestVarHandleCombinators.java b/test/jdk/java/foreign/TestVarHandleCombinators.java --- a/test/jdk/java/foreign/TestVarHandleCombinators.java +++ b/test/jdk/java/foreign/TestVarHandleCombinators.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @run testng TestVarHandleCombinators */ import java.lang.foreign.Arena; import java.lang.foreign.ValueLayout; @@ -42,52 +41,50 @@ public class TestVarHandleCombinators { @Test public void testElementAccess() { - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_BYTE); + VarHandle vh = ValueLayout.JAVA_BYTE.varHandle(); byte[] arr = { 0, 0, -1, 0 }; MemorySegment segment = MemorySegment.ofArray(arr); assertEquals((byte) vh.get(segment, 2), (byte) -1); } @Test(expectedExceptions = IllegalArgumentException.class) public void testUnalignedElement() { - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_BYTE.withByteAlignment(4)); + VarHandle vh = ValueLayout.JAVA_BYTE.withByteAlignment(4).varHandle(); MemorySegment segment = MemorySegment.ofArray(new byte[4]); vh.get(segment, 2L); //should throw //FIXME: the VH only checks the alignment of the segment, which is fine if the VH is derived from layouts, //FIXME: but not if the VH is just created from scratch - we need a VH variable to govern this property, //FIXME: at least until the VM is fixed } @Test public void testAlign() { - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_BYTE.withByteAlignment(2)); + VarHandle vh = ValueLayout.JAVA_BYTE.withByteAlignment(2).varHandle(); Arena scope = Arena.ofAuto(); MemorySegment segment = scope.allocate(1L, 2); vh.set(segment, 0L, (byte) 10); // fine, memory region is aligned assertEquals((byte) vh.get(segment, 0L), (byte) 10); } @Test public void testByteOrderLE() { - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_SHORT_UNALIGNED - .withOrder(ByteOrder.LITTLE_ENDIAN)); + VarHandle vh = ValueLayout.JAVA_SHORT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN).varHandle(); byte[] arr = new byte[2]; MemorySegment segment = MemorySegment.ofArray(arr); vh.set(segment, 0L, (short) 0xFF); assertEquals(arr[0], (byte) 0xFF); assertEquals(arr[1], (byte) 0); } @Test public void testByteOrderBE() { - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_SHORT_UNALIGNED - .withOrder(ByteOrder.BIG_ENDIAN)); + VarHandle vh = ValueLayout.JAVA_SHORT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN).varHandle(); byte[] arr = new byte[2]; MemorySegment segment = MemorySegment.ofArray(arr); vh.set(segment, 0L, (short) 0xFF); assertEquals(arr[0], (byte) 0); assertEquals(arr[1], (byte) 0xFF); @@ -98,11 +95,11 @@ int outer_size = 10; int inner_size = 5; //[10 : [5 : [x32 i32]]] - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayout.JAVA_INT); + VarHandle vh = ValueLayout.JAVA_INT.varHandle(); int count = 0; try (Arena arena = Arena.ofConfined()) { MemorySegment segment = arena.allocate(inner_size * outer_size * 8, 4); for (long i = 0; i < outer_size; i++) { for (long j = 0; j < inner_size; j++) { diff a/test/jdk/java/foreign/UpcallTestHelper.java b/test/jdk/java/foreign/UpcallTestHelper.java --- a/test/jdk/java/foreign/UpcallTestHelper.java +++ b/test/jdk/java/foreign/UpcallTestHelper.java @@ -54,11 +54,10 @@ public Output runInNewProcess(Class target, boolean useSpec, String... programArgs) throws IOException, InterruptedException { assert !target.isArray(); List command = new ArrayList<>(List.of( - "--enable-preview", "--enable-native-access=ALL-UNNAMED", "-Djava.library.path=" + System.getProperty("java.library.path"), "-Djdk.internal.foreign.UpcallLinker.USE_SPEC=" + useSpec, target.getName() )); diff a/test/jdk/java/foreign/arraystructs/TestArrayStructs.java b/test/jdk/java/foreign/arraystructs/TestArrayStructs.java --- a/test/jdk/java/foreign/arraystructs/TestArrayStructs.java +++ b/test/jdk/java/foreign/arraystructs/TestArrayStructs.java @@ -21,13 +21,11 @@ * questions. */ /* * @test id=specialized - * @enablePreview * @library ../ - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires (!(os.name == "Mac OS X" & os.arch == "aarch64") | jdk.foreign.linker != "FALLBACK") * @modules java.base/jdk.internal.foreign * @run testng/othervm * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=true @@ -35,13 +33,11 @@ * TestArrayStructs */ /* * @test id=interpreted - * @enablePreview * @library ../ - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires (!(os.name == "Mac OS X" & os.arch == "aarch64") | jdk.foreign.linker != "FALLBACK") * @modules java.base/jdk.internal.foreign * @run testng/othervm * --enable-native-access=ALL-UNNAMED * -Djdk.internal.foreign.DowncallLinker.USE_SPEC=false diff a/test/jdk/java/foreign/callarranger/TestLayoutEquality.java b/test/jdk/java/foreign/callarranger/TestLayoutEquality.java --- a/test/jdk/java/foreign/callarranger/TestLayoutEquality.java +++ b/test/jdk/java/foreign/callarranger/TestLayoutEquality.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign.abi * @modules java.base/jdk.internal.foreign.layout * @run testng TestLayoutEquality */ diff a/test/jdk/java/foreign/callarranger/TestLinuxAArch64CallArranger.java b/test/jdk/java/foreign/callarranger/TestLinuxAArch64CallArranger.java --- a/test/jdk/java/foreign/callarranger/TestLinuxAArch64CallArranger.java +++ b/test/jdk/java/foreign/callarranger/TestLinuxAArch64CallArranger.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @requires sun.arch.data.model == "64" * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign * java.base/jdk.internal.foreign.abi * java.base/jdk.internal.foreign.abi.aarch64 diff a/test/jdk/java/foreign/callarranger/TestMacOsAArch64CallArranger.java b/test/jdk/java/foreign/callarranger/TestMacOsAArch64CallArranger.java --- a/test/jdk/java/foreign/callarranger/TestMacOsAArch64CallArranger.java +++ b/test/jdk/java/foreign/callarranger/TestMacOsAArch64CallArranger.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @requires sun.arch.data.model == "64" * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign * java.base/jdk.internal.foreign.abi * java.base/jdk.internal.foreign.abi.aarch64 diff a/test/jdk/java/foreign/callarranger/TestRISCV64CallArranger.java b/test/jdk/java/foreign/callarranger/TestRISCV64CallArranger.java --- a/test/jdk/java/foreign/callarranger/TestRISCV64CallArranger.java +++ b/test/jdk/java/foreign/callarranger/TestRISCV64CallArranger.java @@ -24,11 +24,10 @@ * */ /* * @test - * @enablePreview * @requires sun.arch.data.model == "64" * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign * java.base/jdk.internal.foreign.abi * java.base/jdk.internal.foreign.abi.riscv64 diff a/test/jdk/java/foreign/callarranger/TestSysVCallArranger.java b/test/jdk/java/foreign/callarranger/TestSysVCallArranger.java --- a/test/jdk/java/foreign/callarranger/TestSysVCallArranger.java +++ b/test/jdk/java/foreign/callarranger/TestSysVCallArranger.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign * java.base/jdk.internal.foreign.abi * java.base/jdk.internal.foreign.abi.x64 * java.base/jdk.internal.foreign.abi.x64.sysv diff a/test/jdk/java/foreign/callarranger/TestWindowsAArch64CallArranger.java b/test/jdk/java/foreign/callarranger/TestWindowsAArch64CallArranger.java --- a/test/jdk/java/foreign/callarranger/TestWindowsAArch64CallArranger.java +++ b/test/jdk/java/foreign/callarranger/TestWindowsAArch64CallArranger.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign * java.base/jdk.internal.foreign.abi * java.base/jdk.internal.foreign.abi.aarch64 * @build CallArrangerTestBase diff a/test/jdk/java/foreign/callarranger/TestWindowsCallArranger.java b/test/jdk/java/foreign/callarranger/TestWindowsCallArranger.java --- a/test/jdk/java/foreign/callarranger/TestWindowsCallArranger.java +++ b/test/jdk/java/foreign/callarranger/TestWindowsCallArranger.java @@ -22,11 +22,10 @@ * */ /* * @test - * @enablePreview * @requires sun.arch.data.model == "64" * @compile platform/PlatformLayouts.java * @modules java.base/jdk.internal.foreign * java.base/jdk.internal.foreign.abi * java.base/jdk.internal.foreign.abi.x64 diff a/test/jdk/java/foreign/capturecallstate/TestCaptureCallState.java b/test/jdk/java/foreign/capturecallstate/TestCaptureCallState.java --- a/test/jdk/java/foreign/capturecallstate/TestCaptureCallState.java +++ b/test/jdk/java/foreign/capturecallstate/TestCaptureCallState.java @@ -21,13 +21,11 @@ * questions. */ /* * @test - * @enablePreview * @library ../ /test/lib - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestCaptureCallState */ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -61,20 +59,16 @@ System.load(system32 + "\\Kernel32.dll"); System.load(system32 + "\\Ws2_32.dll"); } } - private record SaveValuesCase(String nativeTarget, FunctionDescriptor nativeDesc, boolean trivial, String threadLocalName, Consumer resultCheck) {} + private record SaveValuesCase(String nativeTarget, FunctionDescriptor nativeDesc, String threadLocalName, Consumer resultCheck) {} @Test(dataProvider = "cases") public void testSavedThreadLocal(SaveValuesCase testCase) throws Throwable { - List options = new ArrayList<>(); - options.add(Linker.Option.captureCallState(testCase.threadLocalName())); - if (testCase.trivial()) { - options.add(Linker.Option.isTrivial()); - } - MethodHandle handle = downcallHandle(testCase.nativeTarget(), testCase.nativeDesc(), options.toArray(Linker.Option[]::new)); + Linker.Option stl = Linker.Option.captureCallState(testCase.threadLocalName()); + MethodHandle handle = downcallHandle(testCase.nativeTarget(), testCase.nativeDesc(), stl); StructLayout capturedStateLayout = Linker.Option.captureStateLayout(); VarHandle errnoHandle = capturedStateLayout.varHandle(groupElement(testCase.threadLocalName())); try (Arena arena = Arena.ofConfined()) { @@ -83,11 +77,11 @@ boolean needsAllocator = testCase.nativeDesc().returnLayout().map(StructLayout.class::isInstance).orElse(false); Object result = needsAllocator ? handle.invoke(arena, saveSeg, testValue) : handle.invoke(saveSeg, testValue); testCase.resultCheck().accept(result); - int savedErrno = (int) errnoHandle.get(saveSeg); + int savedErrno = (int) errnoHandle.get(saveSeg, 0L); assertEquals(savedErrno, testValue); } } @Test(dataProvider = "invalidCaptureSegmentCases") @@ -103,59 +97,51 @@ assertTrue(expectedExceptionType.isInstance(t)); assertTrue(t.getMessage().matches(expectedExceptionMessage)); } } - interface CaseAdder { - void addCase(String nativeTarget, FunctionDescriptor nativeDesc, String threadLocalName, Consumer resultCheck); - } - @DataProvider public static Object[][] cases() { List cases = new ArrayList<>(); - CaseAdder adder = (nativeTarget, nativeDesc, threadLocalName, resultCheck) -> { - cases.add(new SaveValuesCase(nativeTarget, nativeDesc, false, threadLocalName, resultCheck)); - cases.add(new SaveValuesCase(nativeTarget, nativeDesc, true, threadLocalName, resultCheck)); - }; - adder.addCase("set_errno_V", FunctionDescriptor.ofVoid(JAVA_INT), "errno", o -> {}); - adder.addCase("set_errno_I", FunctionDescriptor.of(JAVA_INT, JAVA_INT), "errno", o -> assertEquals((int) o, 42)); - adder.addCase("set_errno_D", FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT), "errno", o -> assertEquals((double) o, 42.0)); - - structCase(adder, "SL", Map.of(JAVA_LONG.withName("x"), 42L)); - structCase(adder, "SLL", Map.of(JAVA_LONG.withName("x"), 42L, - JAVA_LONG.withName("y"), 42L)); - structCase(adder, "SLLL", Map.of(JAVA_LONG.withName("x"), 42L, - JAVA_LONG.withName("y"), 42L, - JAVA_LONG.withName("z"), 42L)); - structCase(adder, "SD", Map.of(JAVA_DOUBLE.withName("x"), 42D)); - structCase(adder, "SDD", Map.of(JAVA_DOUBLE.withName("x"), 42D, - JAVA_DOUBLE.withName("y"), 42D)); - structCase(adder, "SDDD", Map.of(JAVA_DOUBLE.withName("x"), 42D, - JAVA_DOUBLE.withName("y"), 42D, - JAVA_DOUBLE.withName("z"), 42D)); + cases.add(new SaveValuesCase("set_errno_V", FunctionDescriptor.ofVoid(JAVA_INT), "errno", o -> {})); + cases.add(new SaveValuesCase("set_errno_I", FunctionDescriptor.of(JAVA_INT, JAVA_INT), "errno", o -> assertEquals((int) o, 42))); + cases.add(new SaveValuesCase("set_errno_D", FunctionDescriptor.of(JAVA_DOUBLE, JAVA_INT), "errno", o -> assertEquals((double) o, 42.0))); + + cases.add(structCase("SL", Map.of(JAVA_LONG.withName("x"), 42L))); + cases.add(structCase("SLL", Map.of(JAVA_LONG.withName("x"), 42L, + JAVA_LONG.withName("y"), 42L))); + cases.add(structCase("SLLL", Map.of(JAVA_LONG.withName("x"), 42L, + JAVA_LONG.withName("y"), 42L, + JAVA_LONG.withName("z"), 42L))); + cases.add(structCase("SD", Map.of(JAVA_DOUBLE.withName("x"), 42D))); + cases.add(structCase("SDD", Map.of(JAVA_DOUBLE.withName("x"), 42D, + JAVA_DOUBLE.withName("y"), 42D))); + cases.add(structCase("SDDD", Map.of(JAVA_DOUBLE.withName("x"), 42D, + JAVA_DOUBLE.withName("y"), 42D, + JAVA_DOUBLE.withName("z"), 42D))); if (IS_WINDOWS) { - adder.addCase("SetLastError", FunctionDescriptor.ofVoid(JAVA_INT), "GetLastError", o -> {}); - adder.addCase("WSASetLastError", FunctionDescriptor.ofVoid(JAVA_INT), "WSAGetLastError", o -> {}); + cases.add(new SaveValuesCase("SetLastError", FunctionDescriptor.ofVoid(JAVA_INT), "GetLastError", o -> {})); + cases.add(new SaveValuesCase("WSASetLastError", FunctionDescriptor.ofVoid(JAVA_INT), "WSAGetLastError", o -> {})); } return cases.stream().map(tc -> new Object[] {tc}).toArray(Object[][]::new); } - static void structCase(CaseAdder adder, String name, Map fields) { + static SaveValuesCase structCase(String name, Map fields) { StructLayout layout = MemoryLayout.structLayout(fields.keySet().toArray(MemoryLayout[]::new)); Consumer check = o -> {}; for (var field : fields.entrySet()) { MemoryLayout fieldLayout = field.getKey(); VarHandle fieldHandle = layout.varHandle(MemoryLayout.PathElement.groupElement(fieldLayout.name().get())); Object value = field.getValue(); - check = check.andThen(o -> assertEquals(fieldHandle.get(o), value)); + check = check.andThen(o -> assertEquals(fieldHandle.get(o, 0L), value)); } - adder.addCase("set_errno_" + name, FunctionDescriptor.of(layout, JAVA_INT), "errno", check); + return new SaveValuesCase("set_errno_" + name, FunctionDescriptor.of(layout, JAVA_INT), "errno", check); } @DataProvider public static Object[][] invalidCaptureSegmentCases() { return new Object[][]{ diff a/test/jdk/java/foreign/channels/TestAsyncSocketChannels.java b/test/jdk/java/foreign/channels/TestAsyncSocketChannels.java --- a/test/jdk/java/foreign/channels/TestAsyncSocketChannels.java +++ b/test/jdk/java/foreign/channels/TestAsyncSocketChannels.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @library /test/lib * @modules java.base/sun.nio.ch * @key randomness * @run testng/othervm TestAsyncSocketChannels * @run testng/othervm -Dsun.nio.ch.disableSynchronousRead=true TestAsyncSocketChannels diff a/test/jdk/java/foreign/channels/TestSocketChannels.java b/test/jdk/java/foreign/channels/TestSocketChannels.java --- a/test/jdk/java/foreign/channels/TestSocketChannels.java +++ b/test/jdk/java/foreign/channels/TestSocketChannels.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @library /test/lib * @modules java.base/sun.nio.ch * @key randomness * @run testng/othervm TestSocketChannels */ diff a/test/jdk/java/foreign/dontrelease/TestDontRelease.java b/test/jdk/java/foreign/dontrelease/TestDontRelease.java --- a/test/jdk/java/foreign/dontrelease/TestDontRelease.java +++ b/test/jdk/java/foreign/dontrelease/TestDontRelease.java @@ -21,14 +21,12 @@ * questions. */ /* * @test - * @enablePreview * @library ../ /test/lib * @modules java.base/jdk.internal.ref java.base/jdk.internal.foreign - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestDontRelease */ import jdk.internal.foreign.MemorySessionImpl; import org.testng.annotations.Test; diff a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccess.java @@ -21,23 +21,20 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires !vm.musl * * @library /test/lib * @build TestEnableNativeAccess * panama_module/* - * org.openjdk.foreigntest.PanamaMainUnnamedModule + * org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule * @run testng/othervm/timeout=180 TestEnableNativeAccess * @summary Basic test for java --enable-native-access */ -import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; @@ -55,79 +52,11 @@ * if flag not present: - permit access to all modules and omit a warning * (on first access per module only) */ @Test -public class TestEnableNativeAccess { - - static final String MODULE_PATH = System.getProperty("jdk.module.path"); - - static final String PANAMA_MAIN = "panama_module/org.openjdk.foreigntest.PanamaMainDirect"; - static final String PANAMA_REFLECTION = "panama_module/org.openjdk.foreigntest.PanamaMainReflection"; - static final String PANAMA_INVOKE = "panama_module/org.openjdk.foreigntest.PanamaMainInvoke"; - static final String PANAMA_JNI = "panama_module/org.openjdk.foreigntest.PanamaMainJNI"; - static final String UNNAMED = "org.openjdk.foreigntest.PanamaMainUnnamedModule"; - - /** - * Represents the expected result of a test. - */ - static final class Result { - private final boolean success; - private final List expectedOutput = new ArrayList<>(); - private final List notExpectedOutput = new ArrayList<>(); - - Result(boolean success) { - this.success = success; - } - - Result expect(String msg) { - expectedOutput.add(msg); - return this; - } - - Result doNotExpect(String msg) { - notExpectedOutput.add(msg); - return this; - } - - boolean shouldSucceed() { - return success; - } - - Stream expectedOutput() { - return expectedOutput.stream(); - } - - Stream notExpectedOutput() { - return notExpectedOutput.stream(); - } - - @Override - public String toString() { - String s = (success) ? "success" : "failure"; - for (String msg : expectedOutput) { - s += "/" + msg; - } - return s; - } - } - - static Result success() { - return new Result(true); - } - - static Result successNoWarning() { - return success().doNotExpect("WARNING"); - } - - static Result successWithWarning(String moduleName) { - return success().expect("WARNING").expect("--enable-native-access=" + moduleName); - } - - static Result failWithWarning(String expectedOutput) { - return new Result(false).expect(expectedOutput).expect("WARNING"); - } +public class TestEnableNativeAccess extends TestEnableNativeAccessBase { @DataProvider(name = "succeedCases") public Object[][] succeedCases() { return new Object[][] { { "panama_enable_native_access", PANAMA_MAIN, successNoWarning(), new String[]{"--enable-native-access=panama_module"} }, @@ -148,25 +77,10 @@ { "panama_no_unnamed_module_native_access", UNNAMED, successWithWarning("ALL-UNNAMED"), new String[]{} }, { "panama_all_unnamed_module_native_access", UNNAMED, successNoWarning(), new String[]{"--enable-native-access=ALL-UNNAMED"} }, }; } - /** - * Checks an expected result with the output captured by the given - * OutputAnalyzer. - */ - void checkResult(Result expectedResult, OutputAnalyzer outputAnalyzer) { - expectedResult.expectedOutput().forEach(outputAnalyzer::shouldContain); - expectedResult.notExpectedOutput().forEach(outputAnalyzer::shouldNotContain); - int exitValue = outputAnalyzer.getExitValue(); - if (expectedResult.shouldSucceed()) { - assertTrue(exitValue == 0); - } else { - assertTrue(exitValue != 0); - } - } - /** * Runs the test to execute the given test action. The VM is run with the * given VM options and the output checked to see that it matches the * expected result. */ @@ -174,12 +88,12 @@ throws Exception { Stream s1 = Stream.concat( Stream.of(vmopts), Stream.of("-Djava.library.path=" + System.getProperty("java.library.path"))); - Stream s2 = cls.equals(UNNAMED) ? Stream.of("--enable-preview", "-p", MODULE_PATH, cls, action) - : Stream.of("--enable-preview", "-p", MODULE_PATH, "-m", cls, action); + Stream s2 = cls.equals(UNNAMED) ? Stream.of("-p", MODULE_PATH, cls, action) + : Stream.of("-p", MODULE_PATH, "-m", cls, action); String[] opts = Stream.concat(s1, s2).toArray(String[]::new); OutputAnalyzer outputAnalyzer = ProcessTools .executeTestJava(opts) .outputTo(System.out) .errorTo(System.out); @@ -197,11 +111,11 @@ * on first access of a module. */ public void testWarnFirstAccess() throws Exception { List output1 = run("panama_enable_native_access_first", PANAMA_MAIN, successWithWarning("panama")).asLines(); - assertTrue(count(output1, "WARNING") == 3); // 3 on first access, none on subsequent access + assertTrue(count(output1, "WARNING") == 4); // 4 on first access, none on subsequent access } /** * Specifies --enable-native-access more than once, each list of module names * is appended. diff a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessBase.java @@ -0,0 +1,124 @@ +/* + * 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 java.util.List; +import java.util.ArrayList; +import java.util.stream.Stream; + +import jdk.test.lib.process.OutputAnalyzer; + +import static org.testng.Assert.*; + +public class TestEnableNativeAccessBase { + static final String MODULE_PATH = System.getProperty("jdk.module.path"); + + static final String PANAMA_MAIN_CLS = "org.openjdk.foreigntest.PanamaMainDirect"; + static final String PANAMA_MAIN = "panama_module/" + PANAMA_MAIN_CLS; + static final String PANAMA_REFLECTION_CLS = "org.openjdk.foreigntest.PanamaMainReflection"; + static final String PANAMA_REFLECTION = "panama_module/" + PANAMA_REFLECTION_CLS; + static final String PANAMA_INVOKE_CLS = "org.openjdk.foreigntest.PanamaMainInvoke"; + static final String PANAMA_INVOKE = "panama_module/" + PANAMA_INVOKE_CLS; + static final String PANAMA_JNI_CLS = "org.openjdk.foreigntest.PanamaMainJNI"; + static final String PANAMA_JNI = "panama_module/" + PANAMA_JNI_CLS; + static final String UNNAMED = "org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule"; + + /** + * Represents the expected result of a test. + */ + static final class Result { + private final boolean success; + private final List expectedOutput = new ArrayList<>(); + private final List notExpectedOutput = new ArrayList<>(); + + Result(boolean success) { + this.success = success; + } + + Result expect(String msg) { + expectedOutput.add(msg); + return this; + } + + Result doNotExpect(String msg) { + notExpectedOutput.add(msg); + return this; + } + + boolean shouldSucceed() { + return success; + } + + Stream expectedOutput() { + return expectedOutput.stream(); + } + + Stream notExpectedOutput() { + return notExpectedOutput.stream(); + } + + @Override + public String toString() { + String s = (success) ? "success" : "failure"; + for (String msg : expectedOutput) { + s += "/" + msg; + } + return s; + } + + } + + static Result success() { + return new Result(true); + } + + static Result successNoWarning() { + return success().doNotExpect("WARNING"); + } + + static Result successWithWarning(String moduleName) { + return success().expect("WARNING").expect("--enable-native-access=" + moduleName); + } + + static Result failWithWarning(String expectedOutput) { + return new Result(false).expect(expectedOutput).expect("WARNING"); + } + + static Result failWithError(String expectedOutput) { + return new Result(false).expect(expectedOutput); + } + + /** + * Checks an expected result with the output captured by the given + * OutputAnalyzer. + */ + void checkResult(Result expectedResult, OutputAnalyzer outputAnalyzer) { + expectedResult.expectedOutput().forEach(outputAnalyzer::shouldContain); + expectedResult.notExpectedOutput().forEach(outputAnalyzer::shouldNotContain); + int exitValue = outputAnalyzer.getExitValue(); + if (expectedResult.shouldSucceed()) { + assertTrue(exitValue == 0); + } else { + assertTrue(exitValue != 0); + } + } +} diff a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessDynamic.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires !vm.musl * * @library /test/lib * @build TestEnableNativeAccessDynamic * panama_module/* @@ -35,84 +33,19 @@ * @summary Test for dynamically setting --enable-native-access flag for a module */ import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.testng.Assert.*; @Test -public class TestEnableNativeAccessDynamic { - - static final String MODULE_PATH = System.getProperty("jdk.module.path"); - - static final String PANAMA_MAIN = "panama_module/org.openjdk.foreigntest.PanamaMainDirect"; - static final String PANAMA_REFLECTION = "panama_module/org.openjdk.foreigntest.PanamaMainReflection"; - static final String PANAMA_INVOKE = "panama_module/org.openjdk.foreigntest.PanamaMainInvoke"; - static final String PANAMA_JNI = "panama_module/org.openjdk.foreigntest.PanamaMainJNI"; - - /** - * Represents the expected result of a test. - */ - static final class Result { - private final boolean success; - private final List expectedOutput = new ArrayList<>(); - private final List notExpectedOutput = new ArrayList<>(); - - Result(boolean success) { - this.success = success; - } - - Result expect(String msg) { - expectedOutput.add(msg); - return this; - } - - Result doNotExpect(String msg) { - notExpectedOutput.add(msg); - return this; - } - - boolean shouldSucceed() { - return success; - } - - Stream expectedOutput() { - return expectedOutput.stream(); - } - - Stream notExpectedOutput() { - return notExpectedOutput.stream(); - } - - @Override - public String toString() { - String s = (success) ? "success" : "failure"; - for (String msg : expectedOutput) { - s += "/" + msg; - } - return s; - } - } - - static Result success() { - return new Result(true); - } - - static Result successNoWarning() { - return success().doNotExpect("WARNING"); - } - - static Result failWithError(String expectedOutput) { - return new Result(false).expect(expectedOutput); - } +public class TestEnableNativeAccessDynamic extends TestEnableNativeAccessBase { @DataProvider(name = "succeedCases") public Object[][] succeedCases() { return new Object[][] { { "panama_enable_native_access", PANAMA_MAIN, successNoWarning() }, @@ -129,35 +62,19 @@ { "panama_enable_native_access_fail_reflection", PANAMA_REFLECTION, failWithError(errMsg) }, { "panama_enable_native_access_fail_invoke", PANAMA_INVOKE, failWithError(errMsg) }, }; } - /** - * Checks an expected result with the output captured by the given - * OutputAnalyzer. - */ - void checkResult(Result expectedResult, OutputAnalyzer outputAnalyzer) { - expectedResult.expectedOutput().forEach(outputAnalyzer::shouldContain); - expectedResult.notExpectedOutput().forEach(outputAnalyzer::shouldNotContain); - int exitValue = outputAnalyzer.getExitValue(); - if (expectedResult.shouldSucceed()) { - assertTrue(exitValue == 0); - } else { - assertTrue(exitValue != 0); - } - } - /** * Runs the test to execute the given test action. The VM is run with the * given VM options and the output checked to see that it matches the * expected result. */ OutputAnalyzer run(String action, String moduleAndCls, boolean enableNativeAccess, Result expectedResult, boolean panamaModuleInBootLayer) throws Exception { List list = new ArrayList<>(); - list.add("--enable-preview"); if (panamaModuleInBootLayer) { list.addAll(List.of("-p", MODULE_PATH)); list.add("--add-modules=panama_module"); list.add("--enable-native-access=panama_module"); } else { diff a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java --- /dev/null +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java @@ -0,0 +1,137 @@ +/* + * 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. + */ + +/** + * @test + * @summary Basic test for Enable-Native-Access attribute in the + * manifest of a main application JAR + * @library /test/lib + * @requires !vm.musl + * + * @build TestEnableNativeAccessJarManifest + * panama_module/* + * org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule + * @run testng/othervm TestEnableNativeAccessJarManifest + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.ArrayList; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.util.JarUtils; + +import org.testng.annotations.Test; +import org.testng.annotations.DataProvider; + +public class TestEnableNativeAccessJarManifest extends TestEnableNativeAccessBase { + + private static final String REINVOKER = "TestEnableNativeAccessJarManifest$Reinvoker"; + + static record Attribute(String name, String value) {} + + @Test(dataProvider = "cases") + public void testEnableNativeAccessInJarManifest(String action, String cls, Result expectedResult, + List attributes, List vmArgs, List programArgs) throws Exception { + Manifest man = new Manifest(); + Attributes attrs = man.getMainAttributes(); + attrs.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + attrs.put(Attributes.Name.MAIN_CLASS, cls); + + for (Attribute attrib : attributes) { + attrs.put(new Attributes.Name(attrib.name()), attrib.value()); + } + + // create the JAR file with Test1 and Test2 + Path jarfile = Paths.get(action + ".jar"); + Files.deleteIfExists(jarfile); + + Path classes = Paths.get(System.getProperty("test.classes", "")); + JarUtils.createJarFile(jarfile, man, classes, Paths.get(cls.replace('.', '/') + ".class")); + + // java -jar test.jar + List command = new ArrayList<>(List.of( + "-Djava.library.path=" + System.getProperty("java.library.path") + )); + command.addAll(vmArgs); + command.add("-jar"); + command.add(jarfile.toString()); + command.addAll(programArgs); + OutputAnalyzer outputAnalyzer = ProcessTools.executeTestJava(command.toArray(String[]::new)) + .outputTo(System.out) + .errorTo(System.out); + checkResult(expectedResult, outputAnalyzer); + } + + @DataProvider + public Object[][] cases() { + return new Object[][] { + // simple cases where a jar contains a single main class with no dependencies + { "panama_no_unnamed_module_native_access", UNNAMED, successWithWarning("ALL-UNNAMED"), + List.of(), List.of(), List.of() }, + { "panama_unnamed_module_native_access", UNNAMED, successNoWarning(), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), List.of(), List.of() }, + { "panama_unnamed_module_native_access_invalid", UNNAMED, failWithError("Only ALL-UNNAMED allowed as value for Enable-Native-Access"), + List.of(new Attribute("Enable-Native-Access", "asdf")), List.of(), List.of() }, + + // more complex cases where a jar invokes a module on the module path that does native access + { "panama_enable_native_access_false", REINVOKER, successWithWarning("panama_module"), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), + List.of("-p", MODULE_PATH, "--add-modules=panama_module"), + List.of(PANAMA_MAIN_CLS) }, + { "panama_enable_native_access_reflection_false", REINVOKER, successWithWarning("panama_module"), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), + List.of("-p", MODULE_PATH, "--add-modules=panama_module"), + List.of(PANAMA_REFLECTION_CLS) }, + { "panama_enable_native_access_invoke_false", REINVOKER, successWithWarning("panama_module"), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), + List.of("-p", MODULE_PATH, "--add-modules=panama_module"), + List.of(PANAMA_INVOKE_CLS) }, + + { "panama_enable_native_access_true", REINVOKER, successNoWarning(), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), + List.of("-p", MODULE_PATH, "--add-modules=panama_module", "--enable-native-access=panama_module"), + List.of(PANAMA_MAIN_CLS) }, + { "panama_enable_native_access_reflection_true", REINVOKER, successNoWarning(), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), + List.of("-p", MODULE_PATH, "--add-modules=panama_module", "--enable-native-access=panama_module"), + List.of(PANAMA_REFLECTION_CLS) }, + { "panama_enable_native_access_invoke_true", REINVOKER, successNoWarning(), + List.of(new Attribute("Enable-Native-Access", "ALL-UNNAMED")), + List.of("-p", MODULE_PATH, "--add-modules=panama_module", "--enable-native-access=panama_module"), + List.of(PANAMA_INVOKE_CLS) } + }; + } + + public class Reinvoker { + public static void main(String[] args) throws Throwable { + Class realMainClass = Class.forName(args[0]); + realMainClass.getMethod("main", String[].class).invoke(null, (Object) new String[0]); + } + } +} diff a/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/PanamaMainUnnamedModule.java b/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/unnamed/PanamaMainUnnamedModule.java --- a/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/PanamaMainUnnamedModule.java +++ b/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/unnamed/PanamaMainUnnamedModule.java @@ -19,11 +19,11 @@ * 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.foreigntest; +package org.openjdk.foreigntest.unnamed; import java.lang.foreign.*; import java.lang.foreign.Linker.Option; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; diff a/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/libLinkerInvokerUnnamed.cpp b/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/unnamed/libLinkerInvokerUnnamed.cpp --- a/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/libLinkerInvokerUnnamed.cpp +++ b/test/jdk/java/foreign/enablenativeaccess/org/openjdk/foreigntest/unnamed/libLinkerInvokerUnnamed.cpp @@ -34,11 +34,11 @@ jvm->DetachCurrentThread(); } extern "C" { JNIEXPORT void JNICALL - Java_org_openjdk_foreigntest_PanamaMainUnnamedModule_nativeLinker0(JNIEnv *env, jclass cls) { + Java_org_openjdk_foreigntest_unnamed_PanamaMainUnnamedModule_nativeLinker0(JNIEnv *env, jclass cls) { JavaVM* jvm; env->GetJavaVM(&jvm); run_in_new_thread_and_join(call, jvm); } } diff a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMain.java b/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMain.java --- a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/PanamaMain.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021, 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.foreigntest; - -import java.lang.foreign.*; - -public class PanamaMain { - public static void main(String[] args) { - System.out.println("Trying to obtain a downcall handle"); - Linker.nativeLinker().downcallHandle(FunctionDescriptor.ofVoid()); - System.out.println("Got downcall handle"); - } -} diff a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp b/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp --- a/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp +++ b/test/jdk/java/foreign/enablenativeaccess/panama_module/org/openjdk/foreigntest/libLinkerInvokerModule.cpp @@ -27,20 +27,23 @@ typedef struct { JavaVM* jvm; jobject linker; jobject desc; jobject opts; + jthrowable exception; } Context; void call(void* arg) { Context* context = (Context*)arg; JNIEnv* env; context->jvm->AttachCurrentThread((void**)&env, NULL); jclass linkerClass = env->FindClass("java/lang/foreign/Linker"); jmethodID nativeLinkerMethod = env->GetMethodID(linkerClass, "downcallHandle", "(Ljava/lang/foreign/FunctionDescriptor;[Ljava/lang/foreign/Linker$Option;)Ljava/lang/invoke/MethodHandle;"); env->CallVoidMethod(context->linker, nativeLinkerMethod, context->desc, context->opts); + context->exception = (jthrowable) env->NewGlobalRef(env->ExceptionOccurred()); + env->ExceptionClear(); context->jvm->DetachCurrentThread(); } extern "C" { JNIEXPORT void JNICALL @@ -49,10 +52,14 @@ env->GetJavaVM(&context.jvm); context.linker = env->NewGlobalRef(linker); context.desc = env->NewGlobalRef(desc); context.opts = env->NewGlobalRef(opts); run_in_new_thread_and_join(call, &context); + if (context.exception != nullptr) { + env->Throw(context.exception); // transfer exception to this thread + } env->DeleteGlobalRef(context.linker); env->DeleteGlobalRef(context.desc); env->DeleteGlobalRef(context.opts); + env->DeleteGlobalRef(context.exception); } } diff a/test/jdk/java/foreign/handles/Driver.java b/test/jdk/java/foreign/handles/Driver.java --- a/test/jdk/java/foreign/handles/Driver.java +++ b/test/jdk/java/foreign/handles/Driver.java @@ -21,11 +21,9 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @build invoker_module/* lookup_module/* * @run testng/othervm --enable-native-access=invoker_module * lookup_module/handle.lookup.MethodHandleLookup */ diff a/test/jdk/java/foreign/largestub/TestLargeStub.java b/test/jdk/java/foreign/largestub/TestLargeStub.java --- a/test/jdk/java/foreign/largestub/TestLargeStub.java +++ b/test/jdk/java/foreign/largestub/TestLargeStub.java @@ -21,13 +21,11 @@ * questions. */ /* * @test - * @enablePreview * @library ../ - * @requires jdk.foreign.linker != "UNSUPPORTED" * @modules java.base/jdk.internal.foreign * @run testng/othervm --enable-native-access=ALL-UNNAMED TestLargeStub */ import org.testng.annotations.Test; diff a/test/jdk/java/foreign/loaderLookup/TestLoaderLookup.java b/test/jdk/java/foreign/loaderLookup/TestLoaderLookup.java --- a/test/jdk/java/foreign/loaderLookup/TestLoaderLookup.java +++ b/test/jdk/java/foreign/loaderLookup/TestLoaderLookup.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @compile lookup/Lookup.java * @compile invoker/Invoker.java * @run main/othervm --enable-native-access=ALL-UNNAMED TestLoaderLookup */ diff a/test/jdk/java/foreign/loaderLookup/TestLoaderLookupJNI.java b/test/jdk/java/foreign/loaderLookup/TestLoaderLookupJNI.java --- a/test/jdk/java/foreign/loaderLookup/TestLoaderLookupJNI.java +++ b/test/jdk/java/foreign/loaderLookup/TestLoaderLookupJNI.java @@ -27,12 +27,10 @@ import static org.testng.Assert.*; /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm TestLoaderLookupJNI */ public class TestLoaderLookupJNI { static { diff a/test/jdk/java/foreign/nested/TestNested.java b/test/jdk/java/foreign/nested/TestNested.java --- a/test/jdk/java/foreign/nested/TestNested.java +++ b/test/jdk/java/foreign/nested/TestNested.java @@ -21,13 +21,11 @@ * questions. */ /* * @test - * @enablePreview * @library ../ /test/lib - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires jdk.foreign.linker != "FALLBACK" * @build NativeTestHelper * @run testng/othervm --enable-native-access=ALL-UNNAMED TestNested */ diff a/test/jdk/java/foreign/normalize/TestNormalize.java b/test/jdk/java/foreign/normalize/TestNormalize.java --- a/test/jdk/java/foreign/normalize/TestNormalize.java +++ b/test/jdk/java/foreign/normalize/TestNormalize.java @@ -21,13 +21,11 @@ * questions. */ /* * @test - * @enablePreview * @library ../ - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm * --enable-native-access=ALL-UNNAMED * -Xbatch * -XX:CompileCommand=dontinline,TestNormalize::doCall* * TestNormalize diff a/test/jdk/java/foreign/passheapsegment/TestPassHeapSegment.java b/test/jdk/java/foreign/passheapsegment/TestPassHeapSegment.java --- a/test/jdk/java/foreign/passheapsegment/TestPassHeapSegment.java +++ b/test/jdk/java/foreign/passheapsegment/TestPassHeapSegment.java @@ -21,13 +21,11 @@ * questions. */ /* * @test - * @enablePreview * @library ../ /test/lib - * @requires jdk.foreign.linker != "UNSUPPORTED" * @run testng/othervm --enable-native-access=ALL-UNNAMED TestPassHeapSegment */ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; diff a/test/jdk/java/foreign/stackwalk/TestAsyncStackWalk.java b/test/jdk/java/foreign/stackwalk/TestAsyncStackWalk.java --- a/test/jdk/java/foreign/stackwalk/TestAsyncStackWalk.java +++ b/test/jdk/java/foreign/stackwalk/TestAsyncStackWalk.java @@ -21,12 +21,10 @@ * questions. */ /* * @test id=default_gc - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc != "Z" * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -40,12 +38,10 @@ * TestAsyncStackWalk */ /* * @test id=ZSinglegen - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc.ZSinglegen * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -60,12 +56,10 @@ * TestAsyncStackWalk */ /* * @test id=ZGenerational - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc.ZGenerational * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -80,12 +74,10 @@ * TestAsyncStackWalk */ /* * @test id=shenandoah - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc.Shenandoah * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox diff a/test/jdk/java/foreign/stackwalk/TestReentrantUpcalls.java b/test/jdk/java/foreign/stackwalk/TestReentrantUpcalls.java --- a/test/jdk/java/foreign/stackwalk/TestReentrantUpcalls.java +++ b/test/jdk/java/foreign/stackwalk/TestReentrantUpcalls.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * diff a/test/jdk/java/foreign/stackwalk/TestStackWalk.java b/test/jdk/java/foreign/stackwalk/TestStackWalk.java --- a/test/jdk/java/foreign/stackwalk/TestStackWalk.java +++ b/test/jdk/java/foreign/stackwalk/TestStackWalk.java @@ -21,12 +21,10 @@ * questions. */ /* * @test id=default_gc - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc != "Z" * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -40,12 +38,10 @@ * TestStackWalk */ /* * @test id=ZSinglegen - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc.ZSinglegen * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -60,12 +56,10 @@ * TestStackWalk */ /* * @test id=ZGenerational - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc.ZGenerational * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox @@ -80,12 +74,10 @@ * TestStackWalk */ /* * @test id=shenandoah - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires vm.gc.Shenandoah * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox diff a/test/jdk/java/foreign/trivial/TestCritical.java b/test/jdk/java/foreign/trivial/TestCritical.java --- /dev/null +++ b/test/jdk/java/foreign/trivial/TestCritical.java @@ -0,0 +1,81 @@ +/* + * 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. + */ + +/* + * @test + * @library ../ /test/lib + * @run testng/othervm --enable-native-access=ALL-UNNAMED TestCritical + */ + +import org.testng.annotations.Test; + +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.StructLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; + +import static org.testng.Assert.assertEquals; + +public class TestCritical extends NativeTestHelper { + + static { + System.loadLibrary("Critical"); + } + + @Test + public void testEmpty() throws Throwable { + MethodHandle handle = downcallHandle("empty", FunctionDescriptor.ofVoid(), Linker.Option.critical()); + handle.invokeExact(); + } + + @Test + public void testIdentity() throws Throwable { + MethodHandle handle = downcallHandle("identity", FunctionDescriptor.of(C_INT, C_INT), Linker.Option.critical()); + int result = (int) handle.invokeExact(42); + assertEquals(result, 42); + } + + @Test + public void testWithReturnBuffer() throws Throwable { + StructLayout bigLayout = MemoryLayout.structLayout( + C_LONG_LONG.withName("x"), + C_LONG_LONG.withName("y")); + + MethodHandle handle = downcallHandle("with_return_buffer", FunctionDescriptor.of(bigLayout), Linker.Option.critical()); + VarHandle vhX = bigLayout.varHandle(MemoryLayout.PathElement.groupElement("x")); + VarHandle vhY = bigLayout.varHandle(MemoryLayout.PathElement.groupElement("y")); + try (Arena arena = Arena.ofConfined()) { + MemorySegment result = (MemorySegment) handle.invokeExact((SegmentAllocator) arena); + long x = (long) vhX.get(result, 0L); + assertEquals(x, 10); + long y = (long) vhY.get(result, 0L); + assertEquals(y, 11); + } + } + +} diff a/test/jdk/java/foreign/trivial/TestCriticalUpcall.java b/test/jdk/java/foreign/trivial/TestCriticalUpcall.java --- /dev/null +++ b/test/jdk/java/foreign/trivial/TestCriticalUpcall.java @@ -0,0 +1,62 @@ +/* + * 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. + */ + +/* + * @test + * @library ../ /test/lib + * @requires jdk.foreign.linker != "FALLBACK" + * @run testng/othervm --enable-native-access=ALL-UNNAMED TestCriticalUpcall + */ + +import org.testng.annotations.Test; + +import java.io.IOException; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; + +import static org.testng.Assert.fail; + +public class TestCriticalUpcall extends UpcallTestHelper { + + @Test + public void testUpcallFailure() throws IOException, InterruptedException { + // test to see if we catch a trivial downcall doing an upcall + runInNewProcess(Runner.class, true).assertStdOutContains("wrong thread state for upcall"); + } + + public static class Runner extends NativeTestHelper { + public static void main(String[] args) throws Throwable { + System.loadLibrary("Critical"); + + MethodHandle mh = downcallHandle("do_upcall", FunctionDescriptor.ofVoid(C_POINTER), Linker.Option.critical()); + MemorySegment stub = upcallStub(Runner.class, "target", FunctionDescriptor.ofVoid()); + mh.invokeExact(stub); + } + + public static void target() { + fail("Should not get here"); + } + } +} diff a/test/jdk/java/foreign/trivial/TestTrivial.java b/test/jdk/java/foreign/trivial/TestTrivial.java --- a/test/jdk/java/foreign/trivial/TestTrivial.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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. - */ - -/* - * @test - * @enablePreview - * @library ../ /test/lib - * @requires jdk.foreign.linker != "UNSUPPORTED" - * @run testng/othervm --enable-native-access=ALL-UNNAMED TestTrivial - */ - -import org.testng.annotations.Test; - -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.StructLayout; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.VarHandle; - -import static org.testng.Assert.assertEquals; - -public class TestTrivial extends NativeTestHelper { - - static { - System.loadLibrary("Trivial"); - } - - @Test - public void testEmpty() throws Throwable { - MethodHandle handle = downcallHandle("empty", FunctionDescriptor.ofVoid(), Linker.Option.isTrivial()); - handle.invokeExact(); - } - - @Test - public void testIdentity() throws Throwable { - MethodHandle handle = downcallHandle("identity", FunctionDescriptor.of(C_INT, C_INT), Linker.Option.isTrivial()); - int result = (int) handle.invokeExact(42); - assertEquals(result, 42); - } - - @Test - public void testWithReturnBuffer() throws Throwable { - StructLayout bigLayout = MemoryLayout.structLayout( - C_LONG_LONG.withName("x"), - C_LONG_LONG.withName("y")); - - MethodHandle handle = downcallHandle("with_return_buffer", FunctionDescriptor.of(bigLayout), Linker.Option.isTrivial()); - VarHandle vhX = bigLayout.varHandle(MemoryLayout.PathElement.groupElement("x")); - VarHandle vhY = bigLayout.varHandle(MemoryLayout.PathElement.groupElement("y")); - try (Arena arena = Arena.ofConfined()) { - MemorySegment result = (MemorySegment) handle.invokeExact((SegmentAllocator) arena); - long x = (long) vhX.get(result); - assertEquals(x, 10); - long y = (long) vhY.get(result); - assertEquals(y, 11); - } - } - - @Test - public void testCaptureErrno() throws Throwable { - Linker.Option ccs = Linker.Option.captureCallState("errno"); - MethodHandle handle = downcallHandle("capture_errno", FunctionDescriptor.ofVoid(C_INT), Linker.Option.isTrivial(), ccs); - StructLayout capturedStateLayout = Linker.Option.captureStateLayout(); - VarHandle errnoHandle = capturedStateLayout.varHandle(MemoryLayout.PathElement.groupElement("errno")); - try (Arena arena = Arena.ofConfined()) { - MemorySegment captureSeg = arena.allocate(capturedStateLayout); - handle.invokeExact(captureSeg, 42); - int capturedErrno = (int) errnoHandle.get(captureSeg); - assertEquals(capturedErrno, 42); - } - } - - -} diff a/test/jdk/java/foreign/trivial/TestTrivialUpcall.java b/test/jdk/java/foreign/trivial/TestTrivialUpcall.java --- a/test/jdk/java/foreign/trivial/TestTrivialUpcall.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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. - */ - -/* - * @test - * @enablePreview - * @library ../ /test/lib - * @requires jdk.foreign.linker != "UNSUPPORTED" - * @requires jdk.foreign.linker != "FALLBACK" - * @run testng/othervm --enable-native-access=ALL-UNNAMED TestTrivialUpcall - */ - -import org.testng.annotations.Test; - -import java.io.IOException; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.Linker; -import java.lang.foreign.MemorySegment; -import java.lang.invoke.MethodHandle; - -import static org.testng.Assert.fail; - -public class TestTrivialUpcall extends UpcallTestHelper { - - @Test - public void testUpcallFailure() throws IOException, InterruptedException { - // test to see if we catch a trivial downcall doing an upcall - runInNewProcess(Runner.class, true).assertStdOutContains("wrong thread state for upcall"); - } - - public static class Runner extends NativeTestHelper { - public static void main(String[] args) throws Throwable { - System.loadLibrary("Trivial"); - - MethodHandle mh = downcallHandle("do_upcall", FunctionDescriptor.ofVoid(C_POINTER), Linker.Option.isTrivial()); - MemorySegment stub = upcallStub(Runner.class, "target", FunctionDescriptor.ofVoid()); - mh.invokeExact(stub); - } - - public static void target() { - fail("Should not get here"); - } - } -} diff a/test/jdk/java/foreign/trivial/libTrivial.c b/test/jdk/java/foreign/trivial/libCritical.c --- a/test/jdk/java/foreign/trivial/libTrivial.c +++ b/test/jdk/java/foreign/trivial/libCritical.c @@ -46,12 +46,8 @@ b.x = 10; b.y = 11; return b; } -EXPORT void capture_errno(int value) { - errno = value; -} - EXPORT void do_upcall(void(*f)(void)) { f(); } diff a/test/jdk/java/foreign/upcalldeopt/TestUpcallDeopt.java b/test/jdk/java/foreign/upcalldeopt/TestUpcallDeopt.java --- a/test/jdk/java/foreign/upcalldeopt/TestUpcallDeopt.java +++ b/test/jdk/java/foreign/upcalldeopt/TestUpcallDeopt.java @@ -21,13 +21,11 @@ * questions. */ /* * @test id=default_gc - * @enablePreview * @bug 8277602 - * @requires jdk.foreign.linker != "UNSUPPORTED" * @library /test/lib * @library ../ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * diff a/test/jdk/java/foreign/virtual/TestVirtualCalls.java b/test/jdk/java/foreign/virtual/TestVirtualCalls.java --- a/test/jdk/java/foreign/virtual/TestVirtualCalls.java +++ b/test/jdk/java/foreign/virtual/TestVirtualCalls.java @@ -21,12 +21,10 @@ * questions. */ /* * @test - * @enablePreview - * @requires jdk.foreign.linker != "UNSUPPORTED" * @library ../ * @run testng/othervm * --enable-native-access=ALL-UNNAMED * TestVirtualCalls */ diff a/test/jdk/java/lang/Thread/jni/AttachCurrentThread/AttachTest.java b/test/jdk/java/lang/Thread/jni/AttachCurrentThread/AttachTest.java --- a/test/jdk/java/lang/Thread/jni/AttachCurrentThread/AttachTest.java +++ b/test/jdk/java/lang/Thread/jni/AttachCurrentThread/AttachTest.java @@ -36,14 +36,14 @@ /** * @test * @summary Test native threads attaching implicitly to the VM by means of an upcall * @requires (os.family == "linux" | os.family == "mac") & (sun.arch.data.model == "64") * @library /test/lib - * @compile --enable-preview -source ${jdk.version} ImplicitAttach.java - * @run main AttachTest --enable-preview --enable-native-access=ALL-UNNAMED ImplicitAttach 1 - * @run main AttachTest --enable-preview --enable-native-access=ALL-UNNAMED ImplicitAttach 2 - * @run main AttachTest --enable-preview --enable-native-access=ALL-UNNAMED ImplicitAttach 4 + * @compile -source ${jdk.version} ImplicitAttach.java + * @run main AttachTest --enable-native-access=ALL-UNNAMED ImplicitAttach 1 + * @run main AttachTest --enable-native-access=ALL-UNNAMED ImplicitAttach 2 + * @run main AttachTest --enable-native-access=ALL-UNNAMED ImplicitAttach 4 */ import java.util.stream.Stream; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; diff a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestExact.java b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestExact.java --- a/test/jdk/java/lang/invoke/VarHandles/VarHandleTestExact.java +++ b/test/jdk/java/lang/invoke/VarHandles/VarHandleTestExact.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules java.base/jdk.internal.access.foreign * @modules java.base/jdk.internal.foreign.layout * * @run testng/othervm -Xverify:all * -Djdk.internal.foreign.SHOULD_ADAPT_HANDLES=false @@ -169,11 +168,11 @@ ".*\\Qhandle's method type (ByteBuffer,int," + arrayClass.componentType().getSimpleName() + ")void \\E.*"); } @Test(dataProvider = "dataSetMemorySegment") public void testExactSegmentSet(Class carrier, Object testValue, SetSegmentX setter) { - VarHandle vh = MethodHandles.memorySegmentViewVarHandle(ValueLayouts.valueLayout(carrier, ByteOrder.nativeOrder())); + VarHandle vh = ValueLayouts.valueLayout(carrier, ByteOrder.nativeOrder()).varHandle(); try (Arena arena = Arena.ofConfined()) { MemorySegment seg = arena.allocate(8); doTest(vh, tvh -> tvh.set(seg, 0L, testValue), tvh -> setter.set(tvh, seg, 0L, testValue), diff a/test/jdk/java/nio/channels/FileChannel/LargeMapTest.java b/test/jdk/java/nio/channels/FileChannel/LargeMapTest.java --- a/test/jdk/java/nio/channels/FileChannel/LargeMapTest.java +++ b/test/jdk/java/nio/channels/FileChannel/LargeMapTest.java @@ -35,11 +35,10 @@ import static java.nio.file.StandardOpenOption.*; /* * @test - * @enablePreview * @bug 8286637 * @summary Ensure that memory mapping beyond 32-bit range does not cause an * EXCEPTION_ACCESS_VIOLATION. * @requires vm.bits == 64 * @library /test/lib diff a/test/jdk/java/nio/channels/FileChannel/MapToMemorySegmentTest.java b/test/jdk/java/nio/channels/FileChannel/MapToMemorySegmentTest.java --- a/test/jdk/java/nio/channels/FileChannel/MapToMemorySegmentTest.java +++ b/test/jdk/java/nio/channels/FileChannel/MapToMemorySegmentTest.java @@ -20,11 +20,10 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ /* @test - * @enablePreview * @bug 8281412 * @summary Test FileChannel::map to MemorySegment with custom file channel * @run testng/othervm MapToMemorySegmentTest */ diff a/test/jdk/java/util/stream/test/TEST.properties b/test/jdk/java/util/stream/test/TEST.properties --- a/test/jdk/java/util/stream/test/TEST.properties +++ b/test/jdk/java/util/stream/test/TEST.properties @@ -4,8 +4,5 @@ lib.dirs = /lib/testlibrary/bootlib # Tests that must run in othervm mode othervm.dirs= /java/util/stream - -# To compile and run tests that use the foreign memory access API -enablePreview=true diff a/test/jdk/jdk/incubator/vector/Byte128VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Byte128VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Byte128VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Byte128VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Byte128VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Byte256VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Byte256VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Byte256VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Byte256VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Byte256VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Byte512VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Byte512VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Byte512VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Byte512VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Byte512VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Byte64VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Byte64VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Byte64VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Byte64VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Byte64VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/ByteMaxVectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/ByteMaxVectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/ByteMaxVectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/ByteMaxVectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation ByteMaxVectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Double128VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Double128VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Double128VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Double128VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Double128VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Double256VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Double256VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Double256VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Double256VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Double256VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Double512VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Double512VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Double512VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Double512VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Double512VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Double64VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Double64VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Double64VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Double64VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Double64VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/DoubleMaxVectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/DoubleMaxVectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/DoubleMaxVectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/DoubleMaxVectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation DoubleMaxVectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Float128VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Float128VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Float128VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Float128VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Float128VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Float256VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Float256VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Float256VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Float256VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Float256VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Float512VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Float512VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Float512VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Float512VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Float512VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Float64VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Float64VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Float64VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Float64VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Float64VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/FloatMaxVectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/FloatMaxVectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/FloatMaxVectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/FloatMaxVectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation FloatMaxVectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Int128VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Int128VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Int128VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Int128VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Int128VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Int256VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Int256VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Int256VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Int256VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Int256VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Int512VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Int512VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Int512VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Int512VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Int512VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Int64VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Int64VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Int64VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Int64VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Int64VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/IntMaxVectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/IntMaxVectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/IntMaxVectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/IntMaxVectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation IntMaxVectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Long128VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Long128VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Long128VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Long128VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Long128VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Long256VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Long256VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Long256VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Long256VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Long256VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Long512VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Long512VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Long512VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Long512VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Long512VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Long64VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Long64VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Long64VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Long64VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Long64VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/LongMaxVectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/LongMaxVectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/LongMaxVectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/LongMaxVectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation LongMaxVectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Short128VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Short128VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Short128VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Short128VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Short128VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Short256VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Short256VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Short256VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Short256VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Short256VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Short512VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Short512VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Short512VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Short512VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Short512VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/Short64VectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/Short64VectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/Short64VectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/Short64VectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm -XX:-TieredCompilation Short64VectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/ShortMaxVectorLoadStoreTests.java b/test/jdk/jdk/incubator/vector/ShortMaxVectorLoadStoreTests.java --- a/test/jdk/jdk/incubator/vector/ShortMaxVectorLoadStoreTests.java +++ b/test/jdk/jdk/incubator/vector/ShortMaxVectorLoadStoreTests.java @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation ShortMaxVectorLoadStoreTests * */ diff a/test/jdk/jdk/incubator/vector/VectorReshapeTests.java b/test/jdk/jdk/incubator/vector/VectorReshapeTests.java --- a/test/jdk/jdk/incubator/vector/VectorReshapeTests.java +++ b/test/jdk/jdk/incubator/vector/VectorReshapeTests.java @@ -34,11 +34,10 @@ import jdk.incubator.vector.VectorShape; import jdk.incubator.vector.VectorSpecies; /** * @test - * @enablePreview * @modules jdk.incubator.vector * @modules java.base/jdk.internal.vm.annotation * @run testng/othervm/timeout=240 --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation VectorReshapeTests */ diff a/test/jdk/jdk/incubator/vector/templates/X-LoadStoreTest.java.template b/test/jdk/jdk/incubator/vector/templates/X-LoadStoreTest.java.template --- a/test/jdk/jdk/incubator/vector/templates/X-LoadStoreTest.java.template +++ b/test/jdk/jdk/incubator/vector/templates/X-LoadStoreTest.java.template @@ -21,11 +21,10 @@ * questions. */ /* * @test - * @enablePreview * @modules jdk.incubator.vector java.base/jdk.internal.vm.annotation #if[MaxBit] * @run testng/othervm --add-opens jdk.incubator.vector/jdk.incubator.vector=ALL-UNNAMED * -XX:-TieredCompilation $vectorteststype$ #else[MaxBit] diff a/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromSliceTest.java @@ -0,0 +1,80 @@ +/* + * 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.MemorySegment; +import java.util.Random; +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 AllocFromSliceTest extends CLayouts { + + @Param({"5", "20", "100", "500", "1000"}) + public int size; + public int start; + public byte[] arr; + + @Setup + public void setup() { + arr = new byte[1024]; + Random random = new Random(0); + random.nextBytes(arr); + start = random.nextInt(1024 - size); + } + + @Benchmark + public MemorySegment alloc_confined() { + Arena arena = Arena.ofConfined(); + MemorySegment segment = arena.allocate(size); + MemorySegment.copy(arr, start, segment, C_CHAR, 0, size); + arena.close(); + return segment; + } + + @Benchmark + public MemorySegment alloc_confined_slice() { + Arena arena = Arena.ofConfined(); + MemorySegment segment = arena.allocateFrom(C_CHAR, MemorySegment.ofArray(arr), C_CHAR, start, size); + arena.close(); + return segment; + } +} diff a/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/AllocFromTest.java @@ -0,0 +1,176 @@ +/* + * 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.MemorySegment; +import java.lang.foreign.MemorySegment.Scope; +import java.lang.foreign.SegmentAllocator; +import java.lang.foreign.ValueLayout; +import java.util.Random; +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 AllocFromTest extends CLayouts { + + Arena arena = Arena.ofConfined(); + + SlicingPool pool = new SlicingPool(); + + @Param({"5", "20", "100", "500", "1000"}) + public int size; + public byte[] arr; + + @Setup + public void setup() { + arr = new byte[size]; + Random random = new Random(0); + random.nextBytes(arr); + } + + @Benchmark + public MemorySegment alloc_confined() { + Arena arena = Arena.ofConfined(); + MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + arena.close(); + return segment; + } + + @Benchmark + public MemorySegment alloc_malloc_arena() { + MallocArena arena = new MallocArena(); + MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + arena.close(); + return segment; + } + + @Benchmark + public MemorySegment alloc_unsafe_arena() { + UnsafeArena arena = new UnsafeArena(); + MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + arena.close(); + return segment; + } + + @Benchmark + public MemorySegment alloc_pool_arena() { + Arena arena = pool.acquire(); + MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, arr); + arena.close(); + return segment; + } + + static class SlicingPool { + final MemorySegment pool = Arena.ofAuto().allocate(1024); + boolean isAcquired = false; + + public Arena acquire() { + if (isAcquired) { + throw new IllegalStateException("An allocator is already in use"); + } + isAcquired = true; + return new SlicingPoolAllocator(); + } + + class SlicingPoolAllocator implements Arena { + + final Arena arena = Arena.ofConfined(); + final SegmentAllocator slicing = SegmentAllocator.slicingAllocator(pool); + + public MemorySegment allocate(long byteSize, long byteAlignment) { + return slicing.allocate(byteSize, byteAlignment) + .reinterpret(arena, null); + } + + @Override + public Scope scope() { + return arena.scope(); + } + + public void close() { + isAcquired = false; + arena.close(); + } + } + } + + public static class MallocArena 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) { + return CLayouts.allocateMemory(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) { + return MemorySegment.ofAddress(Utils.unsafe.allocateMemory(byteSize)) + .reinterpret(byteSize, arena, ms -> Utils.unsafe.freeMemory(ms.address())); + } + } +} diff a/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/AllocTest.java --- /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 a/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java b/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/BulkOps.java @@ -49,11 +49,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class BulkOps { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/CLayouts.java b/test/micro/org/openjdk/bench/java/lang/foreign/CLayouts.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/CLayouts.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/CLayouts.java @@ -67,11 +67,11 @@ public static final ValueLayout.OfDouble C_DOUBLE = ValueLayout.JAVA_DOUBLE; /** * The {@code T*} native type. */ public static final AddressLayout C_POINTER = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(C_CHAR)); + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, C_CHAR)); private static Linker LINKER = Linker.nativeLinker(); private static final MethodHandle FREE = LINKER.downcallHandle( LINKER.defaultLookup().find("free").get(), FunctionDescriptor.ofVoid(C_POINTER)); diff a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadConstant.java b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadConstant.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadConstant.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadConstant.java @@ -39,11 +39,11 @@ @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", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class CallOverheadConstant { @Benchmark public void jni_blank() throws Throwable { blank(); @@ -53,12 +53,12 @@ public void panama_blank() throws Throwable { func.invokeExact(); } @Benchmark - public void panama_blank_trivial() throws Throwable { - func_trivial.invokeExact(); + public void panama_blank_critical() throws Throwable { + func_critical.invokeExact(); } @Benchmark public int jni_identity() throws Throwable { return identity(10); @@ -68,12 +68,12 @@ public int panama_identity() throws Throwable { return (int) identity.invokeExact(10); } @Benchmark - public int panama_identity_trivial() throws Throwable { - return (int) identity_trivial.invokeExact(10); + public int panama_identity_critical() throws Throwable { + return (int) identity_critical.invokeExact(10); } @Benchmark public MemorySegment panama_identity_struct_confined() throws Throwable { return (MemorySegment) identity_struct.invokeExact(recycling_allocator, confinedPoint); diff a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadHelper.java b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadHelper.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadHelper.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadHelper.java @@ -32,18 +32,18 @@ public class CallOverheadHelper extends CLayouts { static final Linker abi = Linker.nativeLinker(); static final MethodHandle func; - static final MethodHandle func_trivial; + static final MethodHandle func_critical; static final MethodHandle func_v; - static final MethodHandle func_trivial_v; + static final MethodHandle func_critical_v; static MemorySegment func_addr; static final MethodHandle identity; - static final MethodHandle identity_trivial; + static final MethodHandle identity_critical; static final MethodHandle identity_v; - static final MethodHandle identity_trivial_v; + static final MethodHandle identity_critical_v; static MemorySegment identity_addr; static final MethodHandle identity_struct; static final MethodHandle identity_struct_v; static MemorySegment identity_struct_addr; static final MethodHandle identity_struct_3; @@ -111,21 +111,21 @@ { func_addr = loaderLibs.find("func").orElseThrow(); MethodType mt = MethodType.methodType(void.class); FunctionDescriptor fd = FunctionDescriptor.ofVoid(); func_v = abi.downcallHandle(fd); - func_trivial_v = abi.downcallHandle(fd, Linker.Option.isTrivial()); + func_critical_v = abi.downcallHandle(fd, Linker.Option.critical()); func = insertArguments(func_v, 0, func_addr); - func_trivial = insertArguments(func_trivial_v, 0, func_addr); + func_critical = insertArguments(func_critical_v, 0, func_addr); } { identity_addr = loaderLibs.find("identity").orElseThrow(); FunctionDescriptor fd = FunctionDescriptor.of(C_INT, C_INT); identity_v = abi.downcallHandle(fd); - identity_trivial_v = abi.downcallHandle(fd, Linker.Option.isTrivial()); + identity_critical_v = abi.downcallHandle(fd, Linker.Option.critical()); identity = insertArguments(identity_v, 0, identity_addr); - identity_trivial = insertArguments(identity_trivial_v, 0, identity_addr); + identity_critical = insertArguments(identity_critical_v, 0, identity_addr); } identity_struct_addr = loaderLibs.find("identity_struct").orElseThrow(); identity_struct_v = abi.downcallHandle( FunctionDescriptor.of(POINT_LAYOUT, POINT_LAYOUT)); identity_struct = insertArguments(identity_struct_v, 0, identity_struct_addr); diff a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadVirtual.java b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadVirtual.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadVirtual.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadVirtual.java @@ -39,11 +39,11 @@ @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", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class CallOverheadVirtual { @Benchmark public void jni_blank() throws Throwable { blank(); @@ -53,12 +53,12 @@ public void panama_blank() throws Throwable { func_v.invokeExact(func_addr); } @Benchmark - public void panama_blank_trivial() throws Throwable { - func_trivial_v.invokeExact(func_addr); + public void panama_blank_critical() throws Throwable { + func_critical_v.invokeExact(func_addr); } @Benchmark public int jni_identity() throws Throwable { return identity(10); @@ -107,12 +107,12 @@ public int panama_identity() throws Throwable { return (int) identity_v.invokeExact(identity_addr, 10); } @Benchmark - public int panama_identity_trivial() throws Throwable { - return (int) identity_trivial_v.invokeExact(identity_addr, 10); + public int panama_identity_critical() throws Throwable { + return (int) identity_critical_v.invokeExact(identity_addr, 10); } @Benchmark public MemorySegment panama_identity_struct() throws Throwable { return (MemorySegment) identity_struct_v.invokeExact(identity_struct_addr, recycling_allocator, point); diff a/test/micro/org/openjdk/bench/java/lang/foreign/InternalStrLen.java b/test/micro/org/openjdk/bench/java/lang/foreign/InternalStrLen.java --- /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 a/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java b/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/JavaLayouts.java @@ -22,18 +22,29 @@ */ package org.openjdk.bench.java.lang.foreign; import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; -import static java.lang.foreign.ValueLayout.*; +import static java.lang.foreign.ValueLayout.JAVA_INT; +import static java.lang.foreign.ValueLayout.JAVA_INT_UNALIGNED; +import static java.lang.foreign.ValueLayout.JAVA_LONG; +import static java.lang.foreign.ValueLayout.JAVA_LONG_UNALIGNED; /** - * Some useful Java {@link ValueLayout} and associated {@link ValueLayout#arrayElementVarHandle(int...)} var handles. + * Some useful Java {@link ValueLayout} and associated array var handles. */ public class JavaLayouts { - static final VarHandle VH_INT_UNALIGNED = JAVA_INT_UNALIGNED.arrayElementVarHandle(); + static final VarHandle VH_INT_UNALIGNED = arrayVarHandle(JAVA_INT_UNALIGNED); + static final VarHandle VH_INT = arrayVarHandle(JAVA_INT); - static final VarHandle VH_INT = JAVA_INT.arrayElementVarHandle(); + static final VarHandle VH_LONG_UNALIGNED = arrayVarHandle(JAVA_LONG_UNALIGNED); + static final VarHandle VH_LONG = arrayVarHandle(JAVA_LONG); + + private static VarHandle arrayVarHandle(ValueLayout layout) { + return MethodHandles.collectCoordinates(layout.varHandle(), + 1, MethodHandles.insertArguments(layout.scaleHandle(), 0, 0L)); + } } diff a/test/micro/org/openjdk/bench/java/lang/foreign/LinkUpcall.java b/test/micro/org/openjdk/bench/java/lang/foreign/LinkUpcall.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LinkUpcall.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LinkUpcall.java @@ -45,11 +45,11 @@ @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(Scope.Benchmark) @OutputTimeUnit(TimeUnit.MICROSECONDS) -@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class LinkUpcall extends CLayouts { static final Linker LINKER = Linker.nativeLinker(); static final MethodHandle BLANK; static final FunctionDescriptor BLANK_DESC = FunctionDescriptor.ofVoid(); diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverConstant.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverConstant.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverConstant.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverConstant.java @@ -46,11 +46,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverConstant extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNew.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNew.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNew.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNew.java @@ -45,11 +45,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverNew extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNewHeap.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNewHeap.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNewHeap.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNewHeap.java @@ -45,11 +45,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverNewHeap extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstant.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstant.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstant.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstant.java @@ -46,11 +46,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = { "--enable-preview", "--enable-native-access=ALL-UNNAMED" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class LoopOverNonConstant extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantFP.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantFP.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantFP.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantFP.java @@ -46,11 +46,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverNonConstantFP { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantHeap.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantHeap.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantHeap.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantHeap.java @@ -47,11 +47,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverNonConstantHeap extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantMapped.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantMapped.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantMapped.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantMapped.java @@ -53,11 +53,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverNonConstantMapped extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantShared.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantShared.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantShared.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverNonConstantShared.java @@ -46,11 +46,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverNonConstantShared extends JavaLayouts { static final Unsafe unsafe = Utils.unsafe; static final int ELEM_SIZE = 1_000_000; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverOfAddress.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverOfAddress.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverOfAddress.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverOfAddress.java @@ -41,11 +41,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverOfAddress extends JavaLayouts { static final int ITERATIONS = 1_000_000; @Benchmark diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedBuffer.java @@ -45,11 +45,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverPollutedBuffer { static final int ELEM_SIZE = 1_000_000; static final int CARRIER_SIZE = (int) JAVA_INT.byteSize(); static final int ALLOC_SIZE = ELEM_SIZE * CARRIER_SIZE; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedSegments.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedSegments.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedSegments.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverPollutedSegments.java @@ -44,11 +44,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class LoopOverPollutedSegments extends JavaLayouts { static final int ELEM_SIZE = 1_000_000; static final int CARRIER_SIZE = (int) JAVA_INT.byteSize(); static final int ALLOC_SIZE = ELEM_SIZE * CARRIER_SIZE; diff a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverSlice.java b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverSlice.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverSlice.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/LoopOverSlice.java @@ -47,11 +47,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = { "--enable-preview", "--enable-native-access=ALL-UNNAMED" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class LoopOverSlice { static final int ELEM_SIZE = 1_000_000; static final int CARRIER_SIZE = (int)JAVA_INT.byteSize(); diff a/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentCopyUnsafe.java b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentCopyUnsafe.java --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentCopyUnsafe.java @@ -0,0 +1,73 @@ +/* + * 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 java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +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.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import sun.misc.Unsafe; + +import java.util.concurrent.TimeUnit; + +import static java.lang.foreign.ValueLayout.*; + +@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 MemorySegmentCopyUnsafe { + + static final Unsafe UNSAFE = Utils.unsafe; + + long src; + long dst; + + @Setup + public void setup() throws Throwable { + src = Arena.global().allocate(JAVA_INT).address(); + dst = Arena.global().allocate(JAVA_INT).address(); + } + + @Benchmark + public void panama() { + MemorySegment srcSeg = MemorySegment.ofAddress(src).reinterpret(JAVA_INT.byteSize()); + MemorySegment dstSeg = MemorySegment.ofAddress(dst).reinterpret(JAVA_INT.byteSize()); + dstSeg.copyFrom(srcSeg); + } + + @Benchmark + public void unsafe() { + UNSAFE.copyMemory(src, dst, JAVA_INT.byteSize()); + } +} diff a/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentGetUnsafe.java b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentGetUnsafe.java --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentGetUnsafe.java @@ -0,0 +1,96 @@ +/* + * 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 java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; + +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.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import sun.misc.Unsafe; + +import java.util.concurrent.TimeUnit; + +import static java.lang.foreign.ValueLayout.*; + +@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 MemorySegmentGetUnsafe { + + static final Unsafe UNSAFE = Utils.unsafe; + static final MethodHandle OF_ADDRESS_UNSAFE; + + static { + try { + OF_ADDRESS_UNSAFE = MethodHandles.lookup().findStatic(MemorySegmentGetUnsafe.class, + "ofAddressUnsafe", MethodType.methodType(MemorySegment.class, long.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + static final VarHandle INT_HANDLE = adaptSegmentHandle(JAVA_INT.varHandle()); + + static VarHandle adaptSegmentHandle(VarHandle handle) { + handle = MethodHandles.insertCoordinates(handle, 1, 0L); + handle = MethodHandles.filterCoordinates(handle, 0, OF_ADDRESS_UNSAFE); + return handle; + } + + static MemorySegment ofAddressUnsafe(long address) { + return MemorySegment.ofAddress(address).reinterpret(JAVA_INT.byteSize()); + } + + long addr; + + @Setup + public void setup() throws Throwable { + addr = Arena.global().allocate(JAVA_INT).address(); + } + + @Benchmark + public int panama() { + return (int) INT_HANDLE.get(addr); + } + + @Benchmark + public int unsafe() { + return UNSAFE.getInt(addr); + } +} diff a/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentVsBits.java b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentVsBits.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentVsBits.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySegmentVsBits.java @@ -56,11 +56,11 @@ @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", "--enable-preview"}) +@Fork(value = 3, jvmArgsAppend = {"--enable-native-access=ALL-UNNAMED"}) public class MemorySegmentVsBits { public static final VarHandle LONG_ARRAY_VH = MethodHandles.byteArrayViewVarHandle(long[].class, BIG_ENDIAN); Arena arena = Arena.ofConfined(); diff a/test/micro/org/openjdk/bench/java/lang/foreign/MemorySessionClose.java b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySessionClose.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/MemorySessionClose.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/MemorySessionClose.java @@ -44,11 +44,11 @@ @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.MICROSECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class MemorySessionClose { static final int ALLOC_SIZE = 1024; public enum StressMode { diff a/test/micro/org/openjdk/bench/java/lang/foreign/ParallelSum.java b/test/micro/org/openjdk/bench/java/lang/foreign/ParallelSum.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/ParallelSum.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/ParallelSum.java @@ -50,11 +50,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class ParallelSum extends JavaLayouts { final static int CARRIER_SIZE = 4; final static int ALLOC_SIZE = CARRIER_SIZE * 1024 * 1024 * 256; final static int ELEM_SIZE = ALLOC_SIZE / CARRIER_SIZE; diff a/test/micro/org/openjdk/bench/java/lang/foreign/PointerInvoke.java b/test/micro/org/openjdk/bench/java/lang/foreign/PointerInvoke.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/PointerInvoke.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/PointerInvoke.java @@ -41,11 +41,11 @@ @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", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class PointerInvoke extends CLayouts { Arena arena = Arena.ofConfined(); MemorySegment segment = arena.allocate(100, 1); diff a/test/micro/org/openjdk/bench/java/lang/foreign/QSort.java b/test/micro/org/openjdk/bench/java/lang/foreign/QSort.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/QSort.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/QSort.java @@ -44,11 +44,11 @@ @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", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class QSort extends CLayouts { static final Linker abi = Linker.nativeLinker(); static final MethodHandle clib_qsort; static final MemorySegment native_compar; diff a/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/StrLenTest.java @@ -46,11 +46,11 @@ @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", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class StrLenTest extends CLayouts { Arena arena = Arena.ofConfined(); SegmentAllocator segmentAllocator; @@ -90,31 +90,31 @@ } @Benchmark public int panama_strlen() throws Throwable { try (Arena arena = Arena.ofConfined()) { - MemorySegment segment = arena.allocateUtf8String(str); + MemorySegment segment = arena.allocateFrom(str); return (int)STRLEN.invokeExact(segment); } } @Benchmark public int panama_strlen_ring() throws Throwable { - return (int)STRLEN.invokeExact(arenaAllocator.allocateUtf8String(str)); + return (int)STRLEN.invokeExact(arenaAllocator.allocateFrom(str)); } @Benchmark public int panama_strlen_pool() throws Throwable { Arena arena = pool.acquire(); - int l = (int) STRLEN.invokeExact(arena.allocateUtf8String(str)); + int l = (int) STRLEN.invokeExact(arena.allocateFrom(str)); arena.close(); return l; } @Benchmark public int panama_strlen_prefix() throws Throwable { - return (int)STRLEN.invokeExact(segmentAllocator.allocateUtf8String(str)); + return (int)STRLEN.invokeExact(segmentAllocator.allocateFrom(str)); } @Benchmark public int panama_strlen_unsafe() throws Throwable { MemorySegment address = makeStringUnsafe(str); @@ -160,11 +160,11 @@ public MemorySegment allocate(long byteSize, long byteAlignment) { if (rem < byteSize) { reset(); } MemorySegment res = current.allocate(byteSize, byteAlignment); - long lastOffset = segment.segmentOffset(res) + res.byteSize(); + long lastOffset = res.address() - segment.address() + res.byteSize(); rem = segment.byteSize() - lastOffset; return res; } void reset() { diff a/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java b/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/TestAdaptVarHandles.java @@ -45,11 +45,11 @@ @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.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) public class TestAdaptVarHandles extends JavaLayouts { static class IntBox { private final int value; diff a/test/micro/org/openjdk/bench/java/lang/foreign/TestLoadBytes.java b/test/micro/org/openjdk/bench/java/lang/foreign/TestLoadBytes.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/TestLoadBytes.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/TestLoadBytes.java @@ -48,12 +48,11 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(value = 1, jvmArgsAppend = { "-Dforeign.restricted=permit", - "--enable-native-access", "ALL-UNNAMED", - "--enable-preview"}) + "--enable-native-access", "ALL-UNNAMED"}) public class TestLoadBytes { @Param("1024") private int size; private byte[] srcArray; diff a/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/ToCStringTest.java --- /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 a/test/micro/org/openjdk/bench/java/lang/foreign/ToJavaStringTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/ToJavaStringTest.java --- /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 a/test/micro/org/openjdk/bench/java/lang/foreign/UnrolledAccess.java b/test/micro/org/openjdk/bench/java/lang/foreign/UnrolledAccess.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/UnrolledAccess.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/UnrolledAccess.java @@ -38,19 +38,15 @@ @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.MICROSECONDS) -@Fork(value = 3, jvmArgsAppend = { "--enable-preview", "--enable-native-access=ALL-UNNAMED" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class UnrolledAccess extends JavaLayouts { static final Unsafe U = Utils.unsafe; - static final VarHandle VH_LONG_UNALIGNED = JAVA_LONG_UNALIGNED.arrayElementVarHandle(); - - static final VarHandle VH_LONG = JAVA_LONG.arrayElementVarHandle(); - final static int SIZE = 1024; @State(Scope.Benchmark) public static class Data { diff a/test/micro/org/openjdk/bench/java/lang/foreign/Upcalls.java b/test/micro/org/openjdk/bench/java/lang/foreign/Upcalls.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/Upcalls.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/Upcalls.java @@ -43,11 +43,11 @@ @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", "--enable-preview" }) +@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED" }) public class Upcalls extends CLayouts { static final Linker abi = Linker.nativeLinker(); static final MethodHandle blank; static final MethodHandle identity; diff a/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java b/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/VarHandleExact.java @@ -45,18 +45,18 @@ @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-preview") +@Fork(3) public class VarHandleExact { static final VarHandle exact; static final VarHandle generic; static { - generic = MethodHandles.memorySegmentViewVarHandle(JAVA_INT); + generic = JAVA_INT.varHandle(); exact = generic.withInvokeExactBehavior(); } Arena arena; MemorySegment data; diff a/test/micro/org/openjdk/bench/java/lang/foreign/libToCString.c b/test/micro/org/openjdk/bench/java/lang/foreign/libToCString.c --- /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 a/test/micro/org/openjdk/bench/java/lang/foreign/libToJavaString.c b/test/micro/org/openjdk/bench/java/lang/foreign/libToJavaString.c --- /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); +} diff a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/NativeType.java b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/NativeType.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/NativeType.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/NativeType.java @@ -38,11 +38,11 @@ public non-sealed static abstract class OfDouble extends NativeType { public abstract ValueLayout.OfDouble layout(); } private static final AddressLayout UNSAFE_ADDRESS = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(ValueLayout.JAVA_BYTE)); + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE)); public final static class OfPointer extends NativeType { public AddressLayout layout() { return UNSAFE_ADDRESS; } diff a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/Pointer.java b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/Pointer.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/Pointer.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/Pointer.java @@ -76,11 +76,11 @@ MemorySegment segment = allocator.allocate(type.layout()); return new Pointer<>(segment); } public static Pointer allocate(NativeType type, long size, SegmentAllocator allocator) { - MemorySegment segment = allocator.allocateArray(type.layout(), size); + MemorySegment segment = allocator.allocate(type.layout(), size); return new Pointer<>(segment); } public static Pointer wrap(NativeType type, MemorySegment segment) { return new Pointer<>(segment); diff a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/pointers/PointerBench.java @@ -44,11 +44,11 @@ @BenchmarkMode(Mode.AverageTime) @Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MILLISECONDS) @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @OutputTimeUnit(TimeUnit.MILLISECONDS) -@Fork(value = 3, jvmArgsAppend = "--enable-preview") +@Fork(3) @State(Scope.Benchmark) public class PointerBench { final Arena arena = Arena.ofConfined(); static final int ELEM_SIZE = 1_000_000; @@ -58,11 +58,11 @@ MemorySegment intSegment = intPointer.segment(); MemorySegment intPointerSegment = intPointerPointer.segment(); MemorySegment pointSegment = pointPointer.segment(); public static final AddressLayout UNSAFE_ADDRESS = ValueLayout.ADDRESS - .withTargetLayout(MemoryLayout.sequenceLayout(ValueLayout.JAVA_BYTE)); + .withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, ValueLayout.JAVA_BYTE)); @Setup public void setup() { for (int i = 0 ; i < ELEM_SIZE ; i++) { intSegment.setAtIndex(ValueLayout.JAVA_INT, i, i); diff a/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java b/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java --- a/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java +++ b/test/micro/org/openjdk/bench/java/lang/foreign/points/support/PanamaPoint.java @@ -66,23 +66,23 @@ setX(x); setY(y); } public void setX(int x) { - VH_x.set(segment, x); + VH_x.set(segment, 0L, x); } public int getX() { - return (int) VH_x.get(segment); + return (int) VH_x.get(segment, 0L); } public void setY(int y) { - VH_y.set(segment, y); + VH_y.set(segment, 0L, y); } public int getY() { - return (int) VH_y.get(segment); + return (int) VH_y.get(segment, 0L); } public double distanceTo(PanamaPoint other) { try { return (double) MH_distance.invokeExact(segment, other.segment); diff a/test/micro/org/openjdk/bench/jdk/incubator/vector/MemorySegmentVectorAccess.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/MemorySegmentVectorAccess.java --- a/test/micro/org/openjdk/bench/jdk/incubator/vector/MemorySegmentVectorAccess.java +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/MemorySegmentVectorAccess.java @@ -48,11 +48,10 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(value = 1, jvmArgsAppend = { "--add-modules=jdk.incubator.vector", - "--enable-preview", "--enable-native-access", "ALL-UNNAMED"}) public class MemorySegmentVectorAccess { private static final VectorSpecies SPECIES = VectorSpecies.ofLargestShape(byte.class); @Param("1024") diff a/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreBytes.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreBytes.java --- a/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreBytes.java +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreBytes.java @@ -47,11 +47,10 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(value = 1, jvmArgsAppend = { "--add-modules=jdk.incubator.vector", - "--enable-preview", "--enable-native-access", "ALL-UNNAMED", "-Djdk.incubator.vector.VECTOR_ACCESS_OOB_CHECK=1"}) public class TestLoadStoreBytes { private static final VectorSpecies SPECIES = VectorSpecies.ofLargestShape(byte.class); diff a/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreShorts.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreShorts.java --- a/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreShorts.java +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/TestLoadStoreShorts.java @@ -50,11 +50,10 @@ @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) @State(org.openjdk.jmh.annotations.Scope.Thread) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Fork(value = 1, jvmArgsAppend = { "--add-modules=jdk.incubator.vector", - "--enable-preview", "--enable-native-access", "ALL-UNNAMED"}) public class TestLoadStoreShorts { private static final VectorSpecies SPECIES = VectorSpecies.ofLargestShape(short.class); @Param("256")