Interface Linker


public sealed interfaceLinker
A linker provides access to foreign functions from Java code, and access to Java code from foreign functions.

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:

A linker provides a way to look up thecanonical layouts associated with the data types used by the ABI. For example, a linker implementing the C ABI might choose to provide a canonical layout for the Csize_t type. On 64-bit platforms, this canonical layout might be equal toValueLayout.JAVA_LONG. The canonical layouts supported by a linker are exposed via thecanonicalLayouts() 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 andlibm. The functions in these libraries are exposed via asymbol lookup.

Calling native functions

Thenative linker can be used to link against functions defined in C libraries (native functions). Suppose we wish to downcall from Java to thestrlen function defined in the standard C library:
size_t strlen(const char *s);
A downcall method handle that exposesstrlen 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));
Note how the native linker also provides access, via itsdefault lookup, to the native functions defined by the C libraries loaded with the Java runtime. Above, the default lookup is used to search the address of thestrlen native function. That address is then passed, along with aplatform-dependent description of the signature of the function expressed as aFunctionDescriptor (more on that below) to the native linker'sdowncallHandle(MemorySegment, FunctionDescriptor, Option...)RESTRICTED 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 }

Describing C signatures

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

Scalar C types such asbool,int are modeled asvalue layouts of a suitable carrier. Themapping 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 asgroup layouts. More specifically, a Cstruct type maps to astruct layout, whereas a Cunion type maps to aunion 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 sizedpadding layout member to the resulting struct layout.

Finally, pointer types such asint** andint(*)(size_t*, size_t*) are modeled asaddress layouts. When the spatial bounds of the pointer type are known statically, the address layout can be associated with atarget layout. For instance, a pointer that is known to point to a Cint[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 isValueLayout.JAVA_INT.

All native linker implementations are guaranteed to provide canonical layouts for the following set of types:

  • bool
  • char
  • short
  • int
  • long
  • long long
  • float
  • double
  • size_t
  • wchar_t
  • void*
As noted above, the specific canonical layout associated with each type can vary, depending on the data model supported by a given ABI. For instance, the C typelong maps to the layout constantValueLayout.JAVA_LONG on Linux/x64, but maps to the layout constantValueLayout.JAVA_INT on Windows/x64. Similarly, the C typesize_t maps to the layout constantValueLayout.JAVA_LONG on 64-bit platforms, but maps to the layout constantValueLayout.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 typeunsigned long maps to the layout constantValueLayout.JAVA_LONG on Linux/x64, but maps to the layout constantValueLayout.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):

Mapping C types
C typeLayoutJava type
boolValueLayout.JAVA_BOOLEANboolean
char
unsigned char
ValueLayout.JAVA_BYTEbyte
short
unsigned short
ValueLayout.JAVA_SHORTshort
int
unsigned int
ValueLayout.JAVA_INTint
long
unsigned long
ValueLayout.JAVA_LONGlong
long long
unsigned long long
ValueLayout.JAVA_LONGlong
floatValueLayout.JAVA_FLOATfloat
doubleValueLayout.JAVA_DOUBLEdouble
size_tValueLayout.JAVA_LONGlong
char*,int**,struct Point*ValueLayout.ADDRESSMemorySegment
int (*ptr)[10]
 ValueLayout.ADDRESS.withTargetLayout(     MemoryLayout.sequenceLayout(10,         ValueLayout.JAVA_INT) );
MemorySegment
struct Point { int x; long y; };
 MemoryLayout.structLayout(     ValueLayout.JAVA_INT.withName("x"),     MemoryLayout.paddingLayout(4),     ValueLayout.JAVA_LONG.withName("y") );
MemorySegment
union 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 arewell-formed layouts. More formally, a layout `L` is well-formed if:

  • L is a value layout andL is derived from a canonical layoutC such thatL.byteAlignment() <= C.byteAlignment()
  • L is a sequence layoutS and all the following conditions hold:
    1. L.byteAlignment() is equal to the sequence layout'snatural alignment , and
    2. S.elementLayout() is a well-formed layout.
  • L is a group layoutG and all the following conditions hold:
    1. G.byteAlignment() is equal to the group layout'snatural alignment
    2. G.byteSize() is a multiple ofG.byteAlignment()
    3. Each member layout inG.memberLayouts() is either a padding layout or a well-formed layout
    4. Each non-padding member layoutE inG.memberLayouts() follows an optional padding member layout, whose size is the minimum size required to alignE
    5. G 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 rejectpacked struct layouts -- struct layouts whose member layouts feature relaxed alignment constraints, to avoid the insertion of additional padding.

Function pointers

Sometimes, it is useful to pass Java code as a function pointer to some native function; this is achieved by using anupcall stubRESTRICTED. To demonstrate this, let's consider the following function from the C standard library:
void qsort(void *base, size_t nmemb, size_t size,           int (*compar)(const void *, const void *));
Theqsort function can be used to sort the contents of an array, using a custom comparator function which is passed as a function pointer (thecompar parameter). To be able to call theqsort 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));
As before, we useValueLayout.JAVA_LONG to map the C typesize_t type, andValueLayout.ADDRESS for both the first pointer parameter (the array pointer) and the last parameter (the function pointer).

To invoke theqsort 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. asmemory segments):

class Qsort {    static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {        return Integer.compare(elem1.get(JAVA_INT, 0), elem2.get(JAVA_INT, 0));    }}
Now let's create a method handle for the comparator method defined above:
FunctionDescriptor comparDesc = FunctionDescriptor.of(JAVA_INT,                                                      ADDRESS.withTargetLayout(JAVA_INT),                                                      ADDRESS.withTargetLayout(JAVA_INT));MethodHandle comparHandle = MethodHandles.lookup()                                         .findStatic(Qsort.class, "qsortCompare",                                                     comparDesc.toMethodType());
First, we create a function descriptor for the function pointer type. Since we know that the parameters passed to the comparator method will be pointers to elements of a Cint[] array, we can specifyValueLayout.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 thenturn that function descriptor into a suitablemethod 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 theqsort 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 ]}
This code creates an off-heap array, copies the contents of a Java array into it, and then passes the array to theqsort 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.

Functions returning pointers

When interacting with native functions, it is common for those functions to allocate a region of memory and return a pointer to that region. Let's consider the following function from the C standard library:
void *malloc(size_t size);
Themalloc 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);
Thefree 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 asafe allocation API (the approach outlined below can of course be generalized to allocation functions other thanmalloc andfree).

First, we need to create the downcall method handles formalloc andfree, 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));
When a native function returning a pointer (such asmalloc) 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);
The size of the segment returned by themalloc downcall method handle iszero. 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 existingarena, 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 methodMemorySegment.reinterpret(long, Arena, Consumer)RESTRICTED, 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()}
TheallocateMemory method defined above accepts two parameters: a size and an arena. The method calls themalloc downcall method handle, and unsafely reinterprets the returned segment, by giving it a new size (the size passed to theallocateMemory method) and a new scope (the scope of the provided arena). The method also specifies acleanup action to be executed when the provided arena is closed. Unsurprisingly, the cleanup action passes the segment to thefree downcall method handle, to deallocate the underlying region of memory. We can use theallocateMemory method as follows:
try (Arena arena = Arena.ofConfined()) {    MemorySegment segment = allocateMemory(100, arena);} // 'free' called here
Note how the segment obtained fromallocateMemory 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 surroundingtry-with-resources block.

Variadic functions

Variadic functions are C functions that can accept a variable number and type of arguments. They are declared with a trailing ellipsis (...) at the end of the formal parameter list, such as:void foo(int x, ...); The arguments passed in place of the ellipsis are calledvariadic arguments. Variadic functions are, essentially, templates that can bespecialized into multiple non-variadic functions by replacing the... with a list ofvariadic 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] int
  • float ->double
whereby the signed-ness of the source type corresponds to the signed-ness of the promoted type. The complete process of default argument promotion is described in the C specification. In effect, these promotions place limits on the types that can be used to replace the..., 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, theLinker.Option.firstVariadicArg(int) 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 calledvariadic 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 Cint 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, andfloat (among others), will be rejected by the linker. ThecanonicalLayouts() method can be used to find which layout corresponds to a particular C type.

A well-known variadic function is theprintf function, defined in the C standard library:

int printf(const char *format, ...);
This function takes a format string, and a number of additional arguments (the number of such arguments is dictated by the format string). Consider the following variadic call:
printf("%d plus %d equals %d", 2, 2, 4);
To perform an equivalent call using a downcall method handle we must create a function descriptor which describes the specialized signature of the C function we want to call. This descriptor must include an additional layout for each variadic argument we intend to provide. In this case, the specialized signature of the C function is(char*, int, int, int) as the format string accepts three integer parameters. We then need to use alinker 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);
We can then call the specialized downcall handle as usual:
 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); }

Safety considerations

Creating a downcall method handle is intrinsically unsafe. A symbol in a foreign library does not, in general, contain enough signature information (e.g. arity and types of foreign function parameters). As a consequence, the linker runtime cannot validate linkage requests. When a client interacts with a downcall method handle obtained through an invalid linkage request (e.g. by specifying a function descriptor featuring too many argument layouts), the result of such interaction is unspecified and can lead to JVM crashes.

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 amemory 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 Requirements:
Implementations of this interface are immutable, thread-safe andvalue-based.
Since:
22
  • Method Details

    • nativeLinker

      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 theJava runtime is currently executing.

      API Note:
      It is not currently possible to obtain a linker for a different combination of OS and processor.
      Implementation Requirements:
      A native linker implementation is guaranteed to provide canonical layouts forbasic C types.
      Implementation Note:
      The libraries exposed by thedefault 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 includelibc,libm andlibdl.
      Returns:
      a linker for the ABI associated with the underlying native platform
    • downcallHandle

      MethodHandle downcallHandle(MemorySegment address,FunctionDescriptor function,Linker.Option... options)
      downcallHandle is arestricted method of the Java platform.
      Programs can only usedowncallHandle when access to restricted methods is enabled.
      Restricted methods are unsafe, and, if used incorrectly, might crash the JVM or result in memory corruption.
      Creates a method handle that is used to call a foreign function withthe given signature and address.

      Calling this method is equivalent to the following code:

      linker.downcallHandle(function, options).bindTo(address);

      Parameters:
      address - the native memory segment whosebase address is the address of the target foreign function
      function - the function descriptor of the target foreign function
      options - the linker options associated with this linkage request
      Returns:
      a downcall method handle
      Throws:
      IllegalArgumentException - if the provided function descriptor is not supported by this linker
      IllegalArgumentException - if!address.isNative(), or ifaddress.equals(MemorySegment.NULL)
      IllegalArgumentException - if an invalid combination of linker options is given
      IllegalCallerException - if the caller is in a module that does not have native access enabled
      See Also:
    • downcallHandle

      MethodHandle downcallHandle(FunctionDescriptor function,Linker.Option... options)
      downcallHandle is arestricted method of the Java platform.
      Programs can only usedowncallHandle when access to restricted methods is enabled.
      Restricted methods are unsafe, and, if used incorrectly, might crash the JVM or result in memory corruption.
      Creates a method handle that is used to call a foreign function with the given signature.

      The Javamethod type associated with the returned method handle isderived from the argument and return layouts in the function descriptor, but features an additional leading parameter of typeMemorySegment, from which the address of the target foreign function is derived. Moreover, if the function descriptor's return layout is a group layout, the resulting downcall method handle accepts an additional leading parameter of typeSegmentAllocator, which is used by the linker runtime to allocate the memory region associated with the struct returned by the downcall method handle.

      Upon invoking a downcall method handle, the linker provides the following guarantees for any argumentA of typeMemorySegment whose corresponding layout is anaddress layout:

      • A.scope().isAlive() == true. Otherwise, the invocation throwsIllegalStateException;
      • The invocation occurs in a threadT such thatA.isAccessibleBy(T) == true. Otherwise, the invocation throwsWrongThreadException; and
      • A is kept alive during the invocation. For instance, ifA has been obtained using ashared arena, any attempt toclose the arena while the downcall method handle is still executing will result in anIllegalStateException.

      Moreover, if the provided function descriptor's return layout is anaddress 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 is0. However, if the function descriptor's return layout has atarget layoutT, then the size of the returned segment is set toT.byteSize().

      The returned method handle will throw anIllegalArgumentException if theMemorySegment representing the target address of the foreign function is theMemorySegment.NULL address. If an argument is aMemorySegment, whose corresponding layout is agroup layout, the linker might attempt to access the contents of the segment. As such, one of the exceptions specified by theMemorySegment.get(ValueLayout.OfByte, long) or theMemorySegment.copy(MemorySegment, long, MemorySegment, long, long) methods may be thrown. If an argument is aMemorySegment whose corresponding layout is anaddress layout, the linker will throw anIllegalArgumentException if the segment is a heap memory segment, unless heap memory segments are explicitly allowed through theLinker.Option.critical(boolean) linker option. The returned method handle will additionally throwNullPointerException if any argument passed to it isnull.

      Parameters:
      function - the function descriptor of the target foreign function
      options - the linker options associated with this linkage request
      Returns:
      a downcall method handle
      Throws:
      IllegalArgumentException - if the provided function descriptor is not supported by this linker
      IllegalArgumentException - if an invalid combination of linker options is given
      IllegalCallerException - if the caller is in a module that does not have native access enabled
    • upcallStub

      MemorySegment upcallStub(MethodHandle target,FunctionDescriptor function,Arena arena,Linker.Option... options)
      upcallStub is arestricted method of the Java platform.
      Programs can only useupcallStub when access to restricted methods is enabled.
      Restricted methods are unsafe, and, if used incorrectly, might crash the JVM or result in memory corruption.
      Creates an upcall stub which can be passed to other foreign functions as afunction pointer, associated with the given arena. Calling such a functionpointer from foreign code will result in the execution of the provided methodhandle.

      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 returnedupcall stub segment is controlled by the provided arena. For instance, if theprovided arena is a confined arena, the returned upcall stub segment will bedeallocated when the provided confined arena isclosed.

      An upcall stub argument whose corresponding layout is anaddress layout is a native segment associated with theglobal scope. Under normal conditions, the size of this segment argument is0. However, if the address layout has atarget layoutT, then the sizeof the segment argument is set toT.byteSize().

      The target method handle should not throw any exceptions. If the target methodhandle 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 tocatch any unexpected exceptions. This can be done using theMethodHandles.catchException(MethodHandle, Class, MethodHandle)method handle combinator, and handle exceptions as desired in the correspondingcatch block.

      Parameters:
      target - the target method handle
      function - the upcall stub function descriptor
      arena - the arena associated with the returned upcall stub segment
      options - the linker options associated with this linkage request
      Returns:
      a zero-length segment whose address is the address of the upcall stub
      Throws:
      IllegalArgumentException - if the provided function descriptor is not supported by this linker
      IllegalArgumentException - if the type oftarget is incompatible with the typederived fromfunction
      IllegalArgumentException - if it is determined that the target method handle can throw an exception
      IllegalStateException - ifarena.scope().isAlive() == false
      WrongThreadException - ifarena is a confined arena, and this method is called from a threadT, other than the arena's owner thread
      IllegalCallerException - if the caller is in a module that does not have native access enabled
    • defaultLookup

      SymbolLookup defaultLookup()
      Returns a symbol lookup for symbols in a set of commonly used libraries.

      EachLinker is responsible for choosing libraries that are widelyrecognized as useful on the OS and processor combination supported by theLinker. Accordingly, the precise set of symbols exposed by the symbollookup is unspecified; it varies from oneLinker to another.

      Implementation Note:
      It is strongly recommended that the result ofdefaultLookup() exposes a set of symbols that is stable over time. Clients ofdefaultLookup() are likely to fail if a symbol that was previously exposed by the symbol lookup is no longer exposed.

      If an implementer providesLinker implementations for multiple OS and processor combinations, then it is strongly recommended that the result ofdefaultLookup() exposes, as much as possible, a consistent set of symbols across all the OS and processor combinations.

      Returns:
      a symbol lookup for symbols in a set of commonly used libraries
    • canonicalLayouts

      Map<String,MemoryLayout> canonicalLayouts()
      Returns an unmodifiable mapping between the names of data types used by the ABI implemented by this linker and theircanonical layouts.

      EachLinker is responsible for choosing the data types that are widelyrecognized as useful on the OS and processor combination supported by theLinker. Accordingly, the precise set of data type names and canonicallayouts exposed by the linker are unspecified; they vary from oneLinkerto another.

      Implementation Note:
      It is strongly recommended that the result ofcanonicalLayouts() exposes a set of symbols that is stable over time. Clients ofcanonicalLayouts() are likely to fail if a data type that was previously exposed by the linker is no longer exposed, or if its canonical layout is updated.

      If an implementer providesLinker implementations for multiple OS and processor combinations, then it is strongly recommended that the result ofcanonicalLayouts() exposes, as much as possible, a consistent set of symbols across all the OS and processor combinations.

      Returns:
      an unmodifiable mapping between the names of data types used by the ABI implemented by this linker and theircanonical layouts