1 ## State of foreign function support 2 3 **January 2022** 4 5 **Maurizio Cimadamore** 6 7 Panama supports foreign functions through the Foreign Linker API, which has been available as an [incubating](https://openjdk.java.net/jeps/11) API since Java [16](https://openjdk.java.net/jeps/389). The central abstraction in the Foreign Linker API is the *foreign linker*, which 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, Panama foreign function support is completely expressed in terms of Java code and no intermediate native code is required. 8 9 ### Native addresses 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 our interop support are modeled using the `MemoryAddress` abstraction. 14 15 If clients want to dereference `MemoryAddress`, they can do so *unsafely* in two ways. First, they can use one of the *unsafe* dereference methods provided by `MemoryAddress` (these methods closely mirror those offered by `MemorySegment`); these methods are *restricted* and can only be called if the caller module has been listed in the `--enable-native-access` command-line flag: 16 17 ```java 18 ... 19 MemoryAddress addr = ... //obtain address from native code 20 int x = addr.get(JAVA_INT, 0); 21 ``` 22 23 Alternatively, the client can create a memory segment from an address *unsafely*, using the `MemorySegment::ofAddress` factory (which is also a *restricted* method); this can also be useful to inject extra knowledge about spatial bounds which might be available in the native library the client is interacting with: 24 25 ```java 26 MemoryAddress addr = ... //obtain address from native code 27 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 28 MemorySegment segment = MemorySegment.ofAddress(100, scope); 29 int x = segment.get(JAVA_INT, 0); 30 } 31 ``` 32 33 Both `MemoryAddress` and `MemorySegment` implement the `Addressable` interface, which is an interface modelling entities that can be passed *by reference* — that is, which can be projected to a `MemoryAddress` instance. In the case of `MemoryAddress` such a projection is the identity function; in the case of a memory segment, the projection returns the `MemoryAddress` instance for the segment's base address. This abstraction allows to pass either memory address or memory segments where an address is expected (this is especially useful when generating native bindings). 34 35 ### Segment allocators 36 37 Idiomatic C code implicitly relies on stack allocation to allow for concise variable declarations; consider this example: 38 39 ```c 40 int arr[] = { 0, 1, 2, 3, 4 }; 41 ``` 42 43 A variable initializer such as the one above can be implemented as follows, using the Foreign Memory Access API: 44 45 ```java 46 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 47 MemorySegment arr = MemorySegment.allocateNative(MemoryLayout.sequenceLayout(5, JAVA_INT), scope); 48 for (int i = 0 ; i < 5 ; i++) { 49 arr.setAtIndex(JAVA_INT, i, i); 50 } 51 } 52 ``` 53 54 There are a number of issues with the above code snippet: 55 56 * compared to the C code, it is more verbose — the native array has to be initialized *element by element* 57 * 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 58 * when having multiple declarations like the one above, it might become increasingly harder to manage the lifecycle of the various segments 59 60 To address these problems, Panama provides a `SegmentAllocator` abstraction, a functional interface which provides methods to allocate commonly used values. Conveniently, the above code can be rewritten as follows: 61 62 ```java 63 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 64 SegmentAllocator allocator = SegmentAllocator.nativeAllocator(scope); 65 MemorySegment arr = allocator.allocateArray(JAVA_INT, new int[] { 0, 1, 2, 3, 4 }); 66 } // 'arr' is released here 67 ``` 68 69 In the above code, the resource scope is used to build a *native* allocator (that is, an allocator built on top of `MemorySegment::allocateNative`). The segment allocator 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 which performed the allocation, meaning that the segment will no longer be accessible after the try-with-resource construct. 70 71 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 instance, it is possible to create an arena-based allocator, as follows: 72 73 ```java 74 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 75 SegmentAllocator allocator = SegmentAllocator.newNativeArena(scope); 76 for (int i = 0 ; i < 100 ; i++) { 77 allocator.allocateArray(JAVA_INT, new int[] { 0, 1, 2, 3, 4 }); 78 } 79 ... 80 } // all memory allocated is released here 81 ``` 82 83 The above code creates a confined scope; inside the *try-with-resources*, a new unbounded arena allocation is created, associated with the existing scope. The allocator will pre-allocate a native segment, of a specific size, and respond to allocation requests by returning different slices of the pre-allocated segment. If the pre-allocated segment does not have sufficient space to accommodate a new allocation request, a new segment will be allocated. If the scope associated with the arena allocator is closed, all memory segments created by the allocator (see the body of the `for` loop) will be deallocated at once. This idiom combines the advantages of deterministic deallocation (provided by the Memory Access API) with a more flexible and scalable allocation scheme, and can be very useful when writing large applications. 84 85 For these reasons, all the methods in the Foreign Linker API which *produce* memory segments (see `VaList::nextVarg`), allow an optional allocator to be provided by user code — this is key in ensuring that an application using the Foreign Linker API achieves optimal allocation performances, especially in non-trivial use cases. 86 87 ### Symbol lookups 88 89 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 2 different ways <a href="#1"><sup>1</sup></a>: 90 91 * `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`) 92 * By obtaining a `CLinker` instance. In fact, `CLinker` implements the `SymbolLookup` interface, and can be used to look up platform-specific symbols in the standard C library. 93 94 Once a lookup has been obtained, a client can use it to retrieve handles to library symbols (either global variables or functions) using the `lookup(String)` method, which returns an `Optional<NativeSymbol>`. `NativeSymbol` is a class used to model references to library symbols, which implements the `Addressable` interface. 95 96 For instance, the following code can be used to look up the `clang_getClangVersion` function provided by the `clang` library: 97 98 ```java 99 System.loadLibrary("clang"); 100 NativeSymbol clangVersion = SymbolLookup.loaderLookup().lookup("clang_getClangVersion").get(); 101 ``` 102 103 ### C Linker 104 105 At the core of Panama foreign function support we find the `CLinker` abstraction. This abstraction plays a dual role: first, for downcalls, it allows modelling native function calls as plain `MethodHandle` calls (see `CLinker::downcallHandle`); second, for upcalls, it allows to convert an existing `MethodHandle` (which might point to some Java method) into a `NativeSymbol` which could then be passed to native functions as a function pointer (see `CLinker::upcallStub`): 106 107 ```java 108 interface CLinker { 109 MethodHandle downcallHandle(NativeSymbol func, FunctionDescriptor function); 110 NativeSymbol upcallStub(MethodHandle target, FunctionDescriptor function, ResourceScope scope); 111 ... // some overloads omitted here 112 113 static CLinker systemCLinker() { ... } 114 } 115 ``` 116 117 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. 118 119 The following table shows the mapping between C types, layouts and Java carriers under the Linux/macOS foreign 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: 120 121 | C type | Layout | Java carrier | 122 | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------------- | 123 | `bool` | `JAVA_BOOLEAN` | `byte` | 124 | `char` | `JAVA_BYTE` | `byte` | 125 | `short` | `JAVA_SHORT` | `short`, `char` | 126 | `int` | `JAVA_INT` | `int` | 127 | `long` | `JAVA_LONG` | `long` | 128 | `long long` | `JAVA_LONG` | `long` | 129 | `float` | `JAVA_FLOAT` | `float` | 130 | `double` | `JAVA_DOUBLE` | `double` | 131 | `char*`<br />`int**`<br /> ... | `ADDRESS` | `Addressable`<br />`MemoryAddress` | 132 | `struct Point { int x; int y; };`<br />`union Choice { float a; int b; };`<br />... | `MemoryLayout.structLayout(...)`<br />`MemoryLayout.unionLayout(...)`<br /> | `MemorySegment` | 133 134 Note that all C pointer types are modelled using the `ADDRESS` layout constant; the Java carrier type associated with this layout is either `Addressable` or `MemoryAddress` depending on where the layout occurs in the function descriptor. For downcall method handles, for instance, the `Addressable` carrier is used when the `ADDRESS` layout occurs in a parameter position of the corresponding function descriptor. This maximizes applicability of a downcall method handles, ensuring that any implementation of `Addressable` (e.g. memory segments, memory address, upcall stubs, va lists) can be passed where a pointer is expected. 135 136 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. 137 138 ### Downcalls 139 140 We will now look at how foreign functions can be called from Java using the foreign linker abstraction. Assume we wanted to call the following function from the standard C library: 141 142 ```c 143 size_t strlen(const char *s); 144 ``` 145 146 In order to do that, we have to: 147 148 * lookup the `strlen` symbol 149 * describe the signature of the C function using a function descriptor 150 151 * create a *downcall* native method handle with the above information, using the standard C foreign linker 152 153 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)): 154 155 ```java 156 CLinker linker = CLinker.systemCLinker(); 157 MethodHandle strlen = linker.downcallHandle( 158 linker.lookup("strlen").get(), 159 FunctionDescriptor.of(JAVA_LONG, ADDRESS) 160 ); 161 ``` 162 163 Note that, since the function `strlen` is part of the standard C library, which is loaded with the VM, we can just use the system lookup 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. 164 165 Once we have obtained the downcall method handle, we can just use it as any other method handle<a href="#2"><sup>2</sup></a>: 166 167 ```java 168 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 169 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 170 long len = strlen.invoke(malloc.allocateUtf8String("Hello")); // 5 171 } 172 ``` 173 174 Here we are using a native segment allocator 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 explicit resource scope to control the lifecycle of the allocated C string, which ensures timely deallocation of the memory segment holding the native string. 175 176 The `CLinker` interface also supports linking of native functions without an address known at link time; when that happens, an address (of type `Addressable`) 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: 177 178 ```java 179 MethodHandle strlen_virtual = linker.downcallHandle( // address parameter missing! 180 FunctionDescriptor.of(JAVA_LONG, ADDRESS) 181 ); 182 183 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 184 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 185 long len = strlen_virtual.invoke( 186 linker.lookup("strlen").get() // address provided here! 187 malloc.allocateUtf8String("Hello") 188 ); // 5 189 } 190 ``` 191 192 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 Panama 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 Panama runtime has to *trust* the function descriptor passed in<a href="#3"><sup>3</sup></a>; for this reason, access to the foreign linker is a restricted operation, which can only be performed if the requesting module is listed in the `--enable-native-access` command-line flag. 193 194 If a native function returns a raw pointer (of type `MemoryAddress`), it is then up to the client to make sure that the address is being accessed and disposed of correctly, compatibly with the requirements of the underlying native library. If a native function returns a struct by value, a *fresh*, memory segment is allocated off-heap and returned to the caller. In such cases, the downcall method handle will feature an additional prefix `SegmentAllocator` (see above) parameter which will be used by the downcall method handle to allocate the returned segment. The allocation will likely associate the segment with a *resource scope* that is known to the caller and which can then be used to release the memory associated with that segment. 195 196 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. 197 198 ### Upcalls 199 200 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: 201 202 ```c 203 void qsort(void *base, size_t nmemb, size_t size, 204 int (*compar)(const void *, const void *)); 205 ``` 206 207 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: 208 209 ```java 210 CLinker linker = CLinker.systemCLinker(); 211 MethodHandle qsort = linker.downcallHandle( 212 CLinker.systemLookup().lookup("qsort").get(), 213 FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) 214 ); 215 ``` 216 217 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). 218 219 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): 220 221 ```java 222 class Qsort { 223 static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) { 224 return addr1.get(JAVA_INT, 0) - addr2.get(JAVA_INT, 0); 225 } 226 } 227 ``` 228 229 Here we can see that the function is performing some *unsafe* dereference of the pointer contents. 230 231 Now let's create a method handle pointing to the comparator function above: 232 233 ```java 234 FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); 235 MethodHandle comparHandle = MethodHandles.lookup() 236 .findStatic(Qsort.class, "qsortCompare", 237 CLinker.upcallType(comparDesc)); 238 ``` 239 240 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: 241 242 ```java 243 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 244 NativeSymbol comparFunc = linker.upcallStub( 245 comparHandle, comparDesc, scope); 246 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 247 MemorySegment array = malloc.allocateArray(new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 })); 248 qsort.invoke(array, 10L, 4L, comparFunc); 249 int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 250 } 251 ``` 252 253 The above code creates an upcall stub — `comparFunc` — a function pointer that can be used to invoke our Java comparator function, of type `NativeSymbol`. The upcall stub is associated with the provided resource scope instance; this means that the stub will be uninstalled when the resource scope is closed. 254 255 The snippet then creates an off-heap array from a Java array (using a `SegmentAllocator`), 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. 256 257 ### Varargs 258 259 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: 260 261 ```c 262 int printf(const char *format, ...); 263 ``` 264 265 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. 266 267 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: 268 269 ```C 270 printf("%d plus %d equals %d", 2, 2, 4); 271 ``` 272 273 To do this using the foreign function support provided by Panama we would have to build a *specialized* downcall handle for that call shape, using the `FunctionDescriptor::asVariadic` to inject additional variadic layouts, as follows: 274 275 ```java 276 CLinker linker = CLinker.systemCLinker(); 277 MethodHandle printf = linker.downcallHandle( 278 linker.lookup("printf").get(), 279 FunctionDescriptor.of(JAVA_INT, ADDRESS).asVariadic(JAVA_INT, JAVA_INT, JAVA_INT) 280 ); 281 ``` 282 283 Then we can call the specialized downcall handle as usual: 284 285 ```java 286 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 287 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 288 printf.invoke(malloc.allocateUtf8String("%d plus %d equals %d"), 2, 2, 4); //prints "2 plus 2 equals 4" 289 } 290 ``` 291 292 While this works, and provides optimal performance, there are some drawbacks: 293 294 * If the variadic function needs to be called with many shapes, we have to create many downcall handles 295 * 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. 296 297 To add flexibility, the standard C foreign linker comes equipped with support for C variable argument lists — or `va_list`. When a variadic function is called, C code has to unpack the variadic arguments by creating a `va_list` structure, and then accessing the variadic arguments through the `va_list` one by one (using the `va_arg` macro). To facilitate interop between standard variadic functions and `va_list` many C library functions in fact define *two* flavors of the same function, one using standard variadic signature, one using an extra `va_list` parameter. For instance, in the case of `printf` we can find that a `va_list`-accepting function performing the same task is also defined: 298 299 ```c 300 int vprintf(const char *format, va_list ap); 301 ``` 302 303 The behavior of this function is the same as before — the only difference is that the ellipsis notation `...` has been replaced with a single `va_list` parameter; in other words, the function is no longer variadic. 304 305 It is indeed fairly easy to create a downcall for `vprintf`: 306 307 ```java 308 CLinker linker = CLinker.systemCLinker(); 309 MethodHandle vprintf = linker.downcallHandle( 310 linker.lookup("vprintf").get(), 311 FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS)); 312 ``` 313 314 Here, the layout of a `va_list` parameter is simply `ADDRESS` (as va lists are passed by reference). To call the `vprintf` handle we need to create an instance of `VaList` which contains the arguments we want to pass to the `vprintf` function — we can do so, as follows: 315 316 ```java 317 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 318 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 319 vprintf.invoke( 320 malloc.allocateUtf8String("%d plus %d equals %d", scope), 321 VaList.make(builder -> 322 builder.addVarg(JAVA_INT, 2) 323 .addVarg(JAVA_INT, 2) 324 .addVarg(JAVA_INT, 4), scope) 325 ); //prints "2 plus 2 equals 4" 326 ``` 327 328 While the callee has to do more work to call the `vprintf` handle, note that that now we're back in a place where the downcall handle `vprintf` can be shared across multiple callees. Note that both the format string and the `VaList` are associated with the given resource scope — this means that both will remain valid throughout the native function call. 329 330 Using `VaList` also scales to upcall stubs — it is therefore possible for clients to create upcalls stubs which take a `VaList` and then, from the Java upcall, read the arguments packed inside the `VaList` one by one using the methods provided by the `VaList` API (e.g. `VaList::nextVarg(ValueLayout.OfInt)`), which mimics the behavior of the C `va_arg` macro. 331 332 ### Appendix: full source code 333 334 The full source code containing most of the code shown throughout this document can be seen below: 335 336 ```java 337 import jdk.incubator.foreign.Addressable; 338 import jdk.incubator.foreign.CLinker; 339 import jdk.incubator.foreign.FunctionDescriptor; 340 import jdk.incubator.foreign.SymbolLookup; 341 import jdk.incubator.foreign.MemoryAddress; 342 import jdk.incubator.foreign.MemorySegment; 343 import jdk.incubator.foreign.NativeSymbol; 344 import jdk.incubator.foreign.ResourceScope; 345 import jdk.incubator.foreign.SegmentAllocator; 346 import jdk.incubator.foreign.VaList; 347 348 import java.lang.invoke.MethodHandle; 349 import java.lang.invoke.MethodHandles; 350 import java.lang.invoke.MethodType; 351 import java.util.Arrays; 352 353 import static jdk.incubator.foreign.ValueLayout.*; 354 355 public class Examples { 356 357 static CLinker LINKER = CLinker.systemCLinker(); 358 359 public static void main(String[] args) throws Throwable { 360 strlen(); 361 strlen_virtual(); 362 qsort(); 363 printf(); 364 vprintf(); 365 } 366 367 public static void strlen() throws Throwable { 368 MethodHandle strlen = LINKER.downcallHandle( 369 LINKER.lookup("strlen").get(), 370 FunctionDescriptor.of(JAVA_LONG, ADDRESS) 371 ); 372 373 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 374 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 375 MemorySegment hello = malloc.allocateUtf8String("Hello"); 376 long len = (long) strlen.invoke(hello); // 5 377 System.out.println(len); 378 } 379 } 380 381 public static void strlen_virtual() throws Throwable { 382 MethodHandle strlen_virtual = LINKER.downcallHandle( 383 FunctionDescriptor.of(JAVA_LONG, ADDRESS) 384 ); 385 386 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 387 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 388 MemorySegment hello = malloc.allocateUtf8String("Hello"); 389 long len = (long) strlen_virtual.invoke( 390 LINKER.lookup("strlen").get(), 391 hello); // 5 392 System.out.println(len); 393 } 394 } 395 396 static class Qsort { 397 static int qsortCompare(MemoryAddress addr1, MemoryAddress addr2) { 398 return addr1.get(JAVA_INT, 0) - addr2.get(JAVA_INT, 0); 399 } 400 } 401 402 public static void qsort() throws Throwable { 403 MethodHandle qsort = LINKER.downcallHandle( 404 LINKER.lookup("qsort").get(), 405 FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) 406 ); 407 FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS); 408 MethodHandle comparHandle = MethodHandles.lookup() 409 .findStatic(Qsort.class, "qsortCompare", 410 CLinker.upcallType(comparDesc)); 411 412 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 413 NativeSymbol comparFunc = LINKER.upcallStub( 414 comparHandle, comparDesc, scope); 415 416 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 417 MemorySegment array = malloc.allocateArray(JAVA_INT, new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 }); 418 qsort.invoke(array, 10L, 4L, comparFunc); 419 int[] sorted = array.toArray(JAVA_INT); // [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 420 System.out.println(Arrays.toString(sorted)); 421 } 422 } 423 424 public static void printf() throws Throwable { 425 MethodHandle printf = LINKER.downcallHandle( 426 LINKER.lookup("printf").get(), 427 FunctionDescriptor.of(JAVA_INT, ADDRESS).asVariadic(JAVA_INT, JAVA_INT, JAVA_INT) 428 ); 429 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 430 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 431 MemorySegment s = malloc.allocateUtf8String("%d plus %d equals %d\n"); 432 printf.invoke(s, 2, 2, 4); 433 } 434 } 435 436 public static void vprintf() throws Throwable { 437 438 MethodHandle vprintf = LINKER.downcallHandle( 439 LINKER.lookup("vprintf").get(), 440 FunctionDescriptor.of(JAVA_INT, ADDRESS, ADDRESS)); 441 442 try (ResourceScope scope = ResourceScope.newConfinedScope()) { 443 SegmentAllocator malloc = SegmentAllocator.nativeAllocator(scope); 444 MemorySegment s = malloc.allocateUtf8String("%d plus %d equals %d\n"); 445 VaList vlist = VaList.make(builder -> 446 builder.addVarg(JAVA_INT, 2) 447 .addVarg(JAVA_INT, 2) 448 .addVarg(JAVA_INT, 4), scope); 449 vprintf.invoke(s, vlist); 450 } 451 } 452 } 453 ``` 454 455 456 457 * <a id="1"/>(<sup>1</sup>):<small> Users might add more ways to obtain a symbol lookup — for instance: `SymbolLookup libraryLookup(String libName, ResourceScope scope)`. This would allow developers to load a library and associate its lifecycle with a `ResourceScope` (rather than a classloader). That is, when the scope is closed, the library will be unloaded. This is possible, as `CLinker` guarantees that memory addresses used by a downcall method handle cannot be released while the downcall method handle is being invoked.</small> 458 * <a id="2"/>(<sup>2</sup>):<small> For simplicity, the examples shown in this document use `MethodHandle::invoke` rather than `MethodHandle::invokeExact`; by doing so we avoid having to cast by-reference arguments back to `Addressable`. With `invokeExact` the method handle invocation should be rewritten as `strlen.invokeExact((Addressable)malloc.allocateUtf8String("Hello"));`</small> 459 * <a id="3"/>(<sup>3</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>