Skip to content

Commit 7a6fc80

Browse files
committed
8308293: A linker should expose the layouts it supports
Reviewed-by: jvernee
1 parent e8d2173 commit 7a6fc80

File tree

15 files changed

+304
-49
lines changed

15 files changed

+304
-49
lines changed

src/java.base/share/classes/java/lang/foreign/Linker.java

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,11 @@
3131
import jdk.internal.foreign.abi.SharedUtils;
3232
import jdk.internal.javac.PreviewFeature;
3333
import jdk.internal.reflect.CallerSensitive;
34-
import jdk.internal.reflect.Reflection;
3534

3635
import java.lang.invoke.MethodHandle;
3736
import java.nio.ByteOrder;
37+
import java.util.Map;
3838
import java.util.Objects;
39-
import java.util.Optional;
4039
import java.util.Set;
4140
import java.util.function.Consumer;
4241
import java.util.stream.Collectors;
@@ -59,6 +58,12 @@
5958
* <li>A linker allows foreign functions to call Java method handles,
6059
* via the generation of {@linkplain #upcallStub(MethodHandle, FunctionDescriptor, Arena, Option...) upcall stubs}.</li>
6160
* </ul>
61+
* A linker provides a way to look up the <em>canonical layouts</em> associated with the data types used by the ABI.
62+
* For example, a linker implementing the C ABI might choose to provide a canonical layout for the C {@code size_t}
63+
* type. On 64-bit platforms, this canonical layout might be equal to {@link ValueLayout#JAVA_LONG}. The canonical
64+
* layouts supported by a linker are exposed via the {@link #canonicalLayouts()} method, which returns a map from
65+
* type names to canonical layouts.
66+
* <p>
6267
* In addition, a linker provides a way to look up foreign functions in libraries that conform to the ABI. Each linker
6368
* chooses a set of libraries that are commonly used on the OS and processor combination associated with the ABI.
6469
* For example, a linker for Linux/x64 might choose two libraries: {@code libc} and {@code libm}. The functions in these
@@ -103,11 +108,8 @@
103108
* defines the layouts associated with the parameter types and return type (if any) of the C function.
104109
* <p>
105110
* Scalar C types such as {@code bool}, {@code int} are modelled as {@linkplain ValueLayout value layouts}
106-
* of a suitable carrier. The mapping between a scalar type and its corresponding layout is dependent on the ABI
107-
* implemented by the native linker. For instance, the C type {@code long} maps to the layout constant
108-
* {@link ValueLayout#JAVA_LONG} on Linux/x64, but maps to the layout constant {@link ValueLayout#JAVA_INT} on
109-
* Windows/x64. Similarly, the C type {@code size_t} maps to the layout constant {@link ValueLayout#JAVA_LONG}
110-
* on 64-bit platforms, but maps to the layout constant {@link ValueLayout#JAVA_INT} on 32-bit platforms.
111+
* of a suitable carrier. The {@linkplain #canonicalLayouts() mapping} between a scalar type and its corresponding
112+
* canonical layout is dependent on the ABI implemented by the native linker (see below).
111113
* <p>
112114
* Composite types are modelled as {@linkplain GroupLayout group layouts}. More specifically, a C {@code struct} type
113115
* maps to a {@linkplain StructLayout struct layout}, whereas a C {@code union} type maps to a {@link UnionLayout union
@@ -122,7 +124,33 @@
122124
* a pointer that is known to point to a C {@code int[2]} array can be modelled as an address layout whose
123125
* target layout is a sequence layout whose element count is 2, and whose element type is {@link ValueLayout#JAVA_INT}.
124126
* <p>
125-
* The following table shows some examples of how C types are modelled in Linux/x64:
127+
* All native linker implementations are guaranteed to provide canonical layouts for the following set of types:
128+
* <ul>
129+
* <li>{@code bool}</li>
130+
* <li>{@code char}</li>
131+
* <li>{@code short}</li>
132+
* <li>{@code int}</li>
133+
* <li>{@code long}</li>
134+
* <li>{@code long long}</li>
135+
* <li>{@code float}</li>
136+
* <li>{@code double}</li>
137+
* <li>{@code size_t}</li>
138+
* <li>{@code wchar_t}</li>
139+
* <li>{@code void*}</li>
140+
* </ul>
141+
* As noted above, the specific canonical layout associated with each type can vary, depending on the data model
142+
* supported by a given ABI. For instance, the C type {@code long} maps to the layout constant {@link ValueLayout#JAVA_LONG}
143+
* on Linux/x64, but maps to the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64. Similarly, the C type
144+
* {@code size_t} maps to the layout constant {@link ValueLayout#JAVA_LONG} on 64-bit platforms, but maps to the layout
145+
* constant {@link ValueLayout#JAVA_INT} on 32-bit platforms.
146+
* <p>
147+
* A native linker typically does not provide canonical layouts for C's unsigned integral types. Instead, they are
148+
* modelled using the canonical layouts associated with their corresponding signed integral types. For instance,
149+
* the C type {@code unsigned long} maps to the layout constant {@link ValueLayout#JAVA_LONG} on Linux/x64, but maps to
150+
* the layout constant {@link ValueLayout#JAVA_INT} on Windows/x64.
151+
* <p>
152+
* The following table shows some examples of how C types are modelled in Linux/x64 (all the examples provided
153+
* here will assume these platform-dependent mappings):
126154
*
127155
* <blockquote><table class="plain">
128156
* <caption style="display:none">Mapping C types</caption>
@@ -137,19 +165,19 @@
137165
* <tr><th scope="row" style="font-weight:normal">{@code bool}</th>
138166
* <td style="text-align:center;">{@link ValueLayout#JAVA_BOOLEAN}</td>
139167
* <td style="text-align:center;">{@code boolean}</td>
140-
* <tr><th scope="row" style="font-weight:normal">{@code char}</th>
168+
* <tr><th scope="row" style="font-weight:normal">{@code char} <br> {@code unsigned char}</th>
141169
* <td style="text-align:center;">{@link ValueLayout#JAVA_BYTE}</td>
142170
* <td style="text-align:center;">{@code byte}</td>
143-
* <tr><th scope="row" style="font-weight:normal">{@code short}</th>
171+
* <tr><th scope="row" style="font-weight:normal">{@code short} <br> {@code unsigned short}</th>
144172
* <td style="text-align:center;">{@link ValueLayout#JAVA_SHORT}</td>
145173
* <td style="text-align:center;">{@code short}</td>
146-
* <tr><th scope="row" style="font-weight:normal">{@code int}</th>
174+
* <tr><th scope="row" style="font-weight:normal">{@code int} <br> {@code unsigned int}</th>
147175
* <td style="text-align:center;">{@link ValueLayout#JAVA_INT}</td>
148176
* <td style="text-align:center;">{@code int}</td>
149-
* <tr><th scope="row" style="font-weight:normal">{@code long}</th>
177+
* <tr><th scope="row" style="font-weight:normal">{@code long} <br> {@code unsigned long}</th>
150178
* <td style="text-align:center;">{@link ValueLayout#JAVA_LONG}</td>
151179
* <td style="text-align:center;">{@code long}</td>
152-
* <tr><th scope="row" style="font-weight:normal">{@code long long}</th>
180+
* <tr><th scope="row" style="font-weight:normal">{@code long long} <br> {@code unsigned long long}</th>
153181
* <td style="text-align:center;">{@link ValueLayout#JAVA_LONG}</td>
154182
* <td style="text-align:center;">{@code long}</td>
155183
* <tr><th scope="row" style="font-weight:normal">{@code float}</th>
@@ -200,20 +228,7 @@
200228
* All native linker implementations operate on a subset of memory layouts. More formally, a layout {@code L}
201229
* is supported by a native linker {@code NL} if:
202230
* <ul>
203-
* <li>{@code L} is a value layout {@code V} and {@code V.withoutName()} is {@linkplain MemoryLayout#equals(Object) equal}
204-
* to one of the following layout constants:
205-
* <ul>
206-
* <li>{@link ValueLayout#JAVA_BOOLEAN}</li>
207-
* <li>{@link ValueLayout#JAVA_BYTE}</li>
208-
* <li>{@link ValueLayout#JAVA_CHAR}</li>
209-
* <li>{@link ValueLayout#JAVA_SHORT}</li>
210-
* <li>{@link ValueLayout#JAVA_INT}</li>
211-
* <li>{@link ValueLayout#JAVA_LONG}</li>
212-
* <li>{@link ValueLayout#JAVA_FLOAT}</li>
213-
* <li>{@link ValueLayout#JAVA_DOUBLE}</li>
214-
* </ul></li>
215-
* <li>{@code L} is an address layout {@code A} and {@code A.withoutTargetLayout().withoutName()} is
216-
* {@linkplain MemoryLayout#equals(Object) equal} to {@link ValueLayout#ADDRESS}</li>
231+
* <li>{@code L} is a value layout {@code V} and {@code V.withoutName()} is a canonical layout</li>
217232
* <li>{@code L} is a sequence layout {@code S} and all the following conditions hold:
218233
* <ol>
219234
* <li>the alignment constraint of {@code S} is set to its <a href="MemoryLayout.html#layout-align">natural alignment</a>, and</li>
@@ -492,6 +507,8 @@ public sealed interface Linker permits AbstractLinker {
492507
* is the combination of OS and processor where the Java runtime is currently executing.
493508
*
494509
* @apiNote It is not currently possible to obtain a linker for a different combination of OS and processor.
510+
* @implSpec A native linker implementation is guaranteed to provide canonical layouts for
511+
* <a href="#describing-c-sigs">basic C types</a>.
495512
* @implNote The libraries exposed by the {@linkplain #defaultLookup() default lookup} associated with the returned
496513
* linker are the native libraries loaded in the process where the Java runtime is currently executing. For example,
497514
* on Linux, these libraries typically include {@code libc}, {@code libm} and {@code libdl}.
@@ -635,6 +652,22 @@ static Linker nativeLinker() {
635652
*/
636653
SymbolLookup defaultLookup();
637654

655+
/**
656+
* {@return an unmodifiable mapping between the names of data types used by the ABI implemented by this linker and their
657+
* <em>canonical layouts</em>}
658+
* <p>
659+
* Each {@link Linker} is responsible for choosing the data types that are widely recognized as useful on the OS
660+
* and processor combination supported by the {@link Linker}. Accordingly, the precise set of data type names
661+
* and canonical layouts exposed by the linker is unspecified; it varies from one {@link Linker} to another.
662+
* @implNote It is strongly recommended that the result of {@link #canonicalLayouts()} exposes a set of symbols that is stable over time.
663+
* Clients of {@link #canonicalLayouts()} are likely to fail if a data type that was previously exposed by the linker
664+
* is no longer exposed, or if its canonical layout is updated.
665+
* <p>If an implementer provides {@link Linker} implementations for multiple OS and processor combinations, then it is strongly
666+
* recommended that the result of {@link #canonicalLayouts()} exposes, as much as possible, a consistent set of symbols
667+
* across all the OS and processor combinations.
668+
*/
669+
Map<String, MemoryLayout> canonicalLayouts();
670+
638671
/**
639672
* A linker option is used to provide additional parameters to a linkage request.
640673
* @since 20

src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.lang.foreign.ValueLayout;
5353
import java.lang.invoke.MethodHandle;
5454
import java.lang.invoke.MethodType;
55+
import java.util.HashSet;
5556
import java.util.List;
5657
import java.nio.ByteOrder;
5758
import java.util.Objects;
@@ -69,6 +70,7 @@ public interface UpcallStubFactory {
6970
private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) {}
7071
private final SoftReferenceCache<LinkRequest, MethodHandle> DOWNCALL_CACHE = new SoftReferenceCache<>();
7172
private final SoftReferenceCache<LinkRequest, UpcallStubFactory> UPCALL_CACHE = new SoftReferenceCache<>();
73+
private final Set<MemoryLayout> CANONICAL_LAYOUTS_CACHE = new HashSet<>(canonicalLayouts().values());
7274

7375
@Override
7476
@CallerSensitive
@@ -209,7 +211,7 @@ private void checkLayoutRecursive(MemoryLayout layout) {
209211
}
210212

211213
// check for trailing padding
212-
private static void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) {
214+
private void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) {
213215
long expectedSize = Utils.alignUp(maxUnpaddedOffset, gl.byteAlignment());
214216
if (gl.byteSize() != expectedSize) {
215217
throw new IllegalArgumentException("Layout '" + gl + "' has unexpected size: "
@@ -219,7 +221,7 @@ private static void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) {
219221

220222
// checks both that there is no excess padding between 'memberLayout' and
221223
// the previous layout
222-
private static void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout,
224+
private void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout,
223225
long lastUnpaddedOffset, long offset) {
224226
long expectedOffset = Utils.alignUp(lastUnpaddedOffset, memberLayout.byteAlignment());
225227
if (expectedOffset != offset) {
@@ -228,17 +230,17 @@ private static void checkMemberOffset(StructLayout parent, MemoryLayout memberLa
228230
}
229231
}
230232

231-
private static void checkSupported(ValueLayout valueLayout) {
233+
private void checkSupported(ValueLayout valueLayout) {
232234
valueLayout = valueLayout.withoutName();
233235
if (valueLayout instanceof AddressLayout addressLayout) {
234236
valueLayout = addressLayout.withoutTargetLayout();
235237
}
236-
if (!SUPPORTED_LAYOUTS.contains(valueLayout.withoutName())) {
238+
if (!CANONICAL_LAYOUTS_CACHE.contains(valueLayout.withoutName())) {
237239
throw new IllegalArgumentException("Unsupported layout: " + valueLayout);
238240
}
239241
}
240242

241-
private static void checkHasNaturalAlignment(MemoryLayout layout) {
243+
private void checkHasNaturalAlignment(MemoryLayout layout) {
242244
if (!((AbstractLayout<?>) layout).hasNaturalAlignment()) {
243245
throw new IllegalArgumentException("Layout alignment must be natural alignment: " + layout);
244246
}
@@ -269,16 +271,4 @@ private static FunctionDescriptor stripNames(FunctionDescriptor function) {
269271
.map(rl -> FunctionDescriptor.of(stripNames(rl), stripNames(function.argumentLayouts())))
270272
.orElseGet(() -> FunctionDescriptor.ofVoid(stripNames(function.argumentLayouts())));
271273
}
272-
273-
private static final Set<MemoryLayout> SUPPORTED_LAYOUTS = Set.of(
274-
ValueLayout.JAVA_BOOLEAN,
275-
ValueLayout.JAVA_BYTE,
276-
ValueLayout.JAVA_CHAR,
277-
ValueLayout.JAVA_SHORT,
278-
ValueLayout.JAVA_INT,
279-
ValueLayout.JAVA_FLOAT,
280-
ValueLayout.JAVA_LONG,
281-
ValueLayout.JAVA_DOUBLE,
282-
ValueLayout.ADDRESS
283-
);
284274
}

src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,4 +493,35 @@ static Object read(MemorySegment ptr, long offset, Class<?> type) {
493493
throw new IllegalArgumentException("Unsupported carrier: " + type);
494494
}
495495
}
496+
497+
public static Map<String, MemoryLayout> canonicalLayouts(ValueLayout longLayout, ValueLayout sizetLayout, ValueLayout wchartLayout) {
498+
return Map.ofEntries(
499+
// specified canonical layouts
500+
Map.entry("bool", ValueLayout.JAVA_BOOLEAN),
501+
Map.entry("char", ValueLayout.JAVA_BYTE),
502+
Map.entry("short", ValueLayout.JAVA_SHORT),
503+
Map.entry("int", ValueLayout.JAVA_INT),
504+
Map.entry("float", ValueLayout.JAVA_FLOAT),
505+
Map.entry("long", longLayout),
506+
Map.entry("long long", ValueLayout.JAVA_LONG),
507+
Map.entry("double", ValueLayout.JAVA_DOUBLE),
508+
Map.entry("void*", ValueLayout.ADDRESS),
509+
Map.entry("size_t", sizetLayout),
510+
Map.entry("wchar_t", wchartLayout),
511+
// unspecified size-dependent layouts
512+
Map.entry("int8_t", ValueLayout.JAVA_BYTE),
513+
Map.entry("int16_t", ValueLayout.JAVA_SHORT),
514+
Map.entry("int32_t", ValueLayout.JAVA_INT),
515+
Map.entry("int64_t", ValueLayout.JAVA_LONG),
516+
// unspecified JNI layouts
517+
Map.entry("jboolean", ValueLayout.JAVA_BOOLEAN),
518+
Map.entry("jchar", ValueLayout.JAVA_CHAR),
519+
Map.entry("jbyte", ValueLayout.JAVA_BYTE),
520+
Map.entry("jshort", ValueLayout.JAVA_SHORT),
521+
Map.entry("jint", ValueLayout.JAVA_INT),
522+
Map.entry("jlong", ValueLayout.JAVA_LONG),
523+
Map.entry("jfloat", ValueLayout.JAVA_FLOAT),
524+
Map.entry("jdouble", ValueLayout.JAVA_DOUBLE)
525+
);
526+
}
496527
}

src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,26 @@
2727

2828
import jdk.internal.foreign.abi.AbstractLinker;
2929
import jdk.internal.foreign.abi.LinkerOptions;
30+
import jdk.internal.foreign.abi.SharedUtils;
3031
import jdk.internal.foreign.abi.aarch64.CallArranger;
3132

3233
import java.lang.foreign.FunctionDescriptor;
34+
import java.lang.foreign.MemoryLayout;
35+
import java.lang.foreign.ValueLayout;
3336
import java.lang.invoke.MethodHandle;
3437
import java.lang.invoke.MethodType;
3538
import java.nio.ByteOrder;
39+
import java.util.Map;
3640

3741
/**
3842
* ABI implementation based on ARM document "Procedure Call Standard for
3943
* the ARM 64-bit Architecture".
4044
*/
4145
public final class LinuxAArch64Linker extends AbstractLinker {
4246

47+
static final Map<String, MemoryLayout> CANONICAL_LAYOUTS =
48+
SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT);
49+
4350
public static LinuxAArch64Linker getInstance() {
4451
final class Holder {
4552
private static final LinuxAArch64Linker INSTANCE = new LinuxAArch64Linker();
@@ -66,4 +73,9 @@ protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescrip
6673
protected ByteOrder linkerByteOrder() {
6774
return ByteOrder.LITTLE_ENDIAN;
6875
}
76+
77+
@Override
78+
public Map<String, MemoryLayout> canonicalLayouts() {
79+
return CANONICAL_LAYOUTS;
80+
}
6981
}

src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,26 @@
2727

2828
import jdk.internal.foreign.abi.AbstractLinker;
2929
import jdk.internal.foreign.abi.LinkerOptions;
30+
import jdk.internal.foreign.abi.SharedUtils;
3031
import jdk.internal.foreign.abi.aarch64.CallArranger;
3132

3233
import java.lang.foreign.FunctionDescriptor;
34+
import java.lang.foreign.MemoryLayout;
35+
import java.lang.foreign.ValueLayout;
3336
import java.lang.invoke.MethodHandle;
3437
import java.lang.invoke.MethodType;
3538
import java.nio.ByteOrder;
39+
import java.util.Map;
3640

3741
/**
3842
* ABI implementation for macOS on Apple silicon. Based on AAPCS with
3943
* changes to va_list and passing arguments on the stack.
4044
*/
4145
public final class MacOsAArch64Linker extends AbstractLinker {
4246

47+
static final Map<String, MemoryLayout> CANONICAL_LAYOUTS =
48+
SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT);
49+
4350
public static MacOsAArch64Linker getInstance() {
4451
final class Holder {
4552
private static final MacOsAArch64Linker INSTANCE = new MacOsAArch64Linker();
@@ -66,4 +73,9 @@ protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescrip
6673
protected ByteOrder linkerByteOrder() {
6774
return ByteOrder.LITTLE_ENDIAN;
6875
}
76+
77+
@Override
78+
public Map<String, MemoryLayout> canonicalLayouts() {
79+
return CANONICAL_LAYOUTS;
80+
}
6981
}

src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,25 @@
2828

2929
import jdk.internal.foreign.abi.AbstractLinker;
3030
import jdk.internal.foreign.abi.LinkerOptions;
31+
import jdk.internal.foreign.abi.SharedUtils;
3132
import jdk.internal.foreign.abi.aarch64.CallArranger;
3233

3334
import java.lang.foreign.FunctionDescriptor;
35+
import java.lang.foreign.MemoryLayout;
36+
import java.lang.foreign.ValueLayout;
3437
import java.lang.invoke.MethodHandle;
3538
import java.lang.invoke.MethodType;
3639
import java.nio.ByteOrder;
40+
import java.util.Map;
3741

3842
/**
3943
* ABI implementation for Windows/AArch64. Based on AAPCS with
4044
* changes to va_list.
4145
*/
4246
public final class WindowsAArch64Linker extends AbstractLinker {
47+
48+
static final Map<String, MemoryLayout> CANONICAL_LAYOUTS =
49+
SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR);
4350
private static WindowsAArch64Linker instance;
4451

4552
public static WindowsAArch64Linker getInstance() {
@@ -63,4 +70,9 @@ protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescrip
6370
protected ByteOrder linkerByteOrder() {
6471
return ByteOrder.LITTLE_ENDIAN;
6572
}
73+
74+
@Override
75+
public Map<String, MemoryLayout> canonicalLayouts() {
76+
return CANONICAL_LAYOUTS;
77+
}
6678
}

0 commit comments

Comments
 (0)