Foreign functions typically reside in libraries that can be loaded on demand. Each library conforms to a specific ABI (Application Binary Interface). An ABI is a set of calling conventions and data types associated with the compiler, OS, and processor where the library was built. For example, a C compiler on Linux/x64 usually builds libraries that conform to the SystemV ABI.
A linker has detailed knowledge of the calling conventions and data types used by a specific ABI. For any library that conforms to that ABI, the linker can mediate between Java code running in the JVM and foreign functions in the library. In particular:
size_t type. On 64-bit platforms,
this canonical layout might be equal to ValueLayout#JAVA_LONG. The canonical
layouts supported by a linker are exposed via the canonicalLayouts() method,
which returns a map from type names to canonical layouts.
In addition, a linker provides a way to look up foreign functions in libraries that
conform to the ABI. Each linker chooses a set of libraries that are commonly used on
the OS and processor combination associated with the ABI. For example, a linker for
Linux/x64 might choose two libraries: libc and libm. The functions in
these libraries are exposed via a symbol lookup.
strlen function defined in the standard C library:
size_t strlen(const char *s);
strlen is obtained, using the native
linker, as follows:
Linker linker = Linker.nativeLinker(); MethodHandle strlen = linker.downcallHandle( linker.defaultLookup().findOrThrow("strlen"), FunctionDescriptor.of(JAVA_LONG, ADDRESS) );
strlen native
function. That address is then passed, along with a platform-dependent description
of the signature of the function expressed as a FunctionDescriptor (more on
that below) to the native linker's downcallHandle(MemorySegment, FunctionDescriptor, Option...)
method. The obtained downcall method handle is then invoked as follows:
try (Arena arena = Arena.ofConfined()) { MemorySegment str = arena.allocateFrom("Hello"); long len = (long) strlen.invokeExact(str); // 5 }
function descriptor, 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. The
mapping between a scalar type and its corresponding
canonical layout is dependent on the ABI implemented by the native linker (see below).
Composite types are modeled as group layouts. More
specifically, a C struct type maps to a struct layout,
whereas a C union type maps to a union layout. When defining
a struct or union layout, clients must pay attention to the size and alignment constraint
of the corresponding composite type definition in C. For instance, padding between two
struct fields 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 modeled as an address layout
whose target layout is a sequence layout whose element count is 2, and whose
element type is ValueLayout#JAVA_INT.
All native linker implementations are guaranteed to provide canonical layouts for the following set of types:
boolcharshortintlonglong longfloatdoublesize_twchar_tvoid*long maps to the layout constant ValueLayout#JAVA_LONG on Linux/x64,
but maps to the layout constant ValueLayout#JAVA_INT on Windows/x64.
Similarly, the C type size_t maps to the layout constant
ValueLayout#JAVA_LONG on 64-bit platforms, but maps to the layout constant
ValueLayout#JAVA_INT on 32-bit platforms.
A native linker typically does not provide canonical layouts for C's unsigned integral
types. Instead, they are modeled using the canonical layouts associated with their
corresponding signed integral types. For instance, the C type unsigned 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 following table shows some examples of how C types are modeled in Linux/x64 according to the "System V Application Binary Interface" (all the examples provided here will assume these platform-dependent mappings):
C type Layout Java type boolValueLayout#JAVA_BOOLEANbooleanchar
unsigned charValueLayout#JAVA_BYTEbyteshort
unsigned shortValueLayout#JAVA_SHORTshortint
unsigned intValueLayout#JAVA_INTintlong
unsigned longValueLayout#JAVA_LONGlonglong long
unsigned long longValueLayout#JAVA_LONGlongfloatValueLayout#JAVA_FLOATfloatdoubleValueLayout#JAVA_DOUBLEdoublesize_tValueLayout#JAVA_LONGlongchar*,int**,struct Point*ValueLayout#ADDRESSMemorySegmentint (*ptr)[10]ValueLayout.ADDRESS.withTargetLayout( MemoryLayout.sequenceLayout(10, ValueLayout.JAVA_INT) );MemorySegmentstruct Point { int x; long y; };MemoryLayout.structLayout( ValueLayout.JAVA_INT.withName("x"), MemoryLayout.paddingLayout(4), ValueLayout.JAVA_LONG.withName("y") );MemorySegmentunion Choice { float a; int b; }MemoryLayout.unionLayout( ValueLayout.JAVA_FLOAT.withName("a"), ValueLayout.JAVA_INT.withName("b") );MemorySegment
A native linker only supports function descriptors whose argument/return layouts are well-formed layouts. More formally, a layout `L` is well-formed if:
L is a value layout and L is derived from a canonical layout
C such that L.byteAlignment() <= C.byteAlignment()L is a sequence layout S and all the following conditions hold:
L.byteAlignment() is equal to the sequence layout's natural alignment
, andS.elementLayout() is a well-formed layout.L is a group layout G and all the following conditions hold:
G.byteAlignment() is equal to the group layout's natural alignmentG.byteSize() is a multiple of G.byteAlignment()G.memberLayouts() is either a padding layout or a
well-formed layoutE in G.memberLayouts() follows an
optional padding member layout, whose size is the minimum size required to
align EG contains an optional trailing padding member layout, whose size is the
minimum size that satisfies (2)A function descriptor is well-formed if its argument and return layouts are well-formed and are not sequence layouts. A native linker is guaranteed to reject function descriptors that are not well-formed. However, a native linker can still reject well-formed function descriptors, according to platform-specific rules. For example, some native linkers may reject packed struct layouts -- struct layouts whose member layouts feature relaxed alignment constraints, to avoid the insertion of additional padding.
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
qsort function can be used to sort the contents of an array, using a
custom comparator function which is passed as a function pointer
(the compar parameter). To be able to call the qsort function from
Java, we must first create a downcall method handle for it, as follows:
Linker linker = Linker.nativeLinker(); MethodHandle qsort = linker.downcallHandle( linker.defaultLookup().findOrThrow("qsort"), FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG, ADDRESS) );
ValueLayout#JAVA_LONG to map the C type size_t type,
and ValueLayout#ADDRESS for both the first pointer parameter (the array
pointer) and the last parameter (the function pointer).
To invoke the qsort downcall handle obtained above, we need a function pointer
to be passed as the last parameter. That is, we need to create a function pointer out
of an existing method handle. First, let's write a Java method that can compare two
int elements passed as pointers (i.e. as memory segments):
class Qsort { static int qsortCompare(MemorySegment elem1, MemorySegment elem2) { return Integer.compare(elem1.get(JAVA_INT, 0), elem2.get(JAVA_INT, 0)); } }
FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT, ADDRESS.withTargetLayout(JAVA_INT), ADDRESS.withTargetLayout(JAVA_INT)); MethodHandle comparHandle = MethodHandles.lookup() .findStatic(Qsort.class, "qsortCompare", comparDesc.toMethodType());
int[] array, we can specify ValueLayout#JAVA_INT as the target
layout for the address layouts of both parameters. This will allow the comparator
method to access the contents of the array elements to be compared. We then
turn that function descriptor into
a suitable method type which we then use to
look up the comparator method handle. We can now create an upcall stub that points to
that method, and pass it, as a function pointer, to the qsort downcall handle,
as follows:
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 ] }
qsort method handle along with the comparator
function we obtained from the native linker. After the invocation, the contents
of the off-heap array will be sorted according to our comparator function, written in
Java. We then extract a new Java array from the segment, which contains the sorted
elements.
void *malloc(size_t size);
malloc function allocates a region of memory with the given size,
and returns a pointer to that region of memory, which is later deallocated using
another function from the C standard library:
void free(void *ptr);
free function takes a pointer to a region of memory and deallocates that
region. In this section we will show how to interact with these native functions,
with the aim of providing a safe allocation API (the approach outlined below
can of course be generalized to allocation functions other than malloc and
free).
First, we need to create the downcall method handles for malloc and
free, as follows:
Linker linker = Linker.nativeLinker(); MethodHandle malloc = linker.downcallHandle( linker.defaultLookup().findOrThrow("malloc"), FunctionDescriptor.of(ADDRESS, JAVA_LONG) ); MethodHandle free = linker.downcallHandle( linker.defaultLookup().findOrThrow("free"), FunctionDescriptor.ofVoid(ADDRESS) );
malloc) is invoked using
a downcall method handle, the Java runtime has no insight into the size or the
lifetime of the returned pointer. Consider the following code:
MemorySegment segment = (MemorySegment)malloc.invokeExact(100);
malloc downcall method handle is
zero. Moreover, the scope of the
returned segment is the global scope. To provide safe access to the segment, we must,
unsafely, resize the segment to the desired size (100, in this case). It might also
be desirable to attach the segment to some existing arena, so that
the lifetime of the region of memory backing the segment can be managed automatically,
as for any other native segment created directly from Java code. Both of these
operations are accomplished using the restricted method
MemorySegment#reinterpret(long, Arena, Consumer), as follows:
MemorySegment allocateMemory(long byteSize, Arena arena) throws Throwable { MemorySegment segment = (MemorySegment) malloc.invokeExact(byteSize); // size = 0, scope = always alive return segment.reinterpret(byteSize, arena, s -> { try { free.invokeExact(s); } catch (Throwable e) { throw new RuntimeException(e); } }); // size = byteSize, scope = arena.scope() }
allocateMemory method defined above accepts two parameters: a size and an
arena. The method calls the malloc downcall method handle, and unsafely
reinterprets the returned segment, by giving it a new size (the size passed to the
allocateMemory method) and a new scope (the scope of the provided arena).
The method also specifies a cleanup action to be executed when the provided
arena is closed. Unsurprisingly, the cleanup action passes the segment to the
free downcall method handle, to deallocate the underlying region of memory.
We can use the allocateMemory method as follows:
try (Arena arena = Arena.ofConfined()) { MemorySegment segment = allocateMemory(100, arena); } // 'free' called here
allocateMemory acts as any other segment
managed by the confined arena. More specifically, the obtained segment has the desired
size, can only be accessed by a single thread (the thread that created the confined
arena), and its lifetime is tied to the surrounding try-with-resources block.
...) at the end of the
formal parameter list, such as: void foo(int x, ...);
The arguments passed in place of the ellipsis are called variadic arguments.
Variadic functions are, essentially, templates that can be specialized into
multiple non-variadic functions by replacing the ... with a list of
variadic parameters of a fixed number and type.
It should be noted that values passed as variadic arguments undergo default argument promotion in C. For instance, the following argument promotions are applied:
_Bool -> unsigned int[signed] char -> [signed] int[signed] short -> [signed] intfloat -> double..., as the variadic parameters of the specialized form
of a variadic function will always have a promoted type.
The native linker only supports linking the specialized form of a variadic function.
A variadic function in its specialized form can be linked using a function descriptor
describing the specialized form. Additionally, the Linker.
linker option must be provided to indicate the first variadic parameter in the
parameter list. The corresponding argument layout (if any), and all following
argument layouts in the specialized function descriptor, are called
variadic argument layouts.
The native linker does not automatically perform default argument promotions. However,
since passing an argument of a non-promoted type as a variadic argument is not
supported in C, the native linker will reject an attempt to link a specialized
function descriptor with any variadic argument value layouts corresponding to a
non-promoted C type. Since the size of the C int type is platform-specific,
exactly which layouts will be rejected is platform-specific as well. As an example:
on Linux/x64 the layouts corresponding to the C types _Bool,
(unsigned) char, (unsigned) short, and float (among others),
will be rejected by the linker. The canonicalLayouts() method can be used to
find which layout corresponds to a particular C type.
A well-known variadic function is the printf function, defined in the
C standard library:
int printf(const char *format, ...);
printf("%d plus %d equals %d", 2, 2, 4);
(char*, int, int, int) as the format string accepts three integer parameters.
We then need to use a linker option
to specify the position of the first variadic layout in the provided function
descriptor (starting from 0). In this case, since the first parameter is the format
string (a non-variadic argument), the first variadic index needs to be set to 1, as
follows:
Linker linker = Linker.nativeLinker(); MethodHandle printf = linker.downcallHandle( linker.defaultLookup().findOrThrow("printf"), FunctionDescriptor.of(JAVA_INT, ADDRESS, JAVA_INT, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1) // first int is variadic );
try (Arena arena = Arena.ofConfined()) { //prints "2 plus 2 equals 4" int res = (int)printf.invokeExact(arena.allocateFrom("%d plus %d equals %d"), 2, 2, 4); }
When an upcall stub is passed to a foreign function, a JVM crash might occur, if the foreign code casts the function pointer associated with the upcall stub to a type that is incompatible with the type of the upcall stub, and then attempts to invoke the function through the resulting function pointer. Moreover, if the method handle associated with an upcall stub returns a memory segment, clients must ensure that this address cannot become invalid after the upcall is completed. This can lead to unspecified behavior, and even JVM crashes, since an upcall is typically executed in the context of a downcall method handle invocation.
Implementation Specification
Implementations of this interface are immutable, thread-safe.
| Modifier and Type | Class and Description |
|---|---|
| public static interface | Linker.
A linker option is used to provide additional parameters to a linkage request. |
| Modifier and Type | Method and Description |
|---|---|
| public Map | Returns: an unmodifiable mapping between the names of data types used by the ABI implemented by this linker and their canonical layoutsReturns an unmodifiable mapping between the names of data types used by the ABI implemented by this linker and their canonical layouts. |
| public SymbolLookup | Returns: a symbol lookup for symbols in a set of commonly used librariesReturns a symbol lookup for symbols in a set of commonly used libraries. |
| public MethodHandle | Returns: a downcall method handlethe native memory segment whose
base address is the address
of the target foreign function address, FunctionDescriptor the function descriptor of the target foreign function function, Linker.the linker options associated with this linkage request optionsCreates a method handle that is used to call a foreign function with the given signature and address. |
| public MethodHandle | Returns: a downcall method handlethe function descriptor of the target foreign function function, Linker.the linker options associated with this linkage request optionsCreates a method handle that is used to call a foreign function with the given signature. |
| public static Linker | Returns: a linker for the ABI associated with the underlying native platformReturns a linker for the ABI associated with the underlying native platform. |
| public MemorySegment | Returns: a zero-length segment whose address is the address of the upcall stubthe target method handle target, FunctionDescriptor the upcall stub function descriptor function, Arena the arena associated with the returned upcall stub segment arena, Linker.the linker options associated with this linkage request optionsCreates an upcall stub which can be passed to other foreign functions as a function pointer, associated with the given arena. |
| canonicalLayouts | back to summary |
|---|---|
| public Map Returns an unmodifiable mapping between the names of data types used by the ABI implemented by this linker and their canonical layouts.
Each Implementation Note It is strongly recommended that the result of If an implementer provides
| |
| defaultLookup | back to summary |
|---|---|
| public SymbolLookup defaultLookup() Returns a symbol lookup for symbols in a set of commonly used libraries.
Each Implementation Note It is strongly recommended that the result of If an implementer provides
| |
| downcallHandle | back to summary |
|---|---|
| public MethodHandle downcallHandle(MemorySegment address, FunctionDescriptor function, Linker. Creates a method handle that is used to call a foreign function with the given signature and address. Calling this method is equivalent to the following code: linker.downcallHandle(function, options).bindTo(address);
| |
| downcallHandle | back to summary |
|---|---|
| public MethodHandle downcallHandle(FunctionDescriptor function, Linker. Creates a method handle that is used to call a foreign function with the given signature.
The Java method type associated with the
returned method handle is derived
from the argument and return layouts in the function descriptor, but features an
additional leading parameter of type
Upon invoking a downcall method handle, the linker provides the following
guarantees for any argument
Moreover, if the provided function descriptor's return layout is an
address layout, invoking the returned method handle
will return a native segment associated with the global scope. Under normal
conditions, the size of the returned segment is
The returned method handle will throw an
| |
| nativeLinker | back to summary |
|---|---|
| public static Linker nativeLinker() Returns a linker for the ABI associated with the underlying native platform. The underlying native platform is the combination of OS and processor where the Java runtime is currently executing. API Note It is not currently possible to obtain a linker for a different combination of OS and processor. Implementation Specification A native linker implementation is guaranteed to provide canonical layouts for basic C types. Implementation Note The libraries exposed by the default lookup
associated with the returned linker are the native libraries loaded in
the process where the Java runtime is currently executing. For example,
on Linux, these libraries typically include
| |
| upcallStub | back to summary |
|---|---|
| public MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker. Creates an upcall stub which can be passed to other foreign functions as a function pointer, associated with the given arena. Calling such a function pointer from foreign code will result in the execution of the provided method handle. The returned memory segment's address points to the newly allocated upcall stub, and is associated with the provided arena. As such, the lifetime of the returned upcall stub segment is controlled by the provided arena. For instance, if the provided arena is a confined arena, the returned upcall stub segment will be deallocated when the provided confined arena is closed.
An upcall stub argument whose corresponding layout is an
address layout is a native segment associated with the
global scope. Under normal conditions, the size of this segment argument is
The target method handle should not throw any exceptions. If the target method
handle does throw an exception, the JVM will terminate abruptly. To avoid this,
clients should wrap the code in the target method handle in a try/catch block to
catch any unexpected exceptions. This can be done using the
| |
| Modifier and Type | Method and Description |
|---|---|
| public static Linker. | Returns: a linker option used to initialize portions of the execution state immediately before, and save portions of the execution state immediately after calling a foreign function associated with a downcall method handle, before it can be overwritten by the Java runtime, or read through conventional meansthe names of the values to save capturedState)Returns a linker option used to initialize portions of the execution state immediately before, and save portions of the execution state immediately after calling a foreign function associated with a downcall method handle, before it can be overwritten by the Java runtime, or read through conventional means. |
| public static StructLayout | Returns: a struct layout that represents the layout of the capture state segment that is passed to a downcall handle linked withcaptureCallState(String.Returns a struct layout that represents the layout of the capture state
segment that is passed to a downcall handle linked with
|
| public static Linker. | Returns: a linker option used to mark a foreign function as criticalwhether the linked function should allow access to the
Java heap. allowHeapAccess)Returns a linker option used to mark a foreign function as critical. |
| public static Linker. | Returns: a linker option used to denote the index indicating the start of the variadic arguments passed to the function described by the function descriptor associated with a downcall linkage requestthe index of the first variadic argument layout in the function
descriptor associated with a downcall linkage request index)Returns a linker option used to denote the index indicating the start of the variadic arguments passed to the function described by the function descriptor associated with a downcall linkage request. |
| captureCallState | back to summary |
|---|---|
| public static Linker. Returns a linker option used to initialize portions of the execution state immediately before, and save portions of the execution state immediately after calling a foreign function associated with a downcall method handle, before it can be overwritten by the Java runtime, or read through conventional means.
Execution state is initialized from, or saved to a native segment provided by
the user to the downcall method handle. For this purpose, a downcall method
handle linked with this option will feature an additional The capture state segment must have size and alignment compatible with the layout returned by captureStateLayout. This layout is a struct layout which has a named field for each captured value. Captured state can be stored in, or retrieved from the capture state segment by constructing var handles from the capture state layout. Some functions require this state to be initialized to a particular value before invoking the downcall. The following example demonstrates the use of this linker option: MemorySegment targetAddress = ... Linker.Option ccs = Linker.Option.captureCallState("errno"); MethodHandle handle = Linker.nativeLinker().downcallHandle(targetAddress, FunctionDescriptor.ofVoid(), ccs); StructLayout capturedStateLayout = Linker.Option.captureStateLayout(); VarHandle errnoHandle = capturedStateLayout.varHandle(PathElement.groupElement("errno")); try (Arena arena = Arena.ofConfined()) { MemorySegment capturedState = arena.allocate(capturedStateLayout); errnoHandle.set(capturedState, 0L, 0); // set errno to 0 handle.invoke(capturedState); int errno = (int) errnoHandle.get(capturedState, 0L); // use errno }
| |
| captureStateLayout | back to summary |
|---|---|
| public static StructLayout captureStateLayout() Returns a struct layout that represents the layout of the capture state
segment that is passed to a downcall handle linked with
The capture state layout is platform-dependent but is guaranteed to be a struct layout containing only value layouts and possibly padding layouts. As an example, on Windows, the returned layout might contain three value layouts named:
Clients can obtain the names of the supported captured value layouts as follows: List<String> capturedNames = Linker.Option.captureStateLayout().memberLayouts().stream() .map(MemoryLayout::name) .flatMap(Optional::stream) .toList();
| |
| critical | back to summary |
|---|---|
| public static Linker. Returns a linker option used to mark a foreign function as critical. A critical function is a function that has an extremely short running time in all cases (similar to calling an empty function), and does not call back into Java (e.g. using an upcall stub). Using this linker option is a hint that some implementations may use to apply optimizations that are only valid for critical functions. Using this linker option when linking non-critical functions is likely to have adverse effects, such as loss of performance or JVM crashes. Critical functions can optionally allow access to the Java heap. This allows clients to pass heap memory segments as addresses, where normally only off-heap memory segments would be allowed. The memory region inside the Java heap is exposed through a temporary native address that is valid for the duration of the function call. Use of this mechanism is therefore only recommended when a function needs to do short-lived access to Java heap memory, and copying the relevant data to an off-heap memory segment would be prohibitive in terms of performance. | |
| firstVariadicArg | back to summary |
|---|---|
| public static Linker. Returns a linker option used to denote the index indicating the start of the variadic arguments passed to the function described by the function descriptor associated with a downcall linkage request.
The
Implementation Note The index value is validated when making a linkage request, which is when the function descriptor against which the index is validated is available.
| |