8289148: j.l.foreign.VaList::nextVarg call could throw IndexOutOfBoundsException or even crash the VM

8289333: Specification of method j.l.foreign.VaList::skip deserves clarification
8289156: j.l.foreign.VaList::skip call could throw java.lang.IndexOutOfBoundsException: Out of bound access on segment

Reviewed-by: mcimadamore
This commit is contained in:
Jorn Vernee 2022-07-12 11:25:45 +00:00
parent c3806b93c4
commit 3164c98f4c
7 changed files with 378 additions and 160 deletions

View file

@ -25,6 +25,7 @@
*/
package java.lang.foreign;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
@ -50,7 +51,16 @@ import jdk.internal.reflect.Reflection;
* <p>
* As such, this interface only supports reading {@code int}, {@code double},
* and any other type that fits into a {@code long}.
*
* <h2 id="safety">Safety considerations</h2>
* It is possible for clients to access elements outside the spatial bounds of a variable argument list.
* Variable argument list implementations will try to detect out-of-bounds reads on a best-effort basis.
* <p>
* Whether this detection succeeds depends on the factory method used to create the variable argument list:
* <ul>
* <li>Variable argument lists created <em>safely</em>, using {@link #make(Consumer, MemorySession)} are capable of detecting out-of-bounds reads;</li>
* <li>Variable argument lists created <em>unsafely</em>, using {@link #ofAddress(MemoryAddress, MemorySession)} are not capable of detecting out-of-bounds reads</li>
* </ul>
* <p>
* This class is not thread safe, and all accesses should occur within a single thread
* (regardless of the memory session associated with the variable arity list).
*
@ -74,6 +84,7 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* {@linkplain MemorySession#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread other than the thread owning
* the {@linkplain #session() session} associated with this variable argument list.
* @throws NoSuchElementException if an <a href=VaList.html#safety>out-of-bounds</a> read is detected.
*/
int nextVarg(ValueLayout.OfInt layout);
@ -87,6 +98,7 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* {@linkplain MemorySession#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread other than the thread owning
* the {@linkplain #session() session} associated with this variable argument list.
* @throws NoSuchElementException if an <a href=VaList.html#safety>out-of-bounds</a> read is detected.
*/
long nextVarg(ValueLayout.OfLong layout);
@ -100,6 +112,7 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* {@linkplain MemorySession#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread other than the thread owning
* the {@linkplain #session() session} associated with this variable argument list.
* @throws NoSuchElementException if an <a href=VaList.html#safety>out-of-bounds</a> read is detected.
*/
double nextVarg(ValueLayout.OfDouble layout);
@ -113,6 +126,7 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* {@linkplain MemorySession#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread other than the thread owning
* the {@linkplain #session() session} associated with this variable argument list.
* @throws NoSuchElementException if an <a href=VaList.html#safety>out-of-bounds</a> read is detected.
*/
MemoryAddress nextVarg(ValueLayout.OfAddress layout);
@ -135,6 +149,7 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* {@linkplain MemorySession#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread other than the thread owning
* the {@linkplain #session() session} associated with this variable argument list.
* @throws NoSuchElementException if an <a href=VaList.html#safety>out-of-bounds</a> read is detected.
*/
MemorySegment nextVarg(GroupLayout layout, SegmentAllocator allocator);
@ -146,6 +161,7 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* {@linkplain MemorySession#isAlive() alive}.
* @throws WrongThreadException if this method is called from a thread other than the thread owning
* the {@linkplain #session() session} associated with this variable argument list.
* @throws NoSuchElementException if an <a href=VaList.html#safety>out-of-bounds</a> read is detected.
*/
void skip(MemoryLayout... layouts);
@ -185,6 +201,8 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible.
*
* @implNote variable argument lists created using this method can not detect <a href=VaList.html#safety>out-of-bounds</a> reads.
*
* @param address a memory address pointing to an existing variable argument list.
* @param session the memory session to be associated with the returned variable argument list.
* @return a new variable argument list backed by the memory region at {@code address}.
@ -214,6 +232,8 @@ sealed public interface VaList extends Addressable permits WinVaList, SysVVaList
* Note that when there are no elements added to the created va list,
* this method will return the same as {@link #empty()}.
*
* @implNote variable argument lists created using this method can detect <a href=VaList.html#safety>out-of-bounds</a> reads.
*
* @param actions a consumer for a builder (see {@link Builder}) which can be used to specify the elements
* of the underlying variable argument list.
* @param session the memory session to be associated with the new variable arity list.

View file

@ -56,6 +56,7 @@ import java.lang.ref.Reference;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -382,13 +383,15 @@ public class SharedUtils {
return firstPos != -1 && argIndex >= firstPos;
}
public static NoSuchElementException newVaListNSEE(MemoryLayout layout) {
return new NoSuchElementException("No such element: " + layout);
}
public static class SimpleVaArg {
public final Class<?> carrier;
public final MemoryLayout layout;
public final Object value;
public SimpleVaArg(Class<?> carrier, MemoryLayout layout, Object value) {
this.carrier = carrier;
public SimpleVaArg(MemoryLayout layout, Object value) {
this.layout = layout;
this.value = value;
}

View file

@ -52,8 +52,6 @@ import static jdk.internal.foreign.abi.aarch64.CallArranger.MAX_REGISTER_ARGUMEN
public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
private static final Unsafe U = Unsafe.getUnsafe();
static final Class<?> CARRIER = MemoryAddress.class;
// See AAPCS Appendix B "Variable Argument Lists" for definition of
// va_list on AArch64.
//
@ -73,6 +71,8 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
AArch64.C_INT.withName("__vr_offs")
).withName("__va_list");
private static final long STACK_SLOT_SIZE = 8;
private static final MemoryLayout GP_REG
= MemoryLayout.paddingLayout(64).withBitAlignment(64);
private static final MemoryLayout FP_REG
@ -101,22 +101,32 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
= new SharedUtils.EmptyVaList(emptyListAddress());
private final MemorySegment segment;
private MemorySegment stack;
private final MemorySegment gpRegsArea;
private final long gpLimit;
private final MemorySegment fpRegsArea;
private final long fpLimit;
private LinuxAArch64VaList(MemorySegment segment, MemorySegment gpRegsArea, MemorySegment fpRegsArea) {
private LinuxAArch64VaList(MemorySegment segment, MemorySegment stack,
MemorySegment gpRegsArea, long gpLimit, MemorySegment fpRegsArea, long fpLimit) {
this.segment = segment;
this.stack = stack;
this.gpRegsArea = gpRegsArea;
this.gpLimit = gpLimit;
this.fpRegsArea = fpRegsArea;
this.fpLimit = fpLimit;
}
private static LinuxAArch64VaList readFromSegment(MemorySegment segment) {
MemorySegment stack = MemorySegment.ofAddress(stackPtr(segment),
Long.MAX_VALUE, segment.session()); // size unknown
MemorySegment gpRegsArea = MemorySegment.ofAddress(grTop(segment).addOffset(-MAX_GP_OFFSET),
MAX_GP_OFFSET, segment.session());
MemorySegment fpRegsArea = MemorySegment.ofAddress(vrTop(segment).addOffset(-MAX_FP_OFFSET),
MAX_FP_OFFSET, segment.session());
return new LinuxAArch64VaList(segment, gpRegsArea, fpRegsArea);
return new LinuxAArch64VaList(segment, stack, gpRegsArea, MAX_GP_OFFSET, fpRegsArea, MAX_FP_OFFSET);
}
private static MemoryAddress emptyListAddress() {
@ -165,12 +175,17 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
return offs;
}
private MemoryAddress stackPtr() {
private static MemoryAddress stackPtr(MemorySegment segment) {
return (MemoryAddress) VH_stack.get(segment);
}
private void stackPtr(MemoryAddress ptr) {
VH_stack.set(segment, ptr);
private MemoryAddress stackPtr() {
return stackPtr(segment);
}
private void setStack(MemorySegment newStack) {
stack = newStack;
VH_stack.set(segment, stack.address());
}
private void consumeGPSlots(int num) {
@ -199,54 +214,62 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
return fpRegsArea.byteSize() + vrOffs();
}
private void preAlignStack(MemoryLayout layout) {
if (layout.byteAlignment() > 8) {
stackPtr(Utils.alignUp(stackPtr(), 16));
private long preAlignOffset(MemoryLayout layout) {
long alignmentOffset = 0;
if (layout.byteAlignment() > STACK_SLOT_SIZE) {
long addr = stack.address().toRawLongValue();
alignmentOffset = Utils.alignUp(addr, 16) - addr;
}
return alignmentOffset;
}
private void preAlignStack(MemoryLayout layout) {
setStack(stack.asSlice(preAlignOffset(layout)));
}
private void postAlignStack(MemoryLayout layout) {
stackPtr(Utils.alignUp(stackPtr().addOffset(layout.byteSize()), 8));
setStack(stack.asSlice(Utils.alignUp(layout.byteSize(), STACK_SLOT_SIZE)));
}
@Override
public int nextVarg(ValueLayout.OfInt layout) {
return (int) read(int.class, layout);
return (int) read(layout);
}
@Override
public long nextVarg(ValueLayout.OfLong layout) {
return (long) read(long.class, layout);
return (long) read(layout);
}
@Override
public double nextVarg(ValueLayout.OfDouble layout) {
return (double) read(double.class, layout);
return (double) read(layout);
}
@Override
public MemoryAddress nextVarg(ValueLayout.OfAddress layout) {
return (MemoryAddress) read(MemoryAddress.class, layout);
return (MemoryAddress) read(layout);
}
@Override
public MemorySegment nextVarg(GroupLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(allocator);
return (MemorySegment) read(MemorySegment.class, layout, allocator);
return (MemorySegment) read( layout, allocator);
}
private Object read(Class<?> carrier, MemoryLayout layout) {
return read(carrier, layout, THROWING_ALLOCATOR);
private Object read(MemoryLayout layout) {
return read(layout, THROWING_ALLOCATOR);
}
private Object read(Class<?> carrier, MemoryLayout layout, SegmentAllocator allocator) {
private Object read(MemoryLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(layout);
TypeClass typeClass = TypeClass.classifyLayout(layout);
if (isRegOverflow(currentGPOffset(), currentFPOffset(), typeClass, layout)) {
checkStackElement(layout);
preAlignStack(layout);
return switch (typeClass) {
case STRUCT_REGISTER, STRUCT_HFA, STRUCT_REFERENCE -> {
MemorySegment slice = MemorySegment.ofAddress(stackPtr(), layout.byteSize(), session());
MemorySegment slice = stack.asSlice(0, layout.byteSize());
MemorySegment seg = allocator.allocate(layout);
seg.copyFrom(slice);
postAlignStack(layout);
@ -254,7 +277,7 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
}
case POINTER, INTEGER, FLOAT -> {
VarHandle reader = layout.varHandle();
MemorySegment slice = MemorySegment.ofAddress(stackPtr(), layout.byteSize(), session());
MemorySegment slice = stack.asSlice(0, layout.byteSize());
Object res = reader.get(slice);
postAlignStack(layout);
yield res;
@ -263,6 +286,7 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
} else {
return switch (typeClass) {
case STRUCT_REGISTER -> {
checkGPElement(layout, numSlots(layout));
// Struct is passed packed in integer registers.
MemorySegment value = allocator.allocate(layout);
long offset = 0;
@ -275,6 +299,7 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
yield value;
}
case STRUCT_HFA -> {
checkFPElement(layout, numSlots(layout));
// Struct is passed with each element in a separate floating
// point register.
MemorySegment value = allocator.allocate(layout);
@ -290,6 +315,7 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
yield value;
}
case STRUCT_REFERENCE -> {
checkGPElement(layout, 1);
// Struct is passed indirectly via a pointer in an integer register.
VarHandle ptrReader = AArch64.C_POINTER.varHandle();
MemoryAddress ptr = (MemoryAddress) ptrReader.get(
@ -302,12 +328,14 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
yield seg;
}
case POINTER, INTEGER -> {
checkGPElement(layout, 1);
VarHandle reader = layout.varHandle();
Object res = reader.get(gpRegsArea.asSlice(currentGPOffset()));
consumeGPSlots(1);
yield res;
}
case FLOAT -> {
checkFPElement(layout, 1);
VarHandle reader = layout.varHandle();
Object res = reader.get(fpRegsArea.asSlice(currentFPOffset()));
consumeFPSlots(1);
@ -317,6 +345,24 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
}
}
private void checkGPElement(MemoryLayout layout, long slots) {
if ((grOffs() + MAX_GP_OFFSET) + (slots * GP_SLOT_SIZE) > gpLimit) {
throw SharedUtils.newVaListNSEE(layout);
}
}
private void checkFPElement(MemoryLayout layout, long slots) {
if ((vrOffs() + MAX_FP_OFFSET) + (slots * FP_SLOT_SIZE) > fpLimit) {
throw SharedUtils.newVaListNSEE(layout);
}
}
private void checkStackElement(MemoryLayout layout) {
if (preAlignOffset(layout) + layout.byteSize() > stack.byteSize()) {
throw SharedUtils.newVaListNSEE(layout);
}
}
@Override
public void skip(MemoryLayout... layouts) {
Objects.requireNonNull(layouts);
@ -325,14 +371,20 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
Objects.requireNonNull(layout);
TypeClass typeClass = TypeClass.classifyLayout(layout);
if (isRegOverflow(currentGPOffset(), currentFPOffset(), typeClass, layout)) {
checkStackElement(layout);
preAlignStack(layout);
postAlignStack(layout);
} else if (typeClass == TypeClass.FLOAT || typeClass == TypeClass.STRUCT_HFA) {
consumeFPSlots(numSlots(layout));
long slots = numSlots(layout);
checkFPElement(layout, slots);
consumeFPSlots((int) slots);
} else if (typeClass == TypeClass.STRUCT_REFERENCE) {
checkGPElement(layout, 1);
consumeGPSlots(1);
} else {
consumeGPSlots(numSlots(layout));
long slots = numSlots(layout);
checkGPElement(layout, slots);
consumeGPSlots((int) slots);
}
}
}
@ -354,7 +406,7 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
public VaList copy() {
MemorySegment copy = MemorySegment.allocateNative(LAYOUT, segment.session());
copy.copyFrom(segment);
return new LinuxAArch64VaList(copy, gpRegsArea, fpRegsArea);
return new LinuxAArch64VaList(copy, stack, gpRegsArea, gpLimit, fpRegsArea, fpLimit);
}
@Override
@ -362,8 +414,8 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
return segment.address();
}
private static int numSlots(MemoryLayout layout) {
return (int) Utils.alignUp(layout.byteSize(), 8) / 8;
private static long numSlots(MemoryLayout layout) {
return Utils.alignUp(layout.byteSize(), STACK_SLOT_SIZE) / STACK_SLOT_SIZE;
}
private static boolean isRegOverflow(long currentGPOffset, long currentFPOffset,
@ -405,35 +457,35 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
@Override
public Builder addVarg(ValueLayout.OfInt layout, int value) {
return arg(int.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfLong layout, long value) {
return arg(long.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfDouble layout, double value) {
return arg(double.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfAddress layout, Addressable value) {
return arg(MemoryAddress.class, layout, value.address());
return arg(layout, value.address());
}
@Override
public Builder addVarg(GroupLayout layout, MemorySegment value) {
return arg(MemorySegment.class, layout, value);
return arg(layout, value);
}
private Builder arg(Class<?> carrier, MemoryLayout layout, Object value) {
private Builder arg(MemoryLayout layout, Object value) {
Objects.requireNonNull(layout);
Objects.requireNonNull(value);
TypeClass typeClass = TypeClass.classifyLayout(layout);
if (isRegOverflow(currentGPOffset, currentFPOffset, typeClass, layout)) {
stackArgs.add(new SimpleVaArg(carrier, layout, value));
stackArgs.add(new SimpleVaArg(layout, value));
} else {
switch (typeClass) {
case STRUCT_REGISTER -> {
@ -495,30 +547,32 @@ public non-sealed class LinuxAArch64VaList implements VaList, Scoped {
SegmentAllocator allocator = SegmentAllocator.newNativeArena(session);
MemorySegment vaListSegment = allocator.allocate(LAYOUT);
MemoryAddress stackArgsPtr = MemoryAddress.NULL;
MemorySegment stackArgsSegment;
if (!stackArgs.isEmpty()) {
long stackArgsSize = stackArgs.stream()
.reduce(0L, (acc, e) -> acc + Utils.alignUp(e.layout.byteSize(), 8), Long::sum);
MemorySegment stackArgsSegment = allocator.allocate(stackArgsSize, 16);
stackArgsPtr = stackArgsSegment.address();
.reduce(0L, (acc, e) -> acc + Utils.alignUp(e.layout.byteSize(), STACK_SLOT_SIZE), Long::sum);
stackArgsSegment = allocator.allocate(stackArgsSize, 16);
MemorySegment writeCursor = stackArgsSegment;
for (SimpleVaArg arg : stackArgs) {
final long alignedSize = Utils.alignUp(arg.layout.byteSize(), 8);
stackArgsSegment = Utils.alignUp(stackArgsSegment, alignedSize);
final long alignedSize = Utils.alignUp(arg.layout.byteSize(), STACK_SLOT_SIZE);
writeCursor = Utils.alignUp(writeCursor, alignedSize);
VarHandle writer = arg.varHandle();
writer.set(stackArgsSegment, arg.value);
stackArgsSegment = stackArgsSegment.asSlice(alignedSize);
writer.set(writeCursor, arg.value);
writeCursor = writeCursor.asSlice(alignedSize);
}
} else {
stackArgsSegment = MemorySegment.ofAddress(MemoryAddress.NULL, 0, session);
}
VH_gr_top.set(vaListSegment, gpRegs.asSlice(gpRegs.byteSize()).address());
VH_vr_top.set(vaListSegment, fpRegs.asSlice(fpRegs.byteSize()).address());
VH_stack.set(vaListSegment, stackArgsPtr);
VH_stack.set(vaListSegment, stackArgsSegment.address());
VH_gr_offs.set(vaListSegment, -MAX_GP_OFFSET);
VH_vr_offs.set(vaListSegment, -MAX_FP_OFFSET);
assert gpRegs.session().ownerThread() == vaListSegment.session().ownerThread();
assert fpRegs.session().ownerThread() == vaListSegment.session().ownerThread();
return new LinuxAArch64VaList(vaListSegment, gpRegs, fpRegs);
return new LinuxAArch64VaList(vaListSegment, stackArgsSegment, gpRegs, currentGPOffset, fpRegs, currentFPOffset);
}
}
}

View file

@ -46,7 +46,6 @@ import static jdk.internal.foreign.abi.SharedUtils.alignUp;
* char* instead of the structure defined in the AAPCS.
*/
public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
public static final Class<?> CARRIER = MemoryAddress.class;
private static final long VA_SLOT_SIZE_BYTES = 8;
private static final VarHandle VH_address = C_POINTER.varHandle();
@ -66,41 +65,42 @@ public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
@Override
public int nextVarg(ValueLayout.OfInt layout) {
return (int) read(int.class, layout);
return (int) read(layout);
}
@Override
public long nextVarg(ValueLayout.OfLong layout) {
return (long) read(long.class, layout);
return (long) read(layout);
}
@Override
public double nextVarg(ValueLayout.OfDouble layout) {
return (double) read(double.class, layout);
return (double) read(layout);
}
@Override
public MemoryAddress nextVarg(ValueLayout.OfAddress layout) {
return (MemoryAddress) read(MemoryAddress.class, layout);
return (MemoryAddress) read(layout);
}
@Override
public MemorySegment nextVarg(GroupLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(allocator);
return (MemorySegment) read(MemorySegment.class, layout, allocator);
return (MemorySegment) read(layout, allocator);
}
private Object read(Class<?> carrier, MemoryLayout layout) {
return read(carrier, layout, SharedUtils.THROWING_ALLOCATOR);
private Object read(MemoryLayout layout) {
return read(layout, SharedUtils.THROWING_ALLOCATOR);
}
private Object read(Class<?> carrier, MemoryLayout layout, SegmentAllocator allocator) {
private Object read(MemoryLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(layout);
Object res;
if (carrier == MemorySegment.class) {
if (layout instanceof GroupLayout) {
TypeClass typeClass = TypeClass.classifyLayout(layout);
res = switch (typeClass) {
case STRUCT_REFERENCE -> {
checkElement(layout, VA_SLOT_SIZE_BYTES);
MemoryAddress structAddr = (MemoryAddress) VH_address.get(segment);
MemorySegment struct = MemorySegment.ofAddress(structAddr, layout.byteSize(), session());
MemorySegment seg = allocator.allocate(layout);
@ -109,14 +109,17 @@ public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
yield seg;
}
case STRUCT_REGISTER, STRUCT_HFA -> {
long size = alignUp(layout.byteSize(), VA_SLOT_SIZE_BYTES);
checkElement(layout, size);
MemorySegment struct = allocator.allocate(layout)
.copyFrom(segment.asSlice(0, layout.byteSize()));
segment = segment.asSlice(alignUp(layout.byteSize(), VA_SLOT_SIZE_BYTES));
segment = segment.asSlice(size);
yield struct;
}
default -> throw new IllegalStateException("Unexpected TypeClass: " + typeClass);
};
} else {
checkElement(layout, VA_SLOT_SIZE_BYTES);
VarHandle reader = layout.varHandle();
res = reader.get(segment);
segment = segment.asSlice(VA_SLOT_SIZE_BYTES);
@ -124,6 +127,13 @@ public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
return res;
}
private static long sizeOf(MemoryLayout layout) {
return switch (TypeClass.classifyLayout(layout)) {
case STRUCT_REGISTER, STRUCT_HFA -> alignUp(layout.byteSize(), VA_SLOT_SIZE_BYTES);
default -> VA_SLOT_SIZE_BYTES;
};
}
@Override
public void skip(MemoryLayout... layouts) {
Objects.requireNonNull(layouts);
@ -131,10 +141,15 @@ public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
for (MemoryLayout layout : layouts) {
Objects.requireNonNull(layout);
segment = segment.asSlice(switch (TypeClass.classifyLayout(layout)) {
case STRUCT_REGISTER, STRUCT_HFA -> alignUp(layout.byteSize(), VA_SLOT_SIZE_BYTES);
default -> VA_SLOT_SIZE_BYTES;
});
long size = sizeOf(layout);
checkElement(layout, size);
segment = segment.asSlice(size);
}
}
private void checkElement(MemoryLayout layout, long size) {
if (segment.byteSize() < size) {
throw SharedUtils.newVaListNSEE(layout);
}
}
@ -173,36 +188,36 @@ public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
this.session = session;
}
private Builder arg(Class<?> carrier, MemoryLayout layout, Object value) {
private Builder arg(MemoryLayout layout, Object value) {
Objects.requireNonNull(layout);
Objects.requireNonNull(value);
args.add(new SimpleVaArg(carrier, layout, value));
args.add(new SimpleVaArg(layout, value));
return this;
}
@Override
public Builder addVarg(ValueLayout.OfInt layout, int value) {
return arg(int.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfLong layout, long value) {
return arg(long.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfDouble layout, double value) {
return arg(double.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfAddress layout, Addressable value) {
return arg(MemoryAddress.class, layout, value.address());
return arg(layout, value.address());
}
@Override
public Builder addVarg(GroupLayout layout, MemorySegment value) {
return arg(MemorySegment.class, layout, value);
return arg(layout, value);
}
public VaList build() {
@ -212,22 +227,18 @@ public non-sealed class MacOsAArch64VaList implements VaList, Scoped {
SegmentAllocator allocator = SegmentAllocator.newNativeArena(session);
// Each argument may occupy up to four slots
MemorySegment segment = allocator.allocate(VA_SLOT_SIZE_BYTES * args.size() * 4);
List<MemorySegment> attachedSegments = new ArrayList<>();
attachedSegments.add(segment);
long allocationSize = args.stream().reduce(0L, (acc, e) -> acc + sizeOf(e.layout), Long::sum);
MemorySegment segment = allocator.allocate(allocationSize);
MemorySegment cursor = segment;
for (SimpleVaArg arg : args) {
if (arg.carrier == MemorySegment.class) {
if (arg.layout instanceof GroupLayout) {
MemorySegment msArg = ((MemorySegment) arg.value);
TypeClass typeClass = TypeClass.classifyLayout(arg.layout);
switch (typeClass) {
case STRUCT_REFERENCE -> {
MemorySegment copy = allocator.allocate(arg.layout);
copy.copyFrom(msArg); // by-value
attachedSegments.add(copy);
VH_address.set(cursor, copy.address());
cursor = cursor.asSlice(VA_SLOT_SIZE_BYTES);
}

View file

@ -48,8 +48,6 @@ import static jdk.internal.foreign.abi.SharedUtils.THROWING_ALLOCATOR;
public non-sealed class SysVVaList implements VaList, Scoped {
private static final Unsafe U = Unsafe.getUnsafe();
static final Class<?> CARRIER = MemoryAddress.class;
// struct typedef __va_list_tag __va_list_tag {
// unsigned int gp_offset; /* 0 4 */
// unsigned int fp_offset; /* 4 4 */
@ -66,6 +64,8 @@ public non-sealed class SysVVaList implements VaList, Scoped {
SysV.C_POINTER.withName("reg_save_area")
).withName("__va_list_tag");
private static final long STACK_SLOT_SIZE = 8;
private static final MemoryLayout GP_REG = MemoryLayout.paddingLayout(64).withBitAlignment(64);
private static final MemoryLayout FP_REG = MemoryLayout.paddingLayout(128).withBitAlignment(128);
@ -112,16 +112,25 @@ public non-sealed class SysVVaList implements VaList, Scoped {
private static final VaList EMPTY = new SharedUtils.EmptyVaList(emptyListAddress());
private final MemorySegment segment;
private MemorySegment overflowArgArea;
private final MemorySegment regSaveArea;
private final long gpLimit;
private final long fpLimit;
private SysVVaList(MemorySegment segment, MemorySegment regSaveArea) {
private SysVVaList(MemorySegment segment,
MemorySegment overflowArgArea,
MemorySegment regSaveArea, long gpLimit, long fpLimit) {
this.segment = segment;
this.overflowArgArea = overflowArgArea;
this.regSaveArea = regSaveArea;
this.gpLimit = gpLimit;
this.fpLimit = fpLimit;
}
private static SysVVaList readFromSegment(MemorySegment segment) {
MemorySegment regSaveArea = getRegSaveArea(segment);
return new SysVVaList(segment, regSaveArea);
MemorySegment overflowArgArea = getArgOverflowArea(segment);
return new SysVVaList(segment, overflowArgArea, regSaveArea, MAX_GP_OFFSET, MAX_FP_OFFSET);
}
private static MemoryAddress emptyListAddress() {
@ -157,72 +166,78 @@ public non-sealed class SysVVaList implements VaList, Scoped {
VH_fp_offset.set(segment, i);
}
private MemoryAddress stackPtr() {
return (MemoryAddress) VH_overflow_arg_area.get(segment);
}
private void stackPtr(MemoryAddress ptr) {
VH_overflow_arg_area.set(segment, ptr);
}
private MemorySegment regSaveArea() {
return getRegSaveArea(segment);
}
private static MemorySegment getRegSaveArea(MemorySegment segment) {
return MemorySegment.ofAddress(((MemoryAddress)VH_reg_save_area.get(segment)),
LAYOUT_REG_SAVE_AREA.byteSize(), segment.session());
}
private void preAlignStack(MemoryLayout layout) {
if (layout.byteAlignment() > 8) {
stackPtr(Utils.alignUp(stackPtr(), 16));
private static MemorySegment getArgOverflowArea(MemorySegment segment) {
return MemorySegment.ofAddress(((MemoryAddress)VH_overflow_arg_area.get(segment)),
Long.MAX_VALUE, segment.session()); // size unknown
}
private long preAlignOffset(MemoryLayout layout) {
long alignmentOffset = 0;
if (layout.byteAlignment() > STACK_SLOT_SIZE) {
long addr = overflowArgArea.address().toRawLongValue();
alignmentOffset = Utils.alignUp(addr, 16) - addr;
}
return alignmentOffset;
}
private void setOverflowArgArea(MemorySegment newSegment) {
overflowArgArea = newSegment;
VH_overflow_arg_area.set(segment, overflowArgArea.address());
}
private void preAlignStack(MemoryLayout layout) {
setOverflowArgArea(overflowArgArea.asSlice(preAlignOffset(layout)));
}
private void postAlignStack(MemoryLayout layout) {
stackPtr(Utils.alignUp(stackPtr().addOffset(layout.byteSize()), 8));
setOverflowArgArea(overflowArgArea.asSlice(Utils.alignUp(layout.byteSize(), STACK_SLOT_SIZE)));
}
@Override
public int nextVarg(ValueLayout.OfInt layout) {
return (int) read(int.class, layout);
return (int) read(layout);
}
@Override
public long nextVarg(ValueLayout.OfLong layout) {
return (long) read(long.class, layout);
return (long) read(layout);
}
@Override
public double nextVarg(ValueLayout.OfDouble layout) {
return (double) read(double.class, layout);
return (double) read(layout);
}
@Override
public MemoryAddress nextVarg(ValueLayout.OfAddress layout) {
return (MemoryAddress) read(MemoryAddress.class, layout);
return (MemoryAddress) read(layout);
}
@Override
public MemorySegment nextVarg(GroupLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(allocator);
return (MemorySegment) read(MemorySegment.class, layout, allocator);
return (MemorySegment) read(layout, allocator);
}
private Object read(Class<?> carrier, MemoryLayout layout) {
return read(carrier, layout, THROWING_ALLOCATOR);
private Object read(MemoryLayout layout) {
return read(layout, THROWING_ALLOCATOR);
}
private Object read(Class<?> carrier, MemoryLayout layout, SegmentAllocator allocator) {
private Object read(MemoryLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(layout);
TypeClass typeClass = TypeClass.classifyLayout(layout);
if (isRegOverflow(currentGPOffset(), currentFPOffset(), typeClass)
|| typeClass.inMemory()) {
checkStackElement(layout);
preAlignStack(layout);
return switch (typeClass.kind()) {
case STRUCT -> {
MemorySegment slice = MemorySegment.ofAddress(stackPtr(), layout.byteSize(), session());
MemorySegment slice = overflowArgArea.asSlice(0, layout.byteSize());
MemorySegment seg = allocator.allocate(layout);
seg.copyFrom(slice);
postAlignStack(layout);
@ -230,15 +245,14 @@ public non-sealed class SysVVaList implements VaList, Scoped {
}
case POINTER, INTEGER, FLOAT -> {
VarHandle reader = layout.varHandle();
try (MemorySession localSession = MemorySession.openConfined()) {
MemorySegment slice = MemorySegment.ofAddress(stackPtr(), layout.byteSize(), localSession);
Object res = reader.get(slice);
postAlignStack(layout);
yield res;
}
MemorySegment slice = overflowArgArea.asSlice(0, layout.byteSize());
Object res = reader.get(slice);
postAlignStack(layout);
yield res;
}
};
} else {
checkRegSaveAreaElement(layout, typeClass);
return switch (typeClass.kind()) {
case STRUCT -> {
MemorySegment value = allocator.allocate(layout);
@ -274,6 +288,22 @@ public non-sealed class SysVVaList implements VaList, Scoped {
}
}
private void checkRegSaveAreaElement(MemoryLayout layout, TypeClass typeClass) {
long gpSize = typeClass.nIntegerRegs() * GP_SLOT_SIZE;
long fpSize = typeClass.nVectorRegs() * FP_SLOT_SIZE;
if (currentGPOffset() + gpSize > gpLimit
|| currentFPOffset() + fpSize > fpLimit) {
throw SharedUtils.newVaListNSEE(layout);
}
}
private void checkStackElement(MemoryLayout layout) {
long offset = preAlignOffset(layout);
if (offset + layout.byteSize() > overflowArgArea.byteSize()) {
throw SharedUtils.newVaListNSEE(layout);
}
}
@Override
public void skip(MemoryLayout... layouts) {
Objects.requireNonNull(layouts);
@ -282,9 +312,11 @@ public non-sealed class SysVVaList implements VaList, Scoped {
Objects.requireNonNull(layout);
TypeClass typeClass = TypeClass.classifyLayout(layout);
if (isRegOverflow(currentGPOffset(), currentFPOffset(), typeClass)) {
checkStackElement(layout);
preAlignStack(layout);
postAlignStack(layout);
} else {
checkRegSaveAreaElement(layout, typeClass);
currentGPOffset(currentGPOffset() + (((int) typeClass.nIntegerRegs()) * GP_SLOT_SIZE));
currentFPOffset(currentFPOffset() + (((int) typeClass.nVectorRegs()) * FP_SLOT_SIZE));
}
@ -308,7 +340,7 @@ public non-sealed class SysVVaList implements VaList, Scoped {
public VaList copy() {
MemorySegment copy = MemorySegment.allocateNative(LAYOUT, segment.session());
copy.copyFrom(segment);
return new SysVVaList(copy, regSaveArea);
return new SysVVaList(copy, overflowArgArea, regSaveArea, gpLimit, fpLimit);
}
@Override
@ -326,8 +358,8 @@ public non-sealed class SysVVaList implements VaList, Scoped {
return "SysVVaList{"
+ "gp_offset=" + currentGPOffset()
+ ", fp_offset=" + currentFPOffset()
+ ", overflow_arg_area=" + stackPtr()
+ ", reg_save_area=" + regSaveArea()
+ ", overflow_arg_area=" + overflowArgArea
+ ", reg_save_area=" + regSaveArea
+ '}';
}
@ -345,37 +377,37 @@ public non-sealed class SysVVaList implements VaList, Scoped {
@Override
public Builder addVarg(ValueLayout.OfInt layout, int value) {
return arg(int.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfLong layout, long value) {
return arg(long.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfDouble layout, double value) {
return arg(double.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfAddress layout, Addressable value) {
return arg(MemoryAddress.class, layout, value.address());
return arg(layout, value.address());
}
@Override
public Builder addVarg(GroupLayout layout, MemorySegment value) {
return arg(MemorySegment.class, layout, value);
return arg(layout, value);
}
private Builder arg(Class<?> carrier, MemoryLayout layout, Object value) {
private Builder arg(MemoryLayout layout, Object value) {
Objects.requireNonNull(layout);
Objects.requireNonNull(value);
TypeClass typeClass = TypeClass.classifyLayout(layout);
if (isRegOverflow(currentGPOffset, currentFPOffset, typeClass)
|| typeClass.inMemory()) {
// stack it!
stackArgs.add(new SimpleVaArg(carrier, layout, value));
stackArgs.add(new SimpleVaArg(layout, value));
} else {
switch (typeClass.kind()) {
case STRUCT -> {
@ -421,31 +453,33 @@ public non-sealed class SysVVaList implements VaList, Scoped {
SegmentAllocator allocator = SegmentAllocator.newNativeArena(session);
MemorySegment vaListSegment = allocator.allocate(LAYOUT);
MemoryAddress stackArgsPtr = MemoryAddress.NULL;
MemorySegment stackArgsSegment;
if (!stackArgs.isEmpty()) {
long stackArgsSize = stackArgs.stream().reduce(0L, (acc, e) -> acc + e.layout.byteSize(), Long::sum);
MemorySegment stackArgsSegment = allocator.allocate(stackArgsSize, 16);
MemorySegment maOverflowArgArea = stackArgsSegment;
long stackArgsSize = stackArgs.stream().reduce(0L,
(acc, e) -> acc + Utils.alignUp(e.layout.byteSize(), STACK_SLOT_SIZE), Long::sum);
stackArgsSegment = allocator.allocate(stackArgsSize, 16);
MemorySegment writeCursor = stackArgsSegment;
for (SimpleVaArg arg : stackArgs) {
if (arg.layout.byteSize() > 8) {
maOverflowArgArea = Utils.alignUp(maOverflowArgArea, Math.min(16, arg.layout.byteSize()));
writeCursor = Utils.alignUp(writeCursor, Math.min(16, arg.layout.byteSize()));
}
if (arg.value instanceof MemorySegment) {
maOverflowArgArea.copyFrom((MemorySegment) arg.value);
writeCursor.copyFrom((MemorySegment) arg.value);
} else {
VarHandle writer = arg.varHandle();
writer.set(maOverflowArgArea, arg.value);
writer.set(writeCursor, arg.value);
}
maOverflowArgArea = maOverflowArgArea.asSlice(arg.layout.byteSize());
writeCursor = writeCursor.asSlice(Utils.alignUp(arg.layout.byteSize(), STACK_SLOT_SIZE));
}
stackArgsPtr = stackArgsSegment.address();
} else {
stackArgsSegment = MemorySegment.ofAddress(MemoryAddress.NULL, 0, session);
}
VH_fp_offset.set(vaListSegment, (int) FP_OFFSET);
VH_overflow_arg_area.set(vaListSegment, stackArgsPtr);
VH_overflow_arg_area.set(vaListSegment, stackArgsSegment.address());
VH_reg_save_area.set(vaListSegment, reg_save_area.address());
assert reg_save_area.session().ownerThread() == vaListSegment.session().ownerThread();
return new SysVVaList(vaListSegment, reg_save_area);
return new SysVVaList(vaListSegment, stackArgsSegment, reg_save_area, currentGPOffset, currentFPOffset);
}
}
}

View file

@ -57,7 +57,6 @@ import static jdk.internal.foreign.PlatformLayouts.Win64.C_POINTER;
// : *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))
//
public non-sealed class WinVaList implements VaList, Scoped {
public static final Class<?> CARRIER = MemoryAddress.class;
private static final long VA_SLOT_SIZE_BYTES = 8;
private static final VarHandle VH_address = C_POINTER.varHandle();
@ -77,38 +76,39 @@ public non-sealed class WinVaList implements VaList, Scoped {
@Override
public int nextVarg(ValueLayout.OfInt layout) {
return (int) read(int.class, layout);
return (int) read(layout);
}
@Override
public long nextVarg(ValueLayout.OfLong layout) {
return (long) read(long.class, layout);
return (long) read(layout);
}
@Override
public double nextVarg(ValueLayout.OfDouble layout) {
return (double) read(double.class, layout);
return (double) read(layout);
}
@Override
public MemoryAddress nextVarg(ValueLayout.OfAddress layout) {
return (MemoryAddress) read(MemoryAddress.class, layout);
return (MemoryAddress) read(layout);
}
@Override
public MemorySegment nextVarg(GroupLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(allocator);
return (MemorySegment) read(MemorySegment.class, layout, allocator);
return (MemorySegment) read(layout, allocator);
}
private Object read(Class<?> carrier, MemoryLayout layout) {
return read(carrier, layout, SharedUtils.THROWING_ALLOCATOR);
private Object read(MemoryLayout layout) {
return read(layout, SharedUtils.THROWING_ALLOCATOR);
}
private Object read(Class<?> carrier, MemoryLayout layout, SegmentAllocator allocator) {
private Object read(MemoryLayout layout, SegmentAllocator allocator) {
Objects.requireNonNull(layout);
Object res;
if (carrier == MemorySegment.class) {
checkElement(layout);
if (layout instanceof GroupLayout) {
TypeClass typeClass = TypeClass.typeClassFor(layout, false);
res = switch (typeClass) {
case STRUCT_REFERENCE -> {
@ -130,12 +130,21 @@ public non-sealed class WinVaList implements VaList, Scoped {
return res;
}
private void checkElement(MemoryLayout layout) {
if (segment.byteSize() < VA_SLOT_SIZE_BYTES) {
throw SharedUtils.newVaListNSEE(layout);
}
}
@Override
public void skip(MemoryLayout... layouts) {
Objects.requireNonNull(layouts);
sessionImpl().checkValidState();
Stream.of(layouts).forEach(Objects::requireNonNull);
segment = segment.asSlice(layouts.length * VA_SLOT_SIZE_BYTES);
for (MemoryLayout layout : layouts) {
Objects.requireNonNull(layout);
checkElement(layout);
segment = segment.asSlice(VA_SLOT_SIZE_BYTES);
}
}
static WinVaList ofAddress(MemoryAddress addr, MemorySession session) {
@ -173,36 +182,36 @@ public non-sealed class WinVaList implements VaList, Scoped {
this.session = session;
}
private Builder arg(Class<?> carrier, MemoryLayout layout, Object value) {
private Builder arg(MemoryLayout layout, Object value) {
Objects.requireNonNull(layout);
Objects.requireNonNull(value);
args.add(new SimpleVaArg(carrier, layout, value));
args.add(new SimpleVaArg(layout, value));
return this;
}
@Override
public Builder addVarg(ValueLayout.OfInt layout, int value) {
return arg(int.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfLong layout, long value) {
return arg(long.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfDouble layout, double value) {
return arg(double.class, layout, value);
return arg(layout, value);
}
@Override
public Builder addVarg(ValueLayout.OfAddress layout, Addressable value) {
return arg(MemoryAddress.class, layout, value.address());
return arg(layout, value.address());
}
@Override
public Builder addVarg(GroupLayout layout, MemorySegment value) {
return arg(MemorySegment.class, layout, value);
return arg(layout, value);
}
public VaList build() {
@ -211,19 +220,16 @@ public non-sealed class WinVaList implements VaList, Scoped {
}
SegmentAllocator allocator = SegmentAllocator.newNativeArena(session);
MemorySegment segment = allocator.allocate(VA_SLOT_SIZE_BYTES * args.size());
List<MemorySegment> attachedSegments = new ArrayList<>();
attachedSegments.add(segment);
MemorySegment cursor = segment;
for (SimpleVaArg arg : args) {
if (arg.carrier == MemorySegment.class) {
if (arg.layout instanceof GroupLayout) {
MemorySegment msArg = ((MemorySegment) arg.value);
TypeClass typeClass = TypeClass.typeClassFor(arg.layout, false);
switch (typeClass) {
case STRUCT_REFERENCE -> {
MemorySegment copy = allocator.allocate(arg.layout);
copy.copyFrom(msArg); // by-value
attachedSegments.add(copy);
VH_address.set(cursor, copy.address());
}
case STRUCT_REGISTER ->