diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java index 3e6bfbb9e0f79..71ef4200eeb3e 100644 --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -34,6 +34,7 @@ import jdk.internal.reflect.Reflection; import java.lang.invoke.MethodHandle; +import java.nio.ByteOrder; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -195,6 +196,17 @@ * {@link MemorySegment} * * + *

+ * All the native linker implementations limit the function descriptors that they support to those that contain + * only so-called canonical layouts. A canonical layout has the following characteristics: + *

    + *
  1. Its alignment constraint is set to its natural alignment
  2. + *
  3. If it is a {@linkplain ValueLayout value layout}, its {@linkplain ValueLayout#order() byte order} is + * the {@linkplain ByteOrder#nativeOrder() native byte order}. + *
  4. If it is a {@linkplain GroupLayout group layout}, its size is a multiple of its alignment constraint, and
  5. + *
  6. It does not contain padding other than what is strictly required to align its non-padding layout elements, + * or to satisfy constraint 3
  7. + *
* *

Function pointers

* diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java index 0744435b98d68..ceccb0a1a7047 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/AbstractLinker.java @@ -25,6 +25,7 @@ package jdk.internal.foreign.abi; import jdk.internal.foreign.SystemLookup; +import jdk.internal.foreign.Utils; import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker; import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker; import jdk.internal.foreign.abi.aarch64.windows.WindowsAArch64Linker; @@ -40,9 +41,14 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.MemorySegment; +import java.lang.foreign.PaddingLayout; import java.lang.foreign.SequenceLayout; +import java.lang.foreign.StructLayout; +import java.lang.foreign.UnionLayout; +import java.lang.foreign.ValueLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; import java.util.Objects; public abstract sealed class AbstractLinker implements Linker permits LinuxAArch64Linker, MacOsAArch64Linker, @@ -62,7 +68,7 @@ private record LinkRequest(FunctionDescriptor descriptor, LinkerOptions options) public MethodHandle downcallHandle(FunctionDescriptor function, Option... options) { Objects.requireNonNull(function); Objects.requireNonNull(options); - checkHasNaturalAlignment(function); + checkLayouts(function); LinkerOptions optionSet = LinkerOptions.forDowncall(function, options); return DOWNCALL_CACHE.get(new LinkRequest(function, optionSet), linkRequest -> { @@ -80,7 +86,7 @@ public MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function Objects.requireNonNull(arena); Objects.requireNonNull(target); Objects.requireNonNull(function); - checkHasNaturalAlignment(function); + checkLayouts(function); SharedUtils.checkExceptions(target); LinkerOptions optionSet = LinkerOptions.forUpcall(function, options); @@ -101,22 +107,64 @@ public SystemLookup defaultLookup() { return SystemLookup.getInstance(); } - // Current limitation of the implementation: - // We don't support packed structs on some platforms, - // so reject them here explicitly - private static void checkHasNaturalAlignment(FunctionDescriptor descriptor) { - descriptor.returnLayout().ifPresent(AbstractLinker::checkHasNaturalAlignmentRecursive); - descriptor.argumentLayouts().forEach(AbstractLinker::checkHasNaturalAlignmentRecursive); + /** {@return byte order used by this linker} */ + protected abstract ByteOrder linkerByteOrder(); + + private void checkLayouts(FunctionDescriptor descriptor) { + descriptor.returnLayout().ifPresent(this::checkLayoutsRecursive); + descriptor.argumentLayouts().forEach(this::checkLayoutsRecursive); } - private static void checkHasNaturalAlignmentRecursive(MemoryLayout layout) { + private void checkLayoutsRecursive(MemoryLayout layout) { checkHasNaturalAlignment(layout); - if (layout instanceof GroupLayout gl) { - for (MemoryLayout member : gl.memberLayouts()) { - checkHasNaturalAlignmentRecursive(member); + if (layout instanceof ValueLayout vl) { + checkByteOrder(vl); + } else if (layout instanceof StructLayout sl) { + long offset = 0; + long lastUnpaddedOffset = 0; + for (MemoryLayout member : sl.memberLayouts()) { + // check element offset before recursing so that an error points at the + // outermost layout first + checkMemberOffset(sl, member, lastUnpaddedOffset, offset); + checkLayoutsRecursive(member); + + offset += member.bitSize(); + if (!(member instanceof PaddingLayout)) { + lastUnpaddedOffset = offset; + } + } + checkGroupSize(sl, lastUnpaddedOffset); + } else if (layout instanceof UnionLayout ul) { + long maxUnpaddedLayout = 0; + for (MemoryLayout member : ul.memberLayouts()) { + checkLayoutsRecursive(member); + if (!(member instanceof PaddingLayout)) { + maxUnpaddedLayout = Long.max(maxUnpaddedLayout, member.bitSize()); + } } + checkGroupSize(ul, maxUnpaddedLayout); } else if (layout instanceof SequenceLayout sl) { - checkHasNaturalAlignmentRecursive(sl.elementLayout()); + checkLayoutsRecursive(sl.elementLayout()); + } + } + + // check for trailing padding + private static void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) { + long expectedSize = Utils.alignUp(maxUnpaddedOffset, gl.bitAlignment()); + if (gl.bitSize() != expectedSize) { + throw new IllegalArgumentException("Layout '" + gl + "' has unexpected size: " + + gl.bitSize() + " != " + expectedSize); + } + } + + // checks both that there is no excess padding between 'memberLayout' and + // the previous layout + private static void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout, + long lastUnpaddedOffset, long offset) { + long expectedOffset = Utils.alignUp(lastUnpaddedOffset, memberLayout.bitAlignment()); + if (expectedOffset != offset) { + throw new IllegalArgumentException("Member layout '" + memberLayout + "', of '" + parent + "'" + + " found at unexpected offset: " + offset + " != " + expectedOffset); } } @@ -125,4 +173,10 @@ private static void checkHasNaturalAlignment(MemoryLayout layout) { throw new IllegalArgumentException("Layout bit alignment must be natural alignment: " + layout); } } + + private void checkByteOrder(ValueLayout vl) { + if (vl.order() != linkerByteOrder()) { + throw new IllegalArgumentException("Layout does not have the right byte order: " + vl); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java index 6c7fed1a48105..28ff423d3883d 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/linux/LinuxAArch64Linker.java @@ -32,6 +32,7 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; /** * ABI implementation based on ARM document "Procedure Call Standard for @@ -60,4 +61,9 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { return CallArranger.LINUX.arrangeUpcall(targetType, function, options); } + + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.LITTLE_ENDIAN; + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java index 3e18c43d2bab8..1a1bdc31be999 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/macos/MacOsAArch64Linker.java @@ -32,6 +32,7 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; /** * ABI implementation for macOS on Apple silicon. Based on AAPCS with @@ -60,4 +61,9 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { return CallArranger.MACOS.arrangeUpcall(targetType, function, options); } + + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.LITTLE_ENDIAN; + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java index 5f5478812f2ce..a654d23fc89cf 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/aarch64/windows/WindowsAArch64Linker.java @@ -33,6 +33,7 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; /** * ABI implementation for Windows/AArch64. Based on AAPCS with @@ -57,4 +58,9 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { return CallArranger.WINDOWS.arrangeUpcall(targetType, function, options); } + + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.LITTLE_ENDIAN; + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java index 3c73f75bfc018..b945aa617cbae 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/fallback/FallbackLinker.java @@ -43,6 +43,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.Reference; +import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; @@ -117,6 +118,11 @@ protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescrip }; } + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.nativeOrder(); + } + private static MemorySegment makeCif(MethodType methodType, FunctionDescriptor function, FFIABI abi, Arena scope) { MemorySegment argTypes = scope.allocate(function.argumentLayouts().size() * ADDRESS.byteSize()); List argLayouts = function.argumentLayouts(); diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java index eb11346c13749..0ffba55d92918 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/riscv64/linux/LinuxRISCV64Linker.java @@ -32,6 +32,7 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; public final class LinuxRISCV64Linker extends AbstractLinker { @@ -56,4 +57,9 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { return LinuxRISCV64CallArranger.arrangeUpcall(targetType, function, options); } + + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.LITTLE_ENDIAN; + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java index e61b743526c2c..91c3beca2294f 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/sysv/SysVx64Linker.java @@ -31,6 +31,7 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; /** * ABI implementation based on System V ABI AMD64 supplement v.0.99.6 @@ -58,4 +59,9 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { return CallArranger.arrangeUpcall(targetType, function, options); } + + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.LITTLE_ENDIAN; + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java index 16cf325f6e6f7..574f2b63411ce 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/x64/windows/Windowsx64Linker.java @@ -30,6 +30,7 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; +import java.nio.ByteOrder; /** * ABI implementation based on Windows ABI AMD64 supplement v.0.99.6 @@ -57,4 +58,9 @@ protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDe protected UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { return CallArranger.arrangeUpcall(targetType, function, options); } + + @Override + protected ByteOrder linkerByteOrder() { + return ByteOrder.LITTLE_ENDIAN; + } } diff --git a/test/jdk/java/foreign/TestIllegalLink.java b/test/jdk/java/foreign/TestIllegalLink.java index cc790667cc010..9f01223a1c086 100644 --- a/test/jdk/java/foreign/TestIllegalLink.java +++ b/test/jdk/java/foreign/TestIllegalLink.java @@ -59,7 +59,7 @@ public void testIllegalLayouts(FunctionDescriptor desc, String expectedException fail("Expected IllegalArgumentException was not thrown"); } catch (IllegalArgumentException e) { assertTrue(e.getMessage().contains(expectedExceptionMessage), - e.getMessage() + " != " + expectedExceptionMessage); + e.getMessage() + " does not contain " + expectedExceptionMessage); } } @@ -154,7 +154,44 @@ public static Object[][] types() { ))), "Layout bit alignment must be natural alignment" }, + { + FunctionDescriptor.ofVoid(MemoryLayout.structLayout( + ValueLayout.JAVA_INT, + MemoryLayout.paddingLayout(32), // no excess padding + ValueLayout.JAVA_INT)), + "unexpected offset" + }, + { + FunctionDescriptor.of(C_INT.withOrder(nonNativeOrder())), + "Layout does not have the right byte order" + }, + { + FunctionDescriptor.of(MemoryLayout.structLayout(C_INT.withOrder(nonNativeOrder()))), + "Layout does not have the right byte order" + }, + { + FunctionDescriptor.of(MemoryLayout.structLayout(MemoryLayout.sequenceLayout(C_INT.withOrder(nonNativeOrder())))), + "Layout does not have the right byte order" + }, + { + FunctionDescriptor.ofVoid(MemoryLayout.structLayout( + ValueLayout.JAVA_LONG, + ValueLayout.JAVA_INT)), // missing trailing padding + "has unexpected size" + }, + { + FunctionDescriptor.ofVoid(MemoryLayout.structLayout( + ValueLayout.JAVA_INT, + MemoryLayout.paddingLayout(32))), // too much trailing padding + "has unexpected size" + }, }; } + private static ByteOrder nonNativeOrder() { + return ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN + ? ByteOrder.BIG_ENDIAN + : ByteOrder.LITTLE_ENDIAN; + } + } diff --git a/test/jdk/java/foreign/TestUpcallStructScope.java b/test/jdk/java/foreign/TestUpcallStructScope.java index fcb08b546cb26..4a787ddc2bf97 100644 --- a/test/jdk/java/foreign/TestUpcallStructScope.java +++ b/test/jdk/java/foreign/TestUpcallStructScope.java @@ -58,7 +58,8 @@ public class TestUpcallStructScope extends NativeTestHelper { static final MemoryLayout S_PDI_LAYOUT = MemoryLayout.structLayout( C_POINTER.withName("p0"), C_DOUBLE.withName("p1"), - C_INT.withName("p2") + C_INT.withName("p2"), + MemoryLayout.paddingLayout(32) ); static {