8254693: Add Panama feature to pass heap segments to native code

Reviewed-by: mcimadamore, lucy, vlivanov
This commit is contained in:
Jorn Vernee 2023-11-14 11:19:30 +00:00
parent c80e691adf
commit 9c98270737
73 changed files with 1990 additions and 960 deletions

View file

@ -669,8 +669,13 @@ public sealed interface Linker permits AbstractLinker {
* might attempt to access the contents of the segment. As such, one of the
* exceptions specified by the {@link MemorySegment#get(ValueLayout.OfByte, long)} or
* the {@link MemorySegment#copy(MemorySegment, long, MemorySegment, long, long)}
* methods may be thrown. The returned method handle will additionally throw
* {@link NullPointerException} if any argument passed to it is {@code null}.
* methods may be thrown. If an argument is a {@link MemorySegment} whose
* corresponding layout is an {@linkplain AddressLayout address layout}, the linker
* will throw an {@link IllegalArgumentException} if the segment is a heap memory
* segment, unless heap memory segments are explicitly allowed through the
* {@link Linker.Option#critical(boolean)} linker option. The returned method handle
* will additionally throw {@link NullPointerException} if any argument passed to it
* is {@code null}.
*
* @param function the function descriptor of the target foreign function
* @param options the linker options associated with this linkage request
@ -914,9 +919,23 @@ public sealed interface Linker permits AbstractLinker {
* <p>
* Using this linker option when linking non-critical functions is likely to have
* adverse effects, such as loss of performance or JVM crashes.
* <p>
* Critical functions can optionally allow access to the Java heap. This allows
* clients to pass heap memory segments as addresses, where normally only off-heap
* memory segments would be allowed. The memory region inside the Java heap is
* exposed through a temporary native address that is valid for the duration of
* the function call. Use of this mechanism is therefore only recommended when a
* function needs to do short-lived access to Java heap memory, and copying the
* relevant data to an off-heap memory segment would be prohibitive in terms of
* performance.
*
* @param allowHeapAccess whether the linked function should allow access to the
* Java heap.
*/
static Option critical() {
return LinkerOptions.Critical.INSTANCE;
static Option critical(boolean allowHeapAccess) {
return allowHeapAccess
? LinkerOptions.Critical.ALLOW_HEAP
: LinkerOptions.Critical.DONT_ALLOW_HEAP;
}
}
}

View file

@ -77,7 +77,8 @@ import static java.lang.invoke.MethodHandleStatics.newInternalError;
|| pType == int.class
|| pType == float.class
|| pType == double.class
|| pType == void.class);
|| pType == void.class
|| pType == Object.class);
}
private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();

View file

@ -24,6 +24,7 @@
*/
package jdk.internal.foreign.abi;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.BindingInterpreter.LoadFunc;
import jdk.internal.foreign.abi.BindingInterpreter.StoreFunc;
@ -201,7 +202,7 @@ public sealed interface Binding {
LoadFunc loadFunc, SegmentAllocator allocator);
private static void checkType(Class<?> type) {
if (!type.isPrimitive() || type == void.class)
if (type != Object.class && (!type.isPrimitive() || type == void.class))
throw new IllegalArgumentException("Illegal type: " + type);
}
@ -267,8 +268,21 @@ public sealed interface Binding {
return new BoxAddress(byteSize, 1, true);
}
static UnboxAddress unboxAddress() {
return UnboxAddress.INSTANCE;
// alias
static SegmentOffset unboxAddress() {
return segmentOffsetNoAllowHeap();
}
static SegmentBase segmentBase() {
return SegmentBase.INSTANCE;
}
static SegmentOffset segmentOffsetAllowHeap() {
return SegmentOffset.INSTANCE_ALLOW_HEAP;
}
static SegmentOffset segmentOffsetNoAllowHeap() {
return SegmentOffset.INSTANCE_NO_ALLOW_HEAP;
}
static Dup dup() {
@ -413,6 +427,21 @@ public sealed interface Binding {
return this;
}
public Binding.Builder segmentBase() {
bindings.add(Binding.segmentBase());
return this;
}
public Binding.Builder segmentOffsetAllowHeap() {
bindings.add(Binding.segmentOffsetAllowHeap());
return this;
}
public Binding.Builder segmentOffsetNoAllowHeap() {
bindings.add(Binding.segmentOffsetNoAllowHeap());
return this;
}
public Binding.Builder dup() {
bindings.add(Binding.dup());
return this;
@ -448,8 +477,10 @@ public sealed interface Binding {
/**
* VM_STORE([storage location], [type])
* Pops a [type] from the operand stack, and moves it to [storage location]
* The [type] must be one of byte, short, char, int, long, float, or double
* Pops a [type] from the operand stack, and moves it to [storage location]
* The [type] must be one of byte, short, char, int, long, float, or double.
* [storage location] may be 'null', indicating that this value should be passed
* to the VM stub, but does not have an explicit target register (e.g. oop offsets)
*/
record VMStore(VMStorage storage, Class<?> type) implements Move {
@ -469,8 +500,8 @@ public sealed interface Binding {
/**
* VM_LOAD([storage location], [type])
* Loads a [type] from [storage location], and pushes it onto the operand stack.
* The [type] must be one of byte, short, char, int, long, float, or double
* Loads a [type] from [storage location], and pushes it onto the operand stack.
* The [type] must be one of byte, short, char, int, long, float, or double
*/
record VMLoad(VMStorage storage, Class<?> type) implements Move {
@ -493,9 +524,9 @@ public sealed interface Binding {
/**
* BUFFER_STORE([offset into memory region], [type], [width])
* Pops a [type] from the operand stack, then pops a MemorySegment from the operand stack.
* Stores [width] bytes of the value contained in the [type] to [offset into memory region].
* The [type] must be one of byte, short, char, int, long, float, or double
* Pops a [type] from the operand stack, then pops a MemorySegment from the operand stack.
* Stores [width] bytes of the value contained in the [type] to [offset into memory region].
* The [type] must be one of byte, short, char, int, long, float, or double
*/
record BufferStore(long offset, Class<?> type, int byteWidth) implements Dereference {
@ -550,9 +581,9 @@ public sealed interface Binding {
/**
* BUFFER_LOAD([offset into memory region], [type], [width])
* Pops a MemorySegment from the operand stack,
* and then loads [width] bytes from it at [offset into memory region], into a [type].
* The [type] must be one of byte, short, char, int, long, float, or double
* Pops a MemorySegment from the operand stack,
* and then loads [width] bytes from it at [offset into memory region], into a [type].
* The [type] must be one of byte, short, char, int, long, float, or double
*/
record BufferLoad(long offset, Class<?> type, int byteWidth) implements Dereference {
@ -606,8 +637,8 @@ public sealed interface Binding {
/**
* COPY([size], [alignment])
* Creates a new MemorySegment with the given [size] and [alignment],
* and copies contents from a MemorySegment popped from the top of the operand stack into this new buffer,
* and pushes the new buffer onto the operand stack
* and copies contents from a MemorySegment popped from the top of the operand stack into this new buffer,
* and pushes the new buffer onto the operand stack
*/
record Copy(long size, long alignment) implements Binding {
private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment, SegmentAllocator allocator) {
@ -653,12 +684,37 @@ public sealed interface Binding {
}
/**
* UNBOX_ADDRESS()
* Pops a 'MemoryAddress' from the operand stack, converts it to a 'long',
* with the given size, and pushes that onto the operand stack
* SEGMENT_BASE()
* Pops a MemorySegment from the stack, retrieves the heap base object from it, or null if there is none
* (See: AbstractMemorySegmentImpl::unsafeGetBase), and pushes the result onto the operand stack.
*/
record UnboxAddress() implements Binding {
static final UnboxAddress INSTANCE = new UnboxAddress();
record SegmentBase() implements Binding {
static final SegmentBase INSTANCE = new SegmentBase();
@Override
public void verify(Deque<Class<?>> stack) {
Class<?> actualType = stack.pop();
SharedUtils.checkType(actualType, MemorySegment.class);
stack.push(Object.class);
}
@Override
public void interpret(Deque<Object> stack, StoreFunc storeFunc,
LoadFunc loadFunc, SegmentAllocator allocator) {
stack.push(((AbstractMemorySegmentImpl)stack.pop()).unsafeGetBase());
}
}
/**
* SEGMENT_OFFSET([allowHeap])
* Pops a MemorySegment from the stack, retrieves the offset from it,
* (See: AbstractMemorySegmentImpl::unsafeGetOffset), and pushes the result onto the operand stack.
* Note that for heap segments, the offset is a virtual address into the heap base object.
* If [allowHeap] is 'false' an exception will be thrown for heap segments (See SharedUtils::checkNative).
*/
record SegmentOffset(boolean allowHeap) implements Binding {
static final SegmentOffset INSTANCE_NO_ALLOW_HEAP = new SegmentOffset(false);
static final SegmentOffset INSTANCE_ALLOW_HEAP = new SegmentOffset(true);
@Override
public void verify(Deque<Class<?>> stack) {
@ -670,14 +726,18 @@ public sealed interface Binding {
@Override
public void interpret(Deque<Object> stack, StoreFunc storeFunc,
LoadFunc loadFunc, SegmentAllocator allocator) {
stack.push(SharedUtils.unboxSegment((MemorySegment)stack.pop()));
MemorySegment operand = (MemorySegment) stack.pop();
if (!allowHeap) {
SharedUtils.checkNative(operand);
}
stack.push(((AbstractMemorySegmentImpl)operand).unsafeGetOffset());
}
}
/**
* BOX_ADDRESS()
* Pops a 'long' from the operand stack, converts it to a 'MemorySegment', with the given size and memory scope
* (either the context scope, or the global scope), and pushes that onto the operand stack.
* Pops a 'long' from the operand stack, converts it to a 'MemorySegment', with the given size and memory scope
* (either the context scope, or the global scope), and pushes that onto the operand stack.
*/
record BoxAddress(long size, long align, boolean needsScope) implements Binding {

View file

@ -27,12 +27,13 @@ package jdk.internal.foreign.abi;
import java.lang.foreign.SegmentAllocator;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
public class BindingInterpreter {
static void unbox(Object arg, List<Binding> bindings, StoreFunc storeFunc, SegmentAllocator allocator) {
Deque<Object> stack = new ArrayDeque<>();
Deque<Object> stack = new LinkedList<>(); // Use LinkedList as a null-friendly Deque for null segment bases
stack.push(arg);
for (Binding b : bindings) {

View file

@ -39,9 +39,10 @@ import jdk.internal.foreign.abi.Binding.BufferStore;
import jdk.internal.foreign.abi.Binding.Cast;
import jdk.internal.foreign.abi.Binding.Copy;
import jdk.internal.foreign.abi.Binding.Dup;
import jdk.internal.foreign.abi.Binding.SegmentBase;
import jdk.internal.foreign.abi.Binding.SegmentOffset;
import jdk.internal.foreign.abi.Binding.ShiftLeft;
import jdk.internal.foreign.abi.Binding.ShiftRight;
import jdk.internal.foreign.abi.Binding.UnboxAddress;
import jdk.internal.foreign.abi.Binding.VMLoad;
import jdk.internal.foreign.abi.Binding.VMStore;
import sun.security.action.GetBooleanAction;
@ -103,7 +104,9 @@ public class BindingSpecializer {
private static final MethodTypeDesc MTD_SCOPE = MethodTypeDesc.of(CD_MemorySegment_Scope);
private static final MethodTypeDesc MTD_SESSION_IMPL = MethodTypeDesc.of(CD_MemorySessionImpl);
private static final MethodTypeDesc MTD_CLOSE = MTD_void;
private static final MethodTypeDesc MTD_UNBOX_SEGMENT = MethodTypeDesc.of(CD_long, CD_MemorySegment);
private static final MethodTypeDesc MTD_CHECK_NATIVE = MethodTypeDesc.of(CD_void, CD_MemorySegment);
private static final MethodTypeDesc MTD_UNSAFE_GET_BASE = MethodTypeDesc.of(CD_Object);
private static final MethodTypeDesc MTD_UNSAFE_GET_OFFSET = MethodTypeDesc.of(CD_long);
private static final MethodTypeDesc MTD_COPY = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_long, CD_MemorySegment, CD_long, CD_long);
private static final MethodTypeDesc MTD_LONG_TO_ADDRESS_NO_SCOPE = MethodTypeDesc.of(CD_MemorySegment, CD_long, CD_long, CD_long);
private static final MethodTypeDesc MTD_LONG_TO_ADDRESS_SCOPE = MethodTypeDesc.of(CD_MemorySegment, CD_long, CD_long, CD_long, CD_MemorySessionImpl);
@ -456,18 +459,19 @@ public class BindingSpecializer {
private void doBindings(List<Binding> bindings) {
for (Binding binding : bindings) {
switch (binding) {
case VMStore vmStore -> emitVMStore(vmStore);
case VMLoad vmLoad -> emitVMLoad(vmLoad);
case BufferStore bufferStore -> emitBufferStore(bufferStore);
case BufferLoad bufferLoad -> emitBufferLoad(bufferLoad);
case Copy copy -> emitCopyBuffer(copy);
case Allocate allocate -> emitAllocBuffer(allocate);
case BoxAddress boxAddress -> emitBoxAddress(boxAddress);
case UnboxAddress unused -> emitUnboxAddress();
case Dup unused -> emitDupBinding();
case ShiftLeft shiftLeft -> emitShiftLeft(shiftLeft);
case ShiftRight shiftRight -> emitShiftRight(shiftRight);
case Cast cast -> emitCast(cast);
case VMStore vmStore -> emitVMStore(vmStore);
case VMLoad vmLoad -> emitVMLoad(vmLoad);
case BufferStore bufferStore -> emitBufferStore(bufferStore);
case BufferLoad bufferLoad -> emitBufferLoad(bufferLoad);
case Copy copy -> emitCopyBuffer(copy);
case Allocate allocate -> emitAllocBuffer(allocate);
case BoxAddress boxAddress -> emitBoxAddress(boxAddress);
case SegmentBase unused -> emitSegmentBase();
case SegmentOffset segmentOffset -> emitSegmentOffset(segmentOffset);
case Dup unused -> emitDupBinding();
case ShiftLeft shiftLeft -> emitShiftLeft(shiftLeft);
case ShiftRight shiftRight -> emitShiftRight(shiftRight);
case Cast cast -> emitCast(cast);
}
}
}
@ -775,9 +779,23 @@ public class BindingSpecializer {
pushType(toType);
}
private void emitUnboxAddress() {
private void emitSegmentBase() {
popType(MemorySegment.class);
cb.invokestatic(CD_SharedUtils, "unboxSegment", MTD_UNBOX_SEGMENT);
cb.checkcast(CD_AbstractMemorySegmentImpl);
cb.invokevirtual(CD_AbstractMemorySegmentImpl, "unsafeGetBase", MTD_UNSAFE_GET_BASE);
pushType(Object.class);
}
private void emitSegmentOffset(SegmentOffset segmentOffset) {
popType(MemorySegment.class);
if (!segmentOffset.allowHeap()) {
cb.dup();
cb.invokestatic(CD_SharedUtils, "checkNative", MTD_CHECK_NATIVE);
}
cb.checkcast(CD_AbstractMemorySegmentImpl);
cb.invokevirtual(CD_AbstractMemorySegmentImpl, "unsafeGetOffset", MTD_UNSAFE_GET_OFFSET);
pushType(long.class);
}

View file

@ -25,16 +25,17 @@
package jdk.internal.foreign.abi;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.Binding.Allocate;
import jdk.internal.foreign.abi.Binding.*;
import jdk.internal.foreign.abi.Binding.BoxAddress;
import jdk.internal.foreign.abi.Binding.BufferLoad;
import jdk.internal.foreign.abi.Binding.BufferStore;
import jdk.internal.foreign.abi.Binding.Cast;
import jdk.internal.foreign.abi.Binding.Copy;
import jdk.internal.foreign.abi.Binding.Dup;
import jdk.internal.foreign.abi.Binding.SegmentBase;
import jdk.internal.foreign.abi.Binding.SegmentOffset;
import jdk.internal.foreign.abi.Binding.ShiftLeft;
import jdk.internal.foreign.abi.Binding.ShiftRight;
import jdk.internal.foreign.abi.Binding.UnboxAddress;
import jdk.internal.foreign.abi.Binding.VMLoad;
import jdk.internal.foreign.abi.Binding.VMStore;
import sun.security.action.GetPropertyAction;
@ -217,19 +218,19 @@ public class CallingSequenceBuilder {
static boolean isUnbox(Binding binding) {
return switch (binding) {
case VMStore unused -> true;
case BufferLoad unused -> true;
case Copy unused -> true;
case UnboxAddress unused -> true;
case Dup unused -> true;
case ShiftLeft unused -> true;
case ShiftRight unused -> true;
case Cast unused -> true;
case VMLoad unused -> false;
case BufferStore unused -> false;
case Allocate unused -> false;
case BoxAddress unused -> false;
case VMStore unused -> true;
case BufferLoad unused -> true;
case Copy unused -> true;
case Dup unused -> true;
case SegmentBase unused -> true;
case SegmentOffset unused -> true;
case ShiftLeft unused -> true;
case ShiftRight unused -> true;
case Cast unused -> true;
case VMLoad unused -> false;
case BufferStore unused -> false;
case Allocate unused -> false;
case BoxAddress unused -> false;
};
}
@ -252,19 +253,20 @@ public class CallingSequenceBuilder {
static boolean isBox(Binding binding) {
return switch (binding) {
case VMLoad unused -> true;
case BufferStore unused -> true;
case Copy unused -> true;
case Allocate unused -> true;
case BoxAddress unused -> true;
case Dup unused -> true;
case ShiftLeft unused -> true;
case ShiftRight unused -> true;
case Cast unused -> true;
case VMLoad unused -> true;
case BufferStore unused -> true;
case Copy unused -> true;
case Allocate unused -> true;
case BoxAddress unused -> true;
case Dup unused -> true;
case ShiftLeft unused -> true;
case ShiftRight unused -> true;
case Cast unused -> true;
case VMStore unused -> false;
case BufferLoad unused -> false;
case UnboxAddress unused -> false;
case VMStore unused -> false;
case BufferLoad unused -> false;
case SegmentBase unused -> false;
case SegmentOffset unused -> false;
};
}

View file

@ -98,9 +98,7 @@ public class DowncallLinker {
if (USE_SPEC) {
handle = BindingSpecializer.specializeDowncall(handle, callingSequence, abi);
} else {
Map<VMStorage, Integer> argIndexMap = SharedUtils.indexMap(argMoves);
InvocationData invData = new InvocationData(handle, callingSequence, argIndexMap);
InvocationData invData = new InvocationData(handle, callingSequence);
handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 2, invData);
MethodType interpType = callingSequence.callerMethodType();
if (callingSequence.needsReturnBuffer()) {
@ -151,7 +149,7 @@ public class DowncallLinker {
return Arrays.stream(moves).map(Binding.Move::storage).toArray(VMStorage[]::new);
}
private record InvocationData(MethodHandle leaf, CallingSequence callingSequence, Map<VMStorage, Integer> argIndexMap) {}
private record InvocationData(MethodHandle leaf, CallingSequence callingSequence) {}
Object invokeInterpBindings(SegmentAllocator allocator, Object[] args, InvocationData invData) throws Throwable {
Arena unboxArena = callingSequence.allocationSize() != 0
@ -172,6 +170,13 @@ public class DowncallLinker {
}
Object[] leafArgs = new Object[invData.leaf.type().parameterCount()];
BindingInterpreter.StoreFunc storeFunc = new BindingInterpreter.StoreFunc() {
int argOffset = 0;
@Override
public void store(VMStorage storage, Object o) {
leafArgs[argOffset++] = o;
}
};
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (callingSequence.functionDesc().argumentLayouts().get(i) instanceof AddressLayout) {
@ -183,8 +188,7 @@ public class DowncallLinker {
acquiredScopes.add(sessionImpl);
}
}
BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i),
(storage, value) -> leafArgs[invData.argIndexMap.get(storage)] = value, unboxArena);
BindingInterpreter.unbox(arg, callingSequence.argumentBindings(i), storeFunc, unboxArena);
}
// call leaf

View file

@ -106,6 +106,11 @@ public class LinkerOptions {
return c != null;
}
public boolean allowsHeapAccess() {
Critical c = getOption(Critical.class);
return c != null && c.allowHeapAccess();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -145,8 +150,9 @@ public class LinkerOptions {
}
}
public record Critical() implements LinkerOptionImpl {
public static Critical INSTANCE = new Critical();
public record Critical(boolean allowHeapAccess) implements LinkerOptionImpl {
public static Critical ALLOW_HEAP = new Critical(true);
public static Critical DONT_ALLOW_HEAP = new Critical(false);
@Override
public void validateForDowncall(FunctionDescriptor descriptor) {

View file

@ -314,10 +314,14 @@ public final class SharedUtils {
}
}
public static long unboxSegment(MemorySegment segment) {
public static void checkNative(MemorySegment segment) {
if (!segment.isNative()) {
throw new IllegalArgumentException("Heap segment not allowed: " + segment);
}
}
public static long unboxSegment(MemorySegment segment) {
checkNative(segment);
return segment.address();
}

View file

@ -151,8 +151,8 @@ public abstract class CallArranger {
boolean forVariadicFunction = options.isVariadicFunction();
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, forVariadicFunction);
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, forVariadicFunction) : new BoxBindingCalculator(false);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, forVariadicFunction, options.allowsHeapAccess());
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, forVariadicFunction, false) : new BoxBindingCalculator(false);
boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout());
if (returnInMemory) {
@ -388,11 +388,13 @@ public abstract class CallArranger {
class UnboxBindingCalculator extends BindingCalculator {
protected final boolean forArguments;
protected final boolean forVariadicFunction;
private final boolean useAddressPairs;
UnboxBindingCalculator(boolean forArguments, boolean forVariadicFunction) {
UnboxBindingCalculator(boolean forArguments, boolean forVariadicFunction, boolean useAddressPairs) {
super(forArguments, forVariadicFunction);
this.forArguments = forArguments;
this.forVariadicFunction = forVariadicFunction;
this.useAddressPairs = useAddressPairs;
}
@Override
@ -432,9 +434,17 @@ public abstract class CallArranger {
bindings.vmStore(storage, long.class);
}
case POINTER -> {
bindings.unboxAddress();
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, (ValueLayout) layout);
bindings.vmStore(storage, long.class);
if (useAddressPairs) {
bindings.dup()
.segmentBase()
.vmStore(storage, Object.class)
.segmentOffsetAllowHeap()
.vmStore(null, long.class);
} else {
bindings.unboxAddress();
bindings.vmStore(storage, long.class);
}
}
case INTEGER -> {
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, (ValueLayout) layout);

View file

@ -24,13 +24,6 @@
*/
package jdk.internal.foreign.abi.fallback;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.abi.AbstractLinker;
import jdk.internal.foreign.abi.CapturableState;
import jdk.internal.foreign.abi.LinkerOptions;
import jdk.internal.foreign.abi.SharedUtils;
import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
@ -39,16 +32,6 @@ import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
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.Map;
import java.util.function.Consumer;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
@ -58,7 +41,21 @@ import static java.lang.foreign.ValueLayout.JAVA_FLOAT;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
import static java.lang.foreign.ValueLayout.JAVA_SHORT;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import static java.lang.invoke.MethodHandles.foldArguments;
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.Map;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.abi.AbstractLinker;
import jdk.internal.foreign.abi.CapturableState;
import jdk.internal.foreign.abi.LinkerOptions;
import jdk.internal.foreign.abi.SharedUtils;
public final class FallbackLinker extends AbstractLinker {
@ -95,7 +92,7 @@ public final class FallbackLinker extends AbstractLinker {
.mapToInt(CapturableState::mask)
.reduce(0, (a, b) -> a | b);
DowncallData invData = new DowncallData(cif, function.returnLayout().orElse(null),
function.argumentLayouts(), capturedStateMask);
function.argumentLayouts(), capturedStateMask, options.allowsHeapAccess());
MethodHandle target = MethodHandles.insertArguments(MH_DO_DOWNCALL, 2, invData);
@ -155,12 +152,13 @@ public final class FallbackLinker extends AbstractLinker {
}
private record DowncallData(MemorySegment cif, MemoryLayout returnLayout, List<MemoryLayout> argLayouts,
int capturedStateMask) {}
int capturedStateMask, boolean allowsHeapAccess) {}
private static Object doDowncall(SegmentAllocator returnAllocator, Object[] args, DowncallData invData) {
List<MemorySessionImpl> acquiredSessions = new ArrayList<>();
try (Arena arena = Arena.ofConfined()) {
int argStart = 0;
Object[] heapBases = invData.allowsHeapAccess() ? new Object[args.length] : null;
MemorySegment target = (MemorySegment) args[argStart++];
MemorySessionImpl targetImpl = ((AbstractMemorySegmentImpl) target).sessionImpl();
@ -180,12 +178,22 @@ public final class FallbackLinker extends AbstractLinker {
for (int i = 0; i < argLayouts.size(); i++) {
Object arg = args[argStart + i];
MemoryLayout layout = argLayouts.get(i);
MemorySegment argSeg = arena.allocate(layout);
writeValue(arg, layout, argSeg, addr -> {
MemorySessionImpl sessionImpl = ((AbstractMemorySegmentImpl) addr).sessionImpl();
if (layout instanceof AddressLayout) {
AbstractMemorySegmentImpl ms = (AbstractMemorySegmentImpl) arg;
MemorySessionImpl sessionImpl = ms.sessionImpl();
sessionImpl.acquire0();
acquiredSessions.add(sessionImpl);
});
if (invData.allowsHeapAccess() && !ms.isNative()) {
heapBases[i] = ms.unsafeGetBase();
// write the offset to the arg segment, add array ptr to it in native code
layout = JAVA_LONG;
arg = ms.address();
}
}
MemorySegment argSeg = arena.allocate(layout);
writeValue(arg, layout, argSeg);
argPtrs.setAtIndex(ADDRESS, i, argSeg);
}
@ -194,7 +202,8 @@ public final class FallbackLinker extends AbstractLinker {
retSeg = (invData.returnLayout() instanceof GroupLayout ? returnAllocator : arena).allocate(invData.returnLayout);
}
LibFallback.doDowncall(invData.cif, target, retSeg, argPtrs, capturedState, invData.capturedStateMask());
LibFallback.doDowncall(invData.cif, target, retSeg, argPtrs, capturedState, invData.capturedStateMask(),
heapBases, args.length);
Reference.reachabilityFence(invData.cif());
@ -235,11 +244,6 @@ public final class FallbackLinker extends AbstractLinker {
// where
private static void writeValue(Object arg, MemoryLayout layout, MemorySegment argSeg) {
writeValue(arg, layout, argSeg, addr -> {});
}
private static void writeValue(Object arg, MemoryLayout layout, MemorySegment argSeg,
Consumer<MemorySegment> acquireCallback) {
switch (layout) {
case ValueLayout.OfBoolean bl -> argSeg.set(bl, 0, (Boolean) arg);
case ValueLayout.OfByte bl -> argSeg.set(bl, 0, (Byte) arg);
@ -249,11 +253,7 @@ public final class FallbackLinker extends AbstractLinker {
case ValueLayout.OfLong ll -> argSeg.set(ll, 0, (Long) arg);
case ValueLayout.OfFloat fl -> argSeg.set(fl, 0, (Float) arg);
case ValueLayout.OfDouble dl -> argSeg.set(dl, 0, (Double) arg);
case AddressLayout al -> {
MemorySegment addrArg = (MemorySegment) arg;
acquireCallback.accept(addrArg);
argSeg.set(al, 0, addrArg);
}
case AddressLayout al -> argSeg.set(al, 0, (MemorySegment) arg);
case GroupLayout _ ->
MemorySegment.copy((MemorySegment) arg, 0, argSeg, 0, argSeg.byteSize()); // by-value struct
case null, default -> {

View file

@ -93,10 +93,12 @@ final class LibFallback {
* @see jdk.internal.foreign.abi.CapturableState
*/
static void doDowncall(MemorySegment cif, MemorySegment target, MemorySegment retPtr, MemorySegment argPtrs,
MemorySegment capturedState, int capturedStateMask) {
MemorySegment capturedState, int capturedStateMask,
Object[] heapBases, int numArgs) {
doDowncall(cif.address(), target.address(),
retPtr == null ? 0 : retPtr.address(), argPtrs.address(),
capturedState == null ? 0 : capturedState.address(), capturedStateMask);
retPtr == null ? 0 : retPtr.address(), argPtrs.address(),
capturedState == null ? 0 : capturedState.address(), capturedStateMask,
heapBases, numArgs);
}
/**
@ -210,7 +212,9 @@ final class LibFallback {
private static native int createClosure(long cif, Object userData, long[] ptrs);
private static native void freeClosure(long closureAddress, long globalTarget);
private static native void doDowncall(long cif, long fn, long rvalue, long avalues, long capturedState, int capturedStateMask);
private static native void doDowncall(long cif, long fn, long rvalue, long avalues,
long capturedState, int capturedStateMask,
Object[] heapBases, int numArgs);
private static native int ffi_prep_cif(long cif, int abi, int nargs, long rtype, long atypes);
private static native int ffi_prep_cif_var(long cif, int abi, int nfixedargs, int ntotalargs, long rtype, long atypes);

View file

@ -110,8 +110,8 @@ public abstract class CallArranger {
public Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) {
CallingSequenceBuilder csb = new CallingSequenceBuilder(C, forUpcall, options);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true);
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false) : new BoxBindingCalculator(false);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess());
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false);
boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout());
if (returnInMemory) {
@ -343,8 +343,11 @@ public abstract class CallArranger {
// Compute recipe for transfering arguments / return values to C from Java.
class UnboxBindingCalculator extends BindingCalculator {
UnboxBindingCalculator(boolean forArguments) {
private final boolean useAddressPairs;
UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) {
super(forArguments);
this.useAddressPairs = useAddressPairs;
}
@Override
@ -411,8 +414,16 @@ public abstract class CallArranger {
}
case POINTER -> {
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER, false);
bindings.unboxAddress()
.vmStore(storage, long.class);
if (useAddressPairs) {
bindings.dup()
.segmentBase()
.vmStore(storage, Object.class)
.segmentOffsetAllowHeap()
.vmStore(null, long.class);
} else {
bindings.unboxAddress();
bindings.vmStore(storage, long.class);
}
}
case INTEGER -> {
// ABI requires all int types to get extended to 64 bit.

View file

@ -85,8 +85,8 @@ public class LinuxRISCV64CallArranger {
public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) {
CallingSequenceBuilder csb = new CallingSequenceBuilder(CLinux, forUpcall, options);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true);
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false) : new BoxBindingCalculator(false);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess());
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false);
boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout());
if (returnInMemory) {
@ -252,11 +252,13 @@ public class LinuxRISCV64CallArranger {
}
static final class UnboxBindingCalculator extends BindingCalculator {
final boolean forArguments;
protected final boolean forArguments;
private final boolean useAddressPairs;
UnboxBindingCalculator(boolean forArguments) {
UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) {
super(forArguments);
this.forArguments = forArguments;
this.useAddressPairs = useAddressPairs;
}
@Override
@ -280,9 +282,17 @@ public class LinuxRISCV64CallArranger {
bindings.vmStore(storage, carrier);
}
case POINTER -> {
bindings.unboxAddress();
VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER);
bindings.vmStore(storage, long.class);
if (useAddressPairs) {
bindings.dup()
.segmentBase()
.vmStore(storage, Object.class)
.segmentOffsetAllowHeap()
.vmStore(null, long.class);
} else {
bindings.unboxAddress();
bindings.vmStore(storage, long.class);
}
}
case STRUCT_REGISTER_X -> {
assert carrier == MemorySegment.class;

View file

@ -82,8 +82,8 @@ public class LinuxS390CallArranger {
public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) {
CallingSequenceBuilder csb = new CallingSequenceBuilder(CLinux, forUpcall, options);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true);
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false) : new BoxBindingCalculator(false);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess());
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false);
boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout());
if (returnInMemory) {
@ -199,8 +199,11 @@ public class LinuxS390CallArranger {
// Compute recipe for transferring arguments / return values to C from Java.
static class UnboxBindingCalculator extends BindingCalculator {
UnboxBindingCalculator(boolean forArguments) {
private final boolean useAddressPairs;
UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) {
super(forArguments);
this.useAddressPairs = useAddressPairs;
}
@Override
@ -231,8 +234,16 @@ public class LinuxS390CallArranger {
}
case POINTER -> {
VMStorage storage = storageCalculator.getStorage(StorageType.INTEGER, false);
bindings.unboxAddress()
.vmStore(storage, long.class);
if (useAddressPairs) {
bindings.dup()
.segmentBase()
.vmStore(storage, Object.class)
.segmentOffsetAllowHeap()
.vmStore(null, long.class);
} else {
bindings.unboxAddress();
bindings.vmStore(storage, long.class);
}
}
case INTEGER -> {
// ABI requires all int types to get extended to 64 bit.

View file

@ -97,8 +97,8 @@ public class CallArranger {
public static Bindings getBindings(MethodType mt, FunctionDescriptor cDesc, boolean forUpcall, LinkerOptions options) {
CallingSequenceBuilder csb = new CallingSequenceBuilder(CSysV, forUpcall, options);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true);
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false) : new BoxBindingCalculator(false);
BindingCalculator argCalc = forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess());
BindingCalculator retCalc = forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false);
boolean returnInMemory = isInMemoryReturn(cDesc.returnLayout());
if (returnInMemory) {
@ -246,9 +246,11 @@ public class CallArranger {
}
static class UnboxBindingCalculator extends BindingCalculator {
private final boolean useAddressPairs;
UnboxBindingCalculator(boolean forArguments) {
UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) {
super(forArguments);
this.useAddressPairs = useAddressPairs;
}
@Override
@ -275,10 +277,18 @@ public class CallArranger {
}
}
case POINTER -> {
bindings.unboxAddress();
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER);
bindings.vmStore(storage, long.class);
}
if (useAddressPairs) {
bindings.dup()
.segmentBase()
.vmStore(storage, Object.class)
.segmentOffsetAllowHeap()
.vmStore(null, long.class);
} else {
bindings.unboxAddress();
bindings.vmStore(storage, long.class);
}
}
case INTEGER -> {
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER);
bindings.vmStore(storage, carrier);

View file

@ -87,9 +87,9 @@ public class CallArranger {
class CallingSequenceBuilderHelper {
final CallingSequenceBuilder csb = new CallingSequenceBuilder(CWindows, forUpcall, options);
final BindingCalculator argCalc =
forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true);
forUpcall ? new BoxBindingCalculator(true) : new UnboxBindingCalculator(true, options.allowsHeapAccess());
final BindingCalculator retCalc =
forUpcall ? new UnboxBindingCalculator(false) : new BoxBindingCalculator(false);
forUpcall ? new UnboxBindingCalculator(false, false) : new BoxBindingCalculator(false);
void addArgumentBindings(Class<?> carrier, MemoryLayout layout, boolean isVararg) {
csb.addArgumentBindings(carrier, layout, argCalc.getBindings(carrier, layout, isVararg));
@ -184,9 +184,12 @@ public class CallArranger {
static class UnboxBindingCalculator implements BindingCalculator {
private final StorageCalculator storageCalculator;
private final boolean useAddressPairs;
UnboxBindingCalculator(boolean forArguments) {
UnboxBindingCalculator(boolean forArguments, boolean useAddressPairs) {
this.storageCalculator = new StorageCalculator(forArguments);
assert !useAddressPairs || forArguments;
this.useAddressPairs = useAddressPairs;
}
@Override
@ -209,9 +212,17 @@ public class CallArranger {
bindings.vmStore(storage, long.class);
}
case POINTER -> {
bindings.unboxAddress();
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER);
bindings.vmStore(storage, long.class);
if (useAddressPairs) {
bindings.dup()
.segmentBase()
.vmStore(storage, Object.class)
.segmentOffsetAllowHeap()
.vmStore(null, long.class);
} else {
bindings.unboxAddress();
bindings.vmStore(storage, long.class);
}
}
case INTEGER -> {
VMStorage storage = storageCalculator.nextStorage(StorageType.INTEGER);