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=24
 
 [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
 warning=issuestitle,binary
 
 [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=.*
@@ -18,19 +18,15 @@
 [checks "whitespace"]
 files=.*\.cpp|.*\.hpp|.*\.c|.*\.h|.*\.java|.*\.cc|.*\.hh|.*\.m|.*\.mm|.*\.S|.*\.md|.*\.properties|.*\.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 <https://openjdk.org/> for more information about the OpenJDK
 Community and the JDK and see <https://bugs.openjdk.org> 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.md b/doc/panama_ffi.md
--- /dev/null
+++ b/doc/panama_ffi.md
@@ -0,0 +1,376 @@
+## State of foreign function support
+
+**December 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 <em>unsafe</em>. 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<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.
+
+```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, Linker.Option... options);
+    MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options);
+    ... // some overloads omitted here
+
+    static Linker nativeLinker() { ... }
+}
+```
+
+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. 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 `FunctionDescriptor` defines the layouts associated with the parameter types and return type (if any) of the C function.
+
+Scalar C types such as `bool`, `int` are modeled as value layouts of a suitable carrier. Which layout is used to model a C type can vary, depending on the data model supported by a given ABI. For instance, the C type `long` maps to the layout constant `ValueLayout::JAVA_LONG` on Linux/x64, but maps to the layout constant `ValueLayout::JAVA_INT` on Windows/x64. The `Linker` provides a method, namely `Linker::canonicalLayouts` to allow clients to discover the mapping between C types and memory layouts programmatically:
+
+```java
+MemoryLayout SIZE_T = Linker.nativeLinker().canonicalLayouts().get("size_t");
+```
+
+Composite types are modeled as group layouts. More specifically, a C struct type maps to a `StructLayout`, whereas a C `union` type maps to a `UnionLayout`. 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 must be modeled explicitly, by adding an adequately sized padding layout member to the resulting struct layout.
+
+Finally, pointer types such as `int**`, and `int(*)(size_t*, size_t*)` are modeled as address layouts. When the spatial bounds of the pointer type are known statically, the address layout can be associated with a *target layout*. For instance, a pointer that is known to point to a C `int[2]` array can be modelled as follows:
+
+```java
+ValueLayout.ADDRESS.withTargetLayout(
+        MemoryLayout.sequenceLayout(2,
+                                    Linker.nativeLinker().canonicalLayouts().get("int")));
+```
+
+For more exhaustive examples of mappings between C types and layouts, please refer to the [appendix](#c-types-mapping-in-linuxx64). In the following sections, we will assume Linux/x64 as our target platform.
+
+> Note: the [jextract](https://github.com/openjdk/jextract) tool can 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](#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.allocateFrom("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.allocateFrom("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<a href="#1"><sup>1</sup></a>; 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 = arena.allocateFrom(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<a href="#2"><sup>2</sup></a> 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.allocateFrom("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
+}
+```
+
+While this works, and provides optimal performance, it has some limitations<a href="#3"><sup>3</sup></a>:
+
+* 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
+
+##### C types mapping in Linux/x64
+
+| 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*`<br />`int**`<br /> ...                               | `ADDRESS`                                                    | `MemorySegment` |
+| `struct Point { int x; int y; };`<br />`union Choice { float a; int b; };`<br />... | `MemoryLayout.structLayout(...)`<br />`MemoryLayout.unionLayout(...)`<br /> | `MemorySegment` |
+
+##### 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);
+        }
+    }
+}
+```
+
+* <a id="1"/>(<sup>1</sup>):<small> 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.</small>
+* <a id="2"/>(<sup>2</sup>):<small> Linker options can be used to customize the linkage request in various ways, for instance to allow clients to pass heap segments to native functions without copying, to remove Java to native thread transitions and to save the state of special runtime variables (such as `errno`). </small>
+* <a id="3"/>(<sup>3</sup>):<small> 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. </small>
diff a/doc/panama_memaccess.md b/doc/panama_memaccess.md
--- /dev/null
+++ b/doc/panama_memaccess.md
@@ -0,0 +1,258 @@
+## State of foreign memory support
+
+**December 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 operation<a href="#1"><sup>1</sup></a> 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::elements` 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 matches 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 <a href="#2"><sup>2</sup></a>:
+
+```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, 0L /* base offset */, (long)i /* index */);
+    int y = (int)yHandle.get(segment, 0L /* base offset */, (long)i /* index */);
+}
+```
+
+In the above, `xHandle` and `yHandle` are two var handle instances whose type is `int` and which takes three access coordinates:
+
+1. a `MemorySegment` instance; the segment whose memory should be dereferenced
+2. a *base offset*, which indicates the portions of the memory segment to be accessed; this is typically left to zero (as above), but can be useful when combining memory access var handles (see below);
+3. 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)
+
+In other words, the offset of the access operation can be expressed as follows:
+
+```java
+offset = baseOffset + (index * JAVA_INT.byteSize());
+```
+
+Or, equivalently, using the `MemoryLayout::scale` method, as:
+
+```java
+offset = JAVA_INT.scale(baseOffset, index);
+```
+
+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 <a href="#3"><sup>3</sup></a>.
+
+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 = JAVA_INT.varHandle(); // (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 the var handles obtained from the sequence layout in the previous section can be in fact derived from  the simple memory access var handle we have constructed here. That is, var handles can be adapted and turned into more complex var handles, using var handle *combinators*. Developers familiar with the method handle API 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 = JAVA_INT.varHandle(); // (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.allocateFrom(ValueLayout.JAVA_INT, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
+    MemorySegment nativeString  = offHeap.allocateFrom("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.allocateFrom(JAVA_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.allocateFrom(JAVA_INT, 1, 2, 3, 4, 5);
+         ...
+     }
+} // all memory allocated is released here
+```
+
+* <a id="1"/>(<sup>1</sup>):<small> 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.</small>
+* <a id="2"/>(<sup>2</sup>):<small> 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.</small>
+* <a id="3"/>(<sup>3</sup>):<small> Clients can enforce stricter type checking when interacting with `VarHandle` instances, by obtaining an *exact* var handle, using the `VarHandle::withInvokeExactBehavior` method.</small>
+
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,11 +195,11 @@
             // 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<ArgumentClassImpl>[] groups = new List[nEightbytes];
         for (MemoryLayout l : group.memberLayouts()) {
             groupByEightBytes(l, offset, groups);
             if (group instanceof StructLayout) {
                 offset += l.byteSize();
