8340205: Native linker allows MemoryLayout consisting of only PaddingLayout

Reviewed-by: jvernee, mcimadamore
This commit is contained in:
Per Minborg 2024-11-25 07:42:57 +00:00
parent 6f622da7fb
commit 68ba7ee5c8
3 changed files with 191 additions and 51 deletions

View file

@ -241,50 +241,40 @@ import java.util.stream.Stream;
* </tbody>
* </table></blockquote>
* <p>
* All native linker implementations support a well-defined subset of layouts. More formally,
* a layout {@code L} is supported by a native linker {@code NL} if:
* A native linker only supports function descriptors whose argument/return layouts are
* <em>well-formed</em> layouts. More formally, a layout `L` is well-formed if:
* <ul>
* <li>{@code L} is a value layout {@code V} and {@code V.withoutName()} is a canonical layout</li>
* <li>{@code L} is a value layout and {@code L} is derived from a canonical layout
* {@code C} such that {@code L.byteAlignment() <= C.byteAlignment()}</li>
* <li>{@code L} is a sequence layout {@code S} and all the following conditions hold:
* <ol>
* <li>the alignment constraint of {@code S} is set to its
* <a href="MemoryLayout.html#layout-align">natural alignment</a>, and</li>
* <li>{@code S.elementLayout()} is a layout supported by {@code NL}.</li>
* <li>{@code L.byteAlignment()} is equal to the sequence layout's <em>natural alignment</em>
* , and</li>
* <li>{@code S.elementLayout()} is a well-formed layout.</li>
* </ol>
* </li>
* <li>{@code L} is a group layout {@code G} and all the following conditions hold:
* <ol>
* <li>the alignment constraint of {@code G} is set to its
* <a href="MemoryLayout.html#layout-align">natural alignment</a>;</li>
* <li>the size of {@code G} is a multiple of its alignment constraint;</li>
* <li>each member layout in {@code G.memberLayouts()} is either a padding layout or
* a layout supported by {@code NL}, and</li>
* <li>{@code G} does not contain padding other than what is strictly required to align
* its non-padding layout elements, or to satisfy (2).</li>
* <li>{@code G.byteAlignment()} is equal to the group layout's <em>natural alignment</em></li>
* <li>{@code G.byteSize()} is a multiple of {@code G.byteAlignment()}</li>
* <li>Each member layout in {@code G.memberLayouts()} is either a padding layout or a
* well-formed layout</li>
* <li>Each non-padding member layout {@code E} in {@code G.memberLayouts()} follows an
* optional padding member layout, whose size is the minimum size required to
* align {@code E}</li>
* <li>{@code G} contains an optional trailing padding member layout, whose size is the
* minimum size that satisfies (2)</li>
* </ol>
* </li>
* </ul>
*
* Linker implementations may optionally support additional layouts, such as
* <em>packed</em> struct layouts. A packed struct is a struct in which there is
* at least one member layout {@code L} that has an alignment constraint less strict
* than its natural alignment. This allows to avoid padding between member layouts,
* as well as avoiding padding at the end of the struct layout. For example:
* {@snippet lang = java:
* // No padding between the 2 element layouts:
* MemoryLayout noFieldPadding = MemoryLayout.structLayout(
* ValueLayout.JAVA_INT,
* ValueLayout.JAVA_DOUBLE.withByteAlignment(4));
*
* // No padding at the end of the struct:
* MemoryLayout noTrailingPadding = MemoryLayout.structLayout(
* ValueLayout.JAVA_DOUBLE.withByteAlignment(4),
* ValueLayout.JAVA_INT);
* }
* <p>
* A native linker only supports function descriptors whose argument/return layouts are
* layouts supported by that linker and are not sequence layouts.
* 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 <em>packed</em> struct layouts -- struct
* layouts whose member layouts feature relaxed alignment constraints, to avoid
* the insertion of additional padding.
*
* <h3 id="function-pointers">Function pointers</h3>
*

View file

@ -57,7 +57,6 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.HashSet;
import java.util.List;
import java.nio.ByteOrder;
import java.util.Objects;
import java.util.Set;
@ -189,6 +188,7 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
checkHasNaturalAlignment(layout);
long offset = 0;
long lastUnpaddedOffset = 0;
PaddingLayout preceedingPadding = null;
for (MemoryLayout member : sl.memberLayouts()) {
// check element offset before recursing so that an error points at the
// outermost layout first
@ -196,29 +196,65 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
checkStructMember(member, offset);
offset += member.byteSize();
if (!(member instanceof PaddingLayout)) {
if (!(member instanceof PaddingLayout pl)) {
lastUnpaddedOffset = offset;
if (preceedingPadding != null) {
preceedingPadding = null;
}
} else {
if (preceedingPadding != null) {
throw new IllegalArgumentException("The padding layout " + pl +
" was preceded by another padding layout " + preceedingPadding +
inMessage(sl));
}
preceedingPadding = pl;
}
}
checkGroupSize(sl, lastUnpaddedOffset);
checkNotAllPadding(sl);
checkGroup(sl, lastUnpaddedOffset);
} else if (layout instanceof UnionLayout ul) {
checkHasNaturalAlignment(layout);
long maxUnpaddedLayout = 0;
// We need to know this up front
long maxUnpaddedLayout = ul.memberLayouts().stream()
.filter(l -> !(l instanceof PaddingLayout))
.mapToLong(MemoryLayout::byteSize)
.max()
.orElse(0);
boolean hasPadding = false;
for (MemoryLayout member : ul.memberLayouts()) {
checkLayoutRecursive(member);
if (!(member instanceof PaddingLayout)) {
maxUnpaddedLayout = Long.max(maxUnpaddedLayout, member.byteSize());
if (member instanceof PaddingLayout pl) {
if (hasPadding) {
throw new IllegalArgumentException("More than one padding" + inMessage(ul));
}
hasPadding = true;
if (pl.byteSize() <= maxUnpaddedLayout) {
throw new IllegalArgumentException("Superfluous padding " + pl + inMessage(ul));
}
}
}
checkGroupSize(ul, maxUnpaddedLayout);
checkGroup(ul, maxUnpaddedLayout);
} else if (layout instanceof SequenceLayout sl) {
checkHasNaturalAlignment(layout);
if (sl.elementLayout() instanceof PaddingLayout pl) {
throw memberException(sl, pl,
"not supported because a sequence of a padding layout is not allowed");
}
checkLayoutRecursive(sl.elementLayout());
}
}
// check for trailing padding
private void checkGroupSize(GroupLayout gl, long maxUnpaddedOffset) {
// check elements are not all padding layouts
private static void checkNotAllPadding(StructLayout sl) {
if (!sl.memberLayouts().isEmpty() && sl.memberLayouts().stream().allMatch(e -> e instanceof PaddingLayout)) {
throw new IllegalArgumentException("Layout '" + sl + "' is non-empty and only has padding layouts");
}
}
// check trailing padding
private static void checkGroup(GroupLayout gl, long maxUnpaddedOffset) {
long expectedSize = Utils.alignUp(maxUnpaddedOffset, gl.byteAlignment());
if (gl.byteSize() != expectedSize) {
throw new IllegalArgumentException("Layout '" + gl + "' has unexpected size: "
@ -226,17 +262,28 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
}
}
private static String inMessage(GroupLayout gl) {
return " in " + gl;
}
// checks both that there is no excess padding between 'memberLayout' and
// the previous layout
private void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout,
private static void checkMemberOffset(StructLayout parent, MemoryLayout memberLayout,
long lastUnpaddedOffset, long offset) {
long expectedOffset = Utils.alignUp(lastUnpaddedOffset, memberLayout.byteAlignment());
if (expectedOffset != offset) {
throw new IllegalArgumentException("Member layout '" + memberLayout + "', of '" + parent + "'" +
" found at unexpected offset: " + offset + " != " + expectedOffset);
throw memberException(parent, memberLayout,
"found at unexpected offset: " + offset + " != " + expectedOffset);
}
}
private static IllegalArgumentException memberException(MemoryLayout parent,
MemoryLayout member,
String info) {
return new IllegalArgumentException(
"Member layout '" + member + "', of '" + parent + "' " + info);
}
private void checkSupported(ValueLayout valueLayout) {
valueLayout = valueLayout.withoutName();
if (valueLayout instanceof AddressLayout addressLayout) {