1 ## State of foreign function support
  2 
  3 **December 2023**
  4 
  5 **Maurizio Cimadamore**
  6 
  7 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.
  8 
  9 ### Zero-length memory segments
 10 
 11 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.
 12 
 13 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*.
 14 
 15 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:
 16 
 17 ```java
 18 MemorySegment foreign = someSegment.get(ValueLayout.ADDRESS, 0); // size = 0
 19                                    .reinterpret(4)               // size = 4
 20 int x = foreign.get(ValueLayout.JAVA_INT, 0);                    // ok
 21 ```
 22 
 23 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:
 24 
 25 ```java
 26 MemorySegment foreign = null;
 27 try (Arena arena = Arena.ofConfined()) {
 28       foreign = someSegment.get(ValueLayout.ADDRESS, 0)           // size = 0, scope = always alive
 29                            .reinterpret(4, arena, null);          // size = 4, scope = arena.scope()
 30       int x = foreign.get(ValueLayout.JAVA_INT, 0);               // ok
 31 }
 32 int x = foreign.get(ValueLayout.JAVA_INT, 0); // throws IllegalStateException
 33 ```
 34 
 35 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.
 36 
 37 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()`:
 38 ```java
 39 MemorySegment foreign = someSegment.get(ValueLayout.ADDRESS.withTargetLayout(JAVA_INT), 0); // size = 4
 40 int x = foreign.get(ValueLayout.JAVA_INT, 0);                                               // ok
 41 ```
 42 
 43 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.
 44 
 45 > 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.
 46 
 47 ### Symbol lookups
 48 
 49 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:
 50 
 51 * `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;
 52 * `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`)
 53 * `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`).
 54 
 55 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.
 56 
 57 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.
 58 
 59 ```java
 60 try (Arena arena = Arena.ofConfined()) {
 61     SymbolLookup libclang = SymbolLookup.libraryLookup("libclang.so", arena);
 62     MemorySegment clangVersion = libclang.find("clang_getClangVersion").get();
 63 }
 64 ```
 65 
 66 ### Linker
 67 
 68 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`):
 69 
 70 ```java
 71 interface Linker {
 72     MethodHandle downcallHandle(MemorySegment symbol, FunctionDescriptor function, Linker.Option... options);
 73     MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options);
 74     ... // some overloads omitted here
 75 
 76     static Linker nativeLinker() { ... }
 77 }
 78 ```
 79 
 80 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.
 81 
 82 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:
 83 
 84 ```java
 85 MemoryLayout SIZE_T = Linker.nativeLinker().canonicalLayouts().get("size_t");
 86 ```
 87 
 88 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.
 89 
 90 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:
 91 
 92 ```java
 93 ValueLayout.ADDRESS.withTargetLayout(
 94         MemoryLayout.sequenceLayout(2,
 95                                     Linker.nativeLinker().canonicalLayouts().get("int")));
 96 ```
 97 
 98 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.
 99 
100 > 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.
101 
102 ### Downcalls
103 
104 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:
105 
106 ```c
107 size_t strlen(const char *s);
108 ```
109 
110 In order to do that, we have to:
111 
112 * lookup the `strlen` symbol
113 * describe the signature of the C function using a function descriptor
114 * create a *downcall* native method handle with the above information, using the native linker
115 
116 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)):
117 
118 ```java
119 Linker linker = Linker.nativeLinker();
120 MethodHandle strlen = linker.downcallHandle(
121         linker.defaultLookup().find("strlen").get(),
122         FunctionDescriptor.of(JAVA_LONG, ADDRESS)
123 );
124 ```
125 
126 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.
127 
128 Once we have obtained the downcall method handle, we can just use it as any other method handle:
129 
130 ```java
131 try (Arena arena = Arena.ofConfined()) {
132     long len = strlen.invokeExact(arena.allocateFrom("Hello")); // 5
133 }
134 ```
135 
136 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.
137 
138 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:
139 
140 ```java
141 MethodHandle strlen_virtual = linker.downcallHandle( // address parameter missing!
142 		FunctionDescriptor.of(JAVA_LONG, ADDRESS)
143 );
144 
145 try (Arena arena = Arena.ofConfined()) {
146     long len = strlen_virtual.invokeExact(
147         linker.defaultLookup().find("strlen").get() // address provided here!
148         arena.allocateFrom("Hello")
149     ); // 5
150 }
151 ```
152 
153 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.
154 
155 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.
156 
157 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.
158 
159 ### Upcalls
160 
161 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:
162 
163 ```c
164 void qsort(void *base, size_t nmemb, size_t size,
165            int (*compar)(const void *, const void *));
166 ```
167 
168 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:
169 
170 ```java
171 Linker linker = Linker.nativeLinker();
172 MethodHandle qsort = linker.downcallHandle(
173 		linker.defaultLookup().lookup("qsort").get(),
174         FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS)
175 );
176 ```
177 
178 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).
179 
180 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):
181 
182 ```java
183 class Qsort {
184     static int qsortCompare(MemorySegment elem1, MemorySegmet elem2) {
185         return elem1.get(JAVA_INT, 0) - elem2.get(JAVA_INT, 0);
186     }
187 }
188 ```
189 
190 Here we can see that the function is performing some *unsafe* dereference of the pointer contents.
191 
192 Now let's create a method handle pointing to the comparator function above:
193 
194 ```java
195 FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT,
196                                                       ADDRESS.withTargetLayout(JAVA_INT),
197                                                       ADDRESS.withTargetLayout(JAVA_INT));
198 MethodHandle comparHandle = MethodHandles.lookup()
199                                          .findStatic(Qsort.class, "qsortCompare",
200                                                      comparDesc.toMethodType());
201 ```
202 
203 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:
204 
205 ```java
206 try (Arena arena = Arena.ofConfined()) {
207     MemorySegment comparFunc = linker.upcallStub(comparHandle, comparDesc, arena);
208     MemorySegment array = arena.allocateFrom(0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
209     qsort.invokeExact(array, 10L, 4L, comparFunc);
210     int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
211 }
212 ```
213 
214 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.
215 
216 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.
217 
218 ### Variadic calls
219 
220 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:
221 
222 ```c
223 int printf(const char *format, ...);
224 ```
225 
226 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.
227 
228 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:
229 
230 ```c
231 printf("%d plus %d equals %d", 2, 2, 4);
232 ```
233 
234 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:
235 
236 ```java
237 Linker linker = Linker.nativeLinker();
238 MethodHandle printf = linker.downcallHandle(
239 		linker.defaultLookup().lookup("printf").get(),
240         FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT)
241         Linker.Option.firstVariadicArg(1) // first int is variadic
242 );
243 ```
244 
245 Then we can call the specialized downcall handle as usual:
246 
247 ```java
248 try (Arena arena = Arena.ofConfined()) {
249     int res = (int)printf.invokeExact(arena.allocateFrom("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4"
250 }
251 ```
252 
253 While this works, and provides optimal performance, it has some limitations<a href="#3"><sup>3</sup></a>:
254 
255 * If the variadic function needs to be called with many shapes, we have to create many downcall handles
256 * 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.
257 
258 ### Appendix
259 
260 ##### C types mapping in Linux/x64
261 
262 | C type                                                       | Layout                                                       | Java carrier    |
263 | ------------------------------------------------------------ | ------------------------------------------------------------ | --------------- |
264 | `bool`                                                       | `JAVA_BOOLEAN`                                               | `byte`          |
265 | `char`                                                       | `JAVA_BYTE`                                                  | `byte`          |
266 | `short`                                                      | `JAVA_SHORT`                                                 | `short`, `char` |
267 | `int`                                                        | `JAVA_INT`                                                   | `int`           |
268 | `long`                                                       | `JAVA_LONG`                                                  | `long`          |
269 | `long long`                                                  | `JAVA_LONG`                                                  | `long`          |
270 | `float`                                                      | `JAVA_FLOAT`                                                 | `float`         |
271 | `double`                                                     | `JAVA_DOUBLE`                                                | `double`        |
272 | `char*`<br />`int**`<br /> ...                               | `ADDRESS`                                                    | `MemorySegment` |
273 | `struct Point { int x; int y; };`<br />`union Choice { float a; int b; };`<br />... | `MemoryLayout.structLayout(...)`<br />`MemoryLayout.unionLayout(...)`<br /> | `MemorySegment` |
274 
275 ##### Full source code
276 
277 The full source code containing most of the code shown throughout this document can be seen below:
278 
279 ```java
280 import java.lang.foreign.Arena;
281 import java.lang.foreign.Linker;
282 import java.lang.foreign.FunctionDescriptor;
283 import java.lang.foreign.SymbolLookup;
284 import java.lang.foreign.MemorySegment;
285 
286 import java.lang.invoke.MethodHandle;
287 import java.lang.invoke.MethodHandles;
288 import java.util.Arrays;
289 
290 import static java.lang.foreign.ValueLayout.*;
291 
292 public class Examples {
293 
294     static Linker LINKER = Linker.nativeLinker();
295     static SymbolLookup STDLIB = LINKER.defaultLookup();
296 
297     public static void main(String[] args) throws Throwable {
298         strlen();
299         strlen_virtual();
300         qsort();
301         printf();
302     }
303 
304     public static void strlen() throws Throwable {
305         MethodHandle strlen = LINKER.downcallHandle(
306                 STDLIB.find("strlen").get(),
307                 FunctionDescriptor.of(JAVA_LONG, ADDRESS)
308         );
309 
310         try (Arena arena = Arena.ofConfined()) {
311             MemorySegment hello = arena.allocateFrom("Hello");
312             long len = (long) strlen.invokeExact(hello); // 5
313             System.out.println(len);
314         }
315     }
316 
317     public static void strlen_virtual() throws Throwable {
318         MethodHandle strlen_virtual = LINKER.downcallHandle(
319                 FunctionDescriptor.of(JAVA_LONG, ADDRESS)
320         );
321 
322         try (Arena arena = Arena.ofConfined()) {
323             MemorySegment hello = arena.allocateFrom("Hello");
324             long len = (long) strlen_virtual.invokeExact(
325                     STDLIB.find("strlen").get(),
326                     hello); // 5
327             System.out.println(len);
328         }
329     }
330 
331     static class Qsort {
332         static int qsortCompare(MemorySegment addr1, MemorySegment addr2) {
333             return addr1.get(JAVA_INT, 0) - addr2.get(JAVA_INT, 0);
334         }
335     }
336 
337     public static void qsort() throws Throwable {
338         MethodHandle qsort = LINKER.downcallHandle(
339                 STDLIB.find("qsort").get(),
340                 FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS)
341         );
342         FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT,
343                 ADDRESS.withTargetLayout(JAVA_INT),
344                 ADDRESS.withTargetLayout(JAVA_INT));
345         MethodHandle comparHandle = MethodHandles.lookup()
346                 .findStatic(Qsort.class, "qsortCompare",
347                         comparDesc.toMethodType());
348 
349         try (Arena arena = Arena.ofConfined()) {
350             MemorySegment comparFunc = LINKER.upcallStub(
351                     comparHandle, comparDesc, arena);
352 
353             MemorySegment array = arena.allocateFrom(JAVA_INT, 0, 9, 3, 4, 6, 5, 1, 8, 2, 7);
354             qsort.invokeExact(array, 10L, 4L, comparFunc);
355             int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
356             System.out.println(Arrays.toString(sorted));
357         }
358     }
359 
360     public static void printf() throws Throwable {
361         MethodHandle printf = LINKER.downcallHandle(
362                 STDLIB.find("printf").get(),
363                 FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT),
364                 Linker.Option.firstVariadicArg(1) // first int is variadic
365         );
366         try (Arena arena = Arena.ofConfined()) {
367             MemorySegment s = arena.allocateFrom("%d plus %d equals %d\n");
368             int res = (int) printf.invokeExact(s, 2, 2, 4);
369         }
370     }
371 }
372 ```
373 
374 * <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>
375 * <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>
376 * <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>