8286669: Replace MethodHandle specialization with ASM in mainline

Co-authored-by: Jorn Vernee <jvernee@openjdk.org>
Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org>
Reviewed-by: mcimadamore
This commit is contained in:
Jorn Vernee 2022-05-18 16:03:18 +00:00
parent d8b0b32f9f
commit ee45a0ac63
24 changed files with 1490 additions and 793 deletions

View File

@ -80,6 +80,7 @@ struct CodeBlobType {
class CodeBlobLayout;
class OptimizedEntryBlob; // for as_optimized_entry_blob()
class RuntimeStub; // for as_runtime_stub()
class JavaFrameAnchor; // for OptimizedEntryBlob::jfa_for_frame
class CodeBlob {
@ -164,12 +165,13 @@ public:
CompilerType compiler_type() const { return _type; }
// Casting
nmethod* as_nmethod_or_null() { return is_nmethod() ? (nmethod*) this : NULL; }
nmethod* as_nmethod() { assert(is_nmethod(), "must be nmethod"); return (nmethod*) this; }
CompiledMethod* as_compiled_method_or_null() { return is_compiled() ? (CompiledMethod*) this : NULL; }
CompiledMethod* as_compiled_method() { assert(is_compiled(), "must be compiled"); return (CompiledMethod*) this; }
CodeBlob* as_codeblob_or_null() const { return (CodeBlob*) this; }
OptimizedEntryBlob* as_optimized_entry_blob() const { assert(is_optimized_entry_blob(), "must be entry blob"); return (OptimizedEntryBlob*) this; }
nmethod* as_nmethod_or_null() { return is_nmethod() ? (nmethod*) this : NULL; }
nmethod* as_nmethod() { assert(is_nmethod(), "must be nmethod"); return (nmethod*) this; }
CompiledMethod* as_compiled_method_or_null() { return is_compiled() ? (CompiledMethod*) this : NULL; }
CompiledMethod* as_compiled_method() { assert(is_compiled(), "must be compiled"); return (CompiledMethod*) this; }
CodeBlob* as_codeblob_or_null() const { return (CodeBlob*) this; }
OptimizedEntryBlob* as_optimized_entry_blob() const { assert(is_optimized_entry_blob(), "must be entry blob"); return (OptimizedEntryBlob*) this; }
RuntimeStub* as_runtime_stub() const { assert(is_runtime_stub(), "must be runtime blob"); return (RuntimeStub*) this; }
// Boundaries
address header_begin() const { return (address) this; }
@ -521,6 +523,8 @@ class RuntimeStub: public RuntimeBlob {
bool caller_must_gc_arguments
);
static void free(RuntimeStub* stub) { RuntimeBlob::free(stub); }
// Typing
bool is_runtime_stub() const { return true; }

View File

@ -77,6 +77,16 @@ JNI_ENTRY(jlong, NEP_makeInvoker(JNIEnv* env, jclass _unused, jobject method_typ
basic_type, pslots, ret_bt, abi, input_regs, output_regs, needs_return_buffer)->code_begin();
JNI_END
JNI_ENTRY(jboolean, NEP_freeInvoker(JNIEnv* env, jclass _unused, jlong invoker))
// safe to call without code cache lock, because stub is always alive
CodeBlob* cb = CodeCache::find_blob((char*) invoker);
if (cb == nullptr) {
return false;
}
RuntimeStub::free(cb->as_runtime_stub());
return true;
JNI_END
#define CC (char*) /*cast a literal from (const char*)*/
#define FN_PTR(f) CAST_FROM_FN_PTR(void*, &f)
#define METHOD_TYPE "Ljava/lang/invoke/MethodType;"
@ -85,6 +95,7 @@ JNI_END
static JNINativeMethod NEP_methods[] = {
{CC "makeInvoker", CC "(" METHOD_TYPE ABI_DESC VM_STORAGE_ARR VM_STORAGE_ARR "Z)J", FN_PTR(NEP_makeInvoker)},
{CC "freeInvoker0", CC "(J)Z", FN_PTR(NEP_freeInvoker)},
};
#undef METHOD_TYPE

View File

@ -25,18 +25,15 @@
*/
package java.lang.foreign;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import jdk.internal.foreign.abi.AbstractLinker;
import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
import jdk.internal.javac.PreviewFeature;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
/**
* A linker provides access to foreign functions from Java code, and access to Java code from foreign functions.
* <p>
@ -148,7 +145,7 @@ import jdk.internal.reflect.Reflection;
* @since 19
*/
@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN)
public sealed interface Linker permits Windowsx64Linker, SysVx64Linker, LinuxAArch64Linker, MacOsAArch64Linker {
public sealed interface Linker permits AbstractLinker {
/**
* Returns a linker for the ABI associated with the underlying native platform. The underlying native platform

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.foreign.abi;
import jdk.internal.foreign.SystemLookup;
import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.Objects;
public abstract sealed class AbstractLinker implements Linker permits LinuxAArch64Linker, MacOsAArch64Linker,
SysVx64Linker, Windowsx64Linker {
private final SoftReferenceCache<FunctionDescriptor, MethodHandle> DOWNCALL_CACHE = new SoftReferenceCache<>();
@Override
public MethodHandle downcallHandle(FunctionDescriptor function) {
Objects.requireNonNull(function);
return DOWNCALL_CACHE.get(function, fd -> {
MethodType type = SharedUtils.inferMethodType(fd, false);
MethodHandle handle = arrangeDowncall(type, fd);
handle = SharedUtils.maybeInsertAllocator(handle);
return handle;
});
}
protected abstract MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function);
@Override
public MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, MemorySession scope) {
Objects.requireNonNull(scope);
Objects.requireNonNull(target);
Objects.requireNonNull(function);
SharedUtils.checkExceptions(target);
MethodType type = SharedUtils.inferMethodType(function, true);
if (!type.equals(target.type())) {
throw new IllegalArgumentException("Wrong method handle type: " + target.type());
}
return arrangeUpcall(target, target.type(), function, scope);
}
protected abstract MemorySegment arrangeUpcall(MethodHandle target, MethodType targetType,
FunctionDescriptor function, MemorySession scope);
@Override
public SystemLookup defaultLookup() {
return SystemLookup.getInstance();
}
}

View File

@ -44,9 +44,6 @@ import java.util.Objects;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.filterArguments;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodType.methodType;
/**
@ -203,29 +200,6 @@ import static java.lang.invoke.MethodType.methodType;
* --------------------
*/
public abstract class Binding {
private static final MethodHandle MH_UNBOX_ADDRESS;
private static final MethodHandle MH_BOX_ADDRESS;
private static final MethodHandle MH_COPY_BUFFER;
private static final MethodHandle MH_ALLOCATE_BUFFER;
private static final MethodHandle MH_TO_SEGMENT;
static {
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MH_UNBOX_ADDRESS = lookup.findVirtual(MemoryAddress.class, "toRawLongValue",
methodType(long.class));
MH_BOX_ADDRESS = lookup.findStatic(MemoryAddress.class, "ofLong",
methodType(MemoryAddress.class, long.class));
MH_COPY_BUFFER = lookup.findStatic(Binding.Copy.class, "copyBuffer",
methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, Context.class));
MH_ALLOCATE_BUFFER = lookup.findStatic(Binding.Allocate.class, "allocateBuffer",
methodType(MemorySegment.class, long.class, long.class, Context.class));
MH_TO_SEGMENT = lookup.findStatic(Binding.ToSegment.class, "toSegment",
methodType(MemorySegment.class, MemoryAddress.class, long.class, Context.class));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
/**
* A binding context is used as an helper to carry out evaluation of certain bindings; for instance,
@ -280,7 +254,7 @@ public abstract class Binding {
* Create a binding context from given scope. The resulting context will throw when
* the context's allocator is accessed.
*/
public static Context ofScope() {
public static Context ofSession() {
MemorySession scope = MemorySession.openConfined();
return new Context(null, scope) {
@Override
@ -338,8 +312,6 @@ public abstract class Binding {
public abstract void interpret(Deque<Object> stack, BindingInterpreter.StoreFunc storeFunc,
BindingInterpreter.LoadFunc loadFunc, Context context);
public abstract MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos);
private static void checkType(Class<?> type) {
if (!type.isPrimitive() || type == void.class)
throw new IllegalArgumentException("Illegal type: " + type);
@ -545,11 +517,6 @@ public abstract class Binding {
storeFunc.store(storage(), type(), stack.pop());
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return specializedHandle; // no-op
}
@Override
public String toString() {
return "VMStore{" +
@ -580,11 +547,6 @@ public abstract class Binding {
stack.push(loadFunc.load(storage(), type()));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return specializedHandle; // no-op
}
@Override
public String toString() {
return "VMLoad{" +
@ -638,8 +600,8 @@ public abstract class Binding {
/**
* BUFFER_STORE([offset into memory region], [type])
* Pops a MemorySegment from the operand stack, loads a [type] from
* [offset into memory region] from it, and pushes it onto the operand stack.
* Pops a [type] from the operand stack, then pops a MemorySegment from the operand stack.
* Stores the [type] to [offset into memory region].
* The [type] must be one of byte, short, char, int, long, float, or double
*/
public static class BufferStore extends Dereference {
@ -664,13 +626,6 @@ public abstract class Binding {
SharedUtils.write(writeAddress, type(), value);
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle setter = varHandle().toMethodHandle(VarHandle.AccessMode.SET);
setter = setter.asType(methodType(void.class, MemorySegment.class, type()));
return collectArguments(specializedHandle, insertPos + 1, setter);
}
@Override
public String toString() {
return "BufferStore{" +
@ -707,14 +662,6 @@ public abstract class Binding {
stack.push(SharedUtils.read(readAddress, type()));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle filter = varHandle()
.toMethodHandle(VarHandle.AccessMode.GET)
.asType(methodType(type(), MemorySegment.class));
return filterArguments(specializedHandle, insertPos, filter);
}
@Override
public String toString() {
return "BufferLoad{" +
@ -740,8 +687,7 @@ public abstract class Binding {
this.alignment = alignment;
}
private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment,
Context context) {
private static MemorySegment copyBuffer(MemorySegment operand, long size, long alignment, Context context) {
return context.allocator().allocate(size, alignment)
.copyFrom(operand.asSlice(0, size));
}
@ -778,13 +724,6 @@ public abstract class Binding {
stack.push(copy);
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle filter = insertArguments(MH_COPY_BUFFER, 1, size, alignment);
specializedHandle = collectArguments(specializedHandle, insertPos, filter);
return SharedUtils.mergeArguments(specializedHandle, allocatorPos, insertPos + 1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -847,13 +786,6 @@ public abstract class Binding {
stack.push(allocateBuffer(size, alignment, context));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle allocateBuffer = insertArguments(MH_ALLOCATE_BUFFER, 0, size, alignment);
specializedHandle = collectArguments(specializedHandle, insertPos, allocateBuffer);
return SharedUtils.mergeArguments(specializedHandle, allocatorPos, insertPos);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -910,12 +842,6 @@ public abstract class Binding {
stack.push(((Addressable)stack.pop()).address().toRawLongValue());
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return filterArguments(specializedHandle, insertPos,
MethodHandles.filterReturnValue(toAddress, MH_UNBOX_ADDRESS));
}
@Override
public String toString() {
return "UnboxAddress{}";
@ -946,11 +872,6 @@ public abstract class Binding {
stack.push(MemoryAddress.ofLong((long) stack.pop()));
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return filterArguments(specializedHandle, insertPos, MH_BOX_ADDRESS);
}
@Override
public String toString() {
return "BoxAddress{}";
@ -971,6 +892,10 @@ public abstract class Binding {
this.size = size;
}
public long size() {
return size;
}
private static MemorySegment toSegment(MemoryAddress operand, long size, Context context) {
return MemoryAddressImpl.ofLongUnchecked(operand.toRawLongValue(), size, context.session);
}
@ -990,13 +915,6 @@ public abstract class Binding {
stack.push(segment);
}
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
MethodHandle toSegmentHandle = insertArguments(MH_TO_SEGMENT, 1, size);
specializedHandle = collectArguments(specializedHandle, insertPos, toSegmentHandle);
return SharedUtils.mergeArguments(specializedHandle, allocatorPos, insertPos + 1);
}
@Override
public String toString() {
return "ToSegemnt{" +
@ -1041,29 +959,6 @@ public abstract class Binding {
stack.push(stack.peekLast());
}
/*
* Fixes up Y-shaped data graphs (produced by DEREFERENCE):
*
* 1. DUP()
* 2. BUFFER_LOAD(0, int.class)
* 3. VM_STORE (ignored)
* 4. BUFFER_LOAD(4, int.class)
* 5. VM_STORE (ignored)
*
* (specialized in reverse!)
*
* 5. (int, int) -> void insertPos = 1
* 4. (MemorySegment, int) -> void insertPos = 1
* 3. (MemorySegment, int) -> void insertPos = 0
* 2. (MemorySegment, MemorySegment) -> void insertPos = 0
* 1. (MemorySegment) -> void insertPos = 0
*
*/
@Override
public MethodHandle specialize(MethodHandle specializedHandle, int insertPos, int allocatorPos) {
return SharedUtils.mergeArguments(specializedHandle, insertPos, insertPos + 1);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -0,0 +1,937 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.foreign.abi;
import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.Scoped;
import jdk.internal.misc.VM;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.ConstantDynamic;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.org.objectweb.asm.Opcodes;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import sun.security.action.GetBooleanAction;
import sun.security.action.GetPropertyAction;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.constant.ConstantDescs;
import java.lang.foreign.Addressable;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
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.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.List;
import java.util.function.BiPredicate;
import static java.lang.foreign.ValueLayout.*;
import static java.lang.invoke.MethodType.methodType;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
public class BindingSpecializer {
private static final String DUMP_CLASSES_DIR
= GetPropertyAction.privilegedGetProperty("jdk.internal.foreign.abi.Specializer.DUMP_CLASSES_DIR");
private static final boolean PERFORM_VERIFICATION
= GetBooleanAction.privilegedGetProperty("jdk.internal.foreign.abi.Specializer.PERFORM_VERIFICATION");
// Bunch of helper constants
private static final int CLASSFILE_VERSION = VM.classFileVersion();
private static final String OBJECT_DESC = Object.class.descriptorString();
private static final String OBJECT_INTRN = Type.getInternalName(Object.class);
private static final String VOID_DESC = methodType(void.class).descriptorString();
private static final String BINDING_CONTEXT_DESC = Binding.Context.class.descriptorString();
private static final String OF_BOUNDED_ALLOCATOR_DESC = methodType(Binding.Context.class, long.class).descriptorString();
private static final String OF_SESSION_DESC = methodType(Binding.Context.class).descriptorString();
private static final String ALLOCATOR_DESC = methodType(SegmentAllocator.class).descriptorString();
private static final String SESSION_DESC = methodType(MemorySession.class).descriptorString();
private static final String SESSION_IMPL_DESC = methodType(MemorySessionImpl.class).descriptorString();
private static final String CLOSE_DESC = VOID_DESC;
private static final String ADDRESS_DESC = methodType(MemoryAddress.class).descriptorString();
private static final String COPY_DESC = methodType(void.class, MemorySegment.class, long.class, MemorySegment.class, long.class, long.class).descriptorString();
private static final String TO_RAW_LONG_VALUE_DESC = methodType(long.class).descriptorString();
private static final String OF_LONG_DESC = methodType(MemoryAddress.class, long.class).descriptorString();
private static final String OF_LONG_UNCHECKED_DESC = methodType(MemorySegment.class, long.class, long.class, MemorySession.class).descriptorString();
private static final String ALLOCATE_DESC = methodType(MemorySegment.class, long.class, long.class).descriptorString();
private static final String HANDLE_UNCAUGHT_EXCEPTION_DESC = methodType(void.class, Throwable.class).descriptorString();
private static final String METHOD_HANDLES_INTRN = Type.getInternalName(MethodHandles.class);
private static final String CLASS_DATA_DESC = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class).descriptorString();
private static final String RELEASE0_DESC = VOID_DESC;
private static final String ACQUIRE0_DESC = VOID_DESC;
private static final Handle BSM_CLASS_DATA = new Handle(
H_INVOKESTATIC,
METHOD_HANDLES_INTRN,
"classData",
CLASS_DATA_DESC,
false);
private static final ConstantDynamic CLASS_DATA_CONDY = new ConstantDynamic(
ConstantDescs.DEFAULT_NAME,
OBJECT_DESC,
BSM_CLASS_DATA);
private static final String CLASS_NAME_DOWNCALL = "jdk/internal/foreign/abi/DowncallStub";
private static final String CLASS_NAME_UPCALL = "jdk/internal/foreign/abi/UpcallStub";
private static final String METHOD_NAME = "invoke";
private static final String SUPER_NAME = OBJECT_INTRN;
private static final SoftReferenceCache<FunctionDescriptor, MethodHandle> UPCALL_WRAPPER_CACHE = new SoftReferenceCache<>();
// Instance fields start here
private final MethodVisitor mv;
private final MethodType callerMethodType;
private final CallingSequence callingSequence;
private final ABIDescriptor abi;
private final MethodType leafType;
private int localIdx = 0;
private int[] paramIndex2ParamSlot;
private int[] leafArgSlots;
private int[] scopeSlots;
private int curScopeLocalIdx = -1;
private int returnAllocatorIdx = -1;
private int CONTEXT_IDX = -1;
private int returnBufferIdx = -1;
private int retValIdx = -1;
private Deque<Class<?>> typeStack;
private List<Class<?>> leafArgTypes;
private int paramIndex;
private long retBufOffset; // for needsReturnBuffer
private BindingSpecializer(MethodVisitor mv, MethodType callerMethodType, CallingSequence callingSequence, ABIDescriptor abi, MethodType leafType) {
this.mv = mv;
this.callerMethodType = callerMethodType;
this.callingSequence = callingSequence;
this.abi = abi;
this.leafType = leafType;
}
static MethodHandle specialize(MethodHandle leafHandle, CallingSequence callingSequence, ABIDescriptor abi) {
if (callingSequence.forUpcall()) {
MethodHandle wrapper = UPCALL_WRAPPER_CACHE.get(callingSequence.functionDesc(), fd -> specializeUpcall(leafHandle, callingSequence, abi));
return MethodHandles.insertArguments(wrapper, 0, leafHandle); // lazily customized for leaf handle instances
} else {
return specializeDowncall(leafHandle, callingSequence, abi);
}
}
private static MethodHandle specializeDowncall(MethodHandle leafHandle, CallingSequence callingSequence, ABIDescriptor abi) {
MethodType callerMethodType = callingSequence.callerMethodType();
if (callingSequence.needsReturnBuffer()) {
callerMethodType = callerMethodType.dropParameterTypes(0, 1); // Return buffer does not appear in the parameter list
}
callerMethodType = callerMethodType.insertParameterTypes(0, SegmentAllocator.class);
byte[] bytes = specializeHelper(leafHandle.type(), callerMethodType, callingSequence, abi);
try {
MethodHandles.Lookup definedClassLookup = MethodHandles.lookup().defineHiddenClassWithClassData(bytes, leafHandle, false);
return definedClassLookup.findStatic(definedClassLookup.lookupClass(), METHOD_NAME, callerMethodType);
} catch (IllegalAccessException | NoSuchMethodException e) {
throw new InternalError("Should not happen", e);
}
}
private static MethodHandle specializeUpcall(MethodHandle leafHandle, CallingSequence callingSequence, ABIDescriptor abi) {
MethodType callerMethodType = callingSequence.callerMethodType();
callerMethodType = callerMethodType.insertParameterTypes(0, MethodHandle.class); // target
byte[] bytes = specializeHelper(leafHandle.type(), callerMethodType, callingSequence, abi);
try {
// For upcalls, we must initialize the class since the upcall stubs don't have a clinit barrier,
// and the slow path in the c2i adapter we end up calling can not handle the particular code shape
// where the caller is an upcall stub.
MethodHandles.Lookup defineClassLookup = MethodHandles.lookup().defineHiddenClass(bytes, true);
return defineClassLookup.findStatic(defineClassLookup.lookupClass(), METHOD_NAME, callerMethodType);
} catch (IllegalAccessException | NoSuchMethodException e) {
throw new InternalError("Should not happen", e);
}
}
private static byte[] specializeHelper(MethodType leafType, MethodType callerMethodType,
CallingSequence callingSequence, ABIDescriptor abi) {
String className = callingSequence.forDowncall() ? CLASS_NAME_DOWNCALL : CLASS_NAME_UPCALL;
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
cw.visit(CLASSFILE_VERSION, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, SUPER_NAME, null);
String descriptor = callerMethodType.descriptorString();
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, METHOD_NAME, descriptor, null, null);
new BindingSpecializer(mv, callerMethodType, callingSequence, abi, leafType).specialize();
mv.visitMaxs(0, 0);
mv.visitEnd();
cw.visitEnd();
byte[] bytes = cw.toByteArray();
if (DUMP_CLASSES_DIR != null) {
String fileName = className + escapeForFileName(callingSequence.functionDesc().toString()) + ".class";
Path dumpPath = Path.of(DUMP_CLASSES_DIR).resolve(fileName);
try {
Files.createDirectories(dumpPath.getParent());
Files.write(dumpPath, bytes);
} catch (IOException e) {
throw new InternalError(e);
}
}
if (PERFORM_VERIFICATION) {
boolean printResults = false; // only print in case of exception
CheckClassAdapter.verify(new ClassReader(bytes), null, printResults, new PrintWriter(System.err));
}
return bytes;
}
private static String escapeForFileName(String str) {
StringBuilder sb = new StringBuilder(str.length());
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
sb.append(switch (c) {
case ' ' -> '_';
case '[', '<' -> '{';
case ']', '>' -> '}';
case '/', '\\', ':', '*', '?', '"', '|' -> '!'; // illegal in Windows file names.
default -> c;
});
}
return sb.toString();
}
// binding operand stack manipulation
private void pushType(Class<?> type) {
typeStack.push(type);
}
private Class<?> popType(Class<?> expected) {
return popType(expected, ASSERT_EQUALS);
}
private Class<?> popType(Class<?> expected, BiPredicate<Class<?>, Class<?>> typePredicate) {
Class<?> found;
if (!typePredicate.test(expected, found = typeStack.pop())) {
throw new IllegalStateException(
String.format("Invalid type on binding operand stack; found %s - expected %s",
found.descriptorString(), expected.descriptorString()));
}
return found;
}
private static final BiPredicate<Class<?>, Class<?>> ASSERT_EQUALS = Class::equals;
private static final BiPredicate<Class<?>, Class<?>> ASSERT_ASSIGNABLE = Class::isAssignableFrom;
// specialization
private void specialize() {
// map of parameter indexes to local var table slots
paramIndex2ParamSlot = new int[callerMethodType.parameterCount()];
for (int i = 0; i < callerMethodType.parameterCount(); i++) {
paramIndex2ParamSlot[i] = newLocal(callerMethodType.parameterType(i));
}
// slots that store the output arguments (passed to the leaf handle)
leafArgSlots = new int[leafType.parameterCount()];
for (int i = 0; i < leafType.parameterCount(); i++) {
leafArgSlots[i] = newLocal(leafType.parameterType(i));
}
// allocator passed to us for allocating the return MS (downcalls only)
if (callingSequence.forDowncall()) {
returnAllocatorIdx = 0; // first param
// for downcalls we also acquire/release scoped parameters before/after the call
// create a bunch of locals here to keep track of their scopes (to release later)
int[] initialScopeSlots = new int[callerMethodType.parameterCount()];
int numScopes = 0;
for (int i = 0; i < callerMethodType.parameterCount(); i++) {
if (shouldAcquire(callerMethodType.parameterType(i))) {
int scopeLocal = newLocal(Object.class);
initialScopeSlots[numScopes++] = scopeLocal;
emitConst(null);
emitStore(Object.class, scopeLocal); // need to initialize all scope locals here in case an exception occurs
}
}
scopeSlots = Arrays.copyOf(initialScopeSlots, numScopes); // fit to size
curScopeLocalIdx = 0; // used from emitGetInput
}
// create a Binding.Context for this call
if (callingSequence.allocationSize() != 0) {
emitConst(callingSequence.allocationSize());
emitInvokeStatic(Binding.Context.class, "ofBoundedAllocator", OF_BOUNDED_ALLOCATOR_DESC);
} else if (callingSequence.forUpcall() && needsSession()) {
emitInvokeStatic(Binding.Context.class, "ofSession", OF_SESSION_DESC);
} else {
emitGetStatic(Binding.Context.class, "DUMMY", BINDING_CONTEXT_DESC);
}
CONTEXT_IDX = newLocal(Object.class);
emitStore(Object.class, CONTEXT_IDX);
// in case the call needs a return buffer, allocate it here.
// for upcalls the VM wrapper stub allocates the buffer.
if (callingSequence.needsReturnBuffer() && callingSequence.forDowncall()) {
emitLoadInternalAllocator();
emitAllocateCall(callingSequence.returnBufferSize(), 1);
returnBufferIdx = newLocal(Object.class);
emitStore(Object.class, returnBufferIdx);
}
Label tryStart = new Label();
Label tryEnd = new Label();
Label catchStart = new Label();
mv.visitLabel(tryStart);
// stack to keep track of types on the bytecode stack between bindings.
// this is needed to e.g. emit the right DUP instruction,
// but also used for type checking.
typeStack = new ArrayDeque<>();
// leaf arg types are the types of the args passed to the leaf handle.
// these are collected from VM_STORE instructions for downcalls, and
// recipe outputs for upcalls (see uses emitSetOutput for both)
leafArgTypes = new ArrayList<>();
paramIndex = 1; // +1 to skip SegmentAllocator or MethodHandle
for (int i = 0; i < callingSequence.argumentBindingsCount(); i++) {
if (callingSequence.forDowncall()) {
// for downcalls, recipes have an input value, which we set up here
if (callingSequence.needsReturnBuffer() && i == 0) {
assert returnBufferIdx != -1;
emitLoad(Object.class, returnBufferIdx);
pushType(MemorySegment.class);
} else {
emitGetInput();
}
}
// emit code according to binding recipe
doBindings(callingSequence.argumentBindings(i));
if (callingSequence.forUpcall()) {
// for upcalls, recipes have a result, which we handle here
if (callingSequence.needsReturnBuffer() && i == 0) {
// return buffer ptr is wrapped in a MemorySegment above, but not passed to the leaf handle
popType(MemorySegment.class);
returnBufferIdx = newLocal(Object.class);
emitStore(Object.class, returnBufferIdx);
} else {
// for upcalls the recipe result is an argument to the leaf handle
emitSetOutput(typeStack.pop());
}
}
assert typeStack.isEmpty();
}
assert leafArgTypes.equals(leafType.parameterList());
// load the leaf MethodHandle
if (callingSequence.forDowncall()) {
mv.visitLdcInsn(CLASS_DATA_CONDY);
} else {
emitLoad(Object.class, 0); // load target arg
}
emitCheckCast(MethodHandle.class);
// load all the leaf args
for (int i = 0; i < leafArgSlots.length; i++) {
emitLoad(leafArgTypes.get(i), leafArgSlots[i]);
}
// call leaf MH
emitInvokeVirtual(MethodHandle.class, "invokeExact", leafType.descriptorString());
// for downcalls, store the result of the leaf handle call away, until
// it is requested by a VM_LOAD in the return recipe.
if (callingSequence.forDowncall() && leafType.returnType() != void.class) {
emitSaveReturnValue(leafType.returnType());
}
// for upcalls we leave the return value on the stack to be picked up
// as an input of the return recipe.
// return value processing
if (callingSequence.hasReturnBindings()) {
if (callingSequence.forUpcall()) {
pushType(leafType.returnType());
}
retBufOffset = 0; // offset for reading from return buffer
doBindings(callingSequence.returnBindings());
if (callingSequence.forUpcall() && !callingSequence.needsReturnBuffer()) {
// was VM_STOREd somewhere in the bindings
emitRestoreReturnValue(callerMethodType.returnType());
}
mv.visitLabel(tryEnd);
// finally
emitCleanup();
if (callerMethodType.returnType() == void.class) {
// The case for upcalls that return by return buffer
assert typeStack.isEmpty();
mv.visitInsn(RETURN);
} else {
popType(callerMethodType.returnType());
assert typeStack.isEmpty();
emitReturn(callerMethodType.returnType());
}
} else {
assert callerMethodType.returnType() == void.class;
assert typeStack.isEmpty();
mv.visitLabel(tryEnd);
// finally
emitCleanup();
mv.visitInsn(RETURN);
}
mv.visitLabel(catchStart);
// finally
emitCleanup();
if (callingSequence.forDowncall()) {
mv.visitInsn(ATHROW);
} else {
emitInvokeStatic(SharedUtils.class, "handleUncaughtException", HANDLE_UNCAUGHT_EXCEPTION_DESC);
if (callerMethodType.returnType() != void.class) {
emitConstZero(callerMethodType.returnType());
emitReturn(callerMethodType.returnType());
} else {
mv.visitInsn(RETURN);
}
}
mv.visitTryCatchBlock(tryStart, tryEnd, catchStart, null);
}
private boolean needsSession() {
return callingSequence.argumentBindings().anyMatch(Binding.ToSegment.class::isInstance);
}
private static boolean shouldAcquire(Class<?> type) {
return type == Addressable.class;
}
private void emitCleanup() {
emitCloseContext();
if (callingSequence.forDowncall()) {
emitReleaseScopes();
}
}
private void doBindings(List<Binding> bindings) {
for (Binding binding : bindings) {
switch (binding.tag()) {
case VM_STORE -> emitVMStore((Binding.VMStore) binding);
case VM_LOAD -> emitVMLoad((Binding.VMLoad) binding);
case BUFFER_STORE -> emitBufferStore((Binding.BufferStore) binding);
case BUFFER_LOAD -> emitBufferLoad((Binding.BufferLoad) binding);
case COPY_BUFFER -> emitCopyBuffer((Binding.Copy) binding);
case ALLOC_BUFFER -> emitAllocBuffer((Binding.Allocate) binding);
case BOX_ADDRESS -> emitBoxAddress();
case UNBOX_ADDRESS -> emitUnboxAddress();
case TO_SEGMENT -> emitToSegment((Binding.ToSegment) binding);
case DUP -> emitDupBinding();
}
}
}
private void emitSetOutput(Class<?> storeType) {
emitStore(storeType, leafArgSlots[leafArgTypes.size()]);
leafArgTypes.add(storeType);
}
private void emitGetInput() {
Class<?> highLevelType = callerMethodType.parameterType(paramIndex);
emitLoad(highLevelType, paramIndex2ParamSlot[paramIndex]);
if (shouldAcquire(highLevelType)) {
emitDup(Object.class);
emitAcquireScope();
}
pushType(highLevelType);
paramIndex++;
}
private void emitAcquireScope() {
emitCheckCast(Scoped.class);
emitInvokeInterface(Scoped.class, "sessionImpl", SESSION_IMPL_DESC);
Label skipAcquire = new Label();
Label end = new Label();
// start with 1 scope to maybe acquire on the stack
assert curScopeLocalIdx != -1;
boolean hasOtherScopes = curScopeLocalIdx != 0;
for (int i = 0; i < curScopeLocalIdx; i++) {
emitDup(Object.class); // dup for comparison
emitLoad(Object.class, scopeSlots[i]);
mv.visitJumpInsn(IF_ACMPEQ, skipAcquire);
}
// 1 scope to acquire on the stack
emitDup(Object.class);
int nextScopeLocal = scopeSlots[curScopeLocalIdx++];
emitStore(Object.class, nextScopeLocal); // store off one to release later
emitInvokeVirtual(MemorySessionImpl.class, "acquire0", ACQUIRE0_DESC); // call acquire on the other
if (hasOtherScopes) { // avoid ASM generating a bunch of nops for the dead code
mv.visitJumpInsn(GOTO, end);
mv.visitLabel(skipAcquire);
mv.visitInsn(POP); // drop scope
}
mv.visitLabel(end);
}
private void emitReleaseScopes() {
for (int scopeLocal : scopeSlots) {
Label skipRelease = new Label();
emitLoad(Object.class, scopeLocal);
mv.visitJumpInsn(IFNULL, skipRelease);
emitLoad(Object.class, scopeLocal);
emitInvokeVirtual(MemorySessionImpl.class, "release0", RELEASE0_DESC);
mv.visitLabel(skipRelease);
}
}
private void emitSaveReturnValue(Class<?> storeType) {
retValIdx = newLocal(storeType);
emitStore(storeType, retValIdx);
}
private void emitRestoreReturnValue(Class<?> loadType) {
assert retValIdx != -1;
emitLoad(loadType, retValIdx);
pushType(loadType);
}
private int newLocal(Class<?> type) {
int idx = localIdx;
localIdx += Type.getType(type).getSize();
return idx;
}
private void emitLoadInternalSession() {
assert CONTEXT_IDX != -1;
emitLoad(Object.class, CONTEXT_IDX);
emitInvokeVirtual(Binding.Context.class, "session", SESSION_DESC);
}
private void emitLoadInternalAllocator() {
assert CONTEXT_IDX != -1;
emitLoad(Object.class, CONTEXT_IDX);
emitInvokeVirtual(Binding.Context.class, "allocator", ALLOCATOR_DESC);
}
private void emitCloseContext() {
assert CONTEXT_IDX != -1;
emitLoad(Object.class, CONTEXT_IDX);
emitInvokeVirtual(Binding.Context.class, "close", CLOSE_DESC);
}
private void emitToSegment(Binding.ToSegment binding) {
long size = binding.size();
popType(MemoryAddress.class);
emitToRawLongValue();
emitConst(size);
emitLoadInternalSession();
emitInvokeStatic(MemoryAddressImpl.class, "ofLongUnchecked", OF_LONG_UNCHECKED_DESC);
pushType(MemorySegment.class);
}
private void emitToRawLongValue() {
emitInvokeInterface(MemoryAddress.class, "toRawLongValue", TO_RAW_LONG_VALUE_DESC);
}
private void emitBoxAddress() {
popType(long.class);
emitInvokeStatic(MemoryAddress.class, "ofLong", OF_LONG_DESC);
pushType(MemoryAddress.class);
}
private void emitAllocBuffer(Binding.Allocate binding) {
if (callingSequence.forDowncall()) {
assert returnAllocatorIdx != -1;
emitLoad(Object.class, returnAllocatorIdx);
} else {
emitLoadInternalAllocator();
}
emitAllocateCall(binding.size(), binding.alignment());
pushType(MemorySegment.class);
}
private void emitBufferStore(Binding.BufferStore bufferStore) {
Class<?> storeType = bufferStore.type();
long offset = bufferStore.offset();
popType(storeType);
popType(MemorySegment.class);
int valueIdx = newLocal(storeType);
emitStore(storeType, valueIdx);
Class<?> valueLayoutType = emitLoadLayoutConstant(storeType);
emitConst(offset);
emitLoad(storeType, valueIdx);
String descriptor = methodType(void.class, valueLayoutType, long.class, storeType).descriptorString();
emitInvokeInterface(MemorySegment.class, "set", descriptor);
}
// VM_STORE and VM_LOAD are emulated, which is different for down/upcalls
private void emitVMStore(Binding.VMStore vmStore) {
Class<?> storeType = vmStore.type();
popType(storeType);
if (callingSequence.forDowncall()) {
// processing arg
emitSetOutput(storeType);
} else {
// processing return
if (!callingSequence.needsReturnBuffer()) {
emitSaveReturnValue(storeType);
} else {
int valueIdx = newLocal(storeType);
emitStore(storeType, valueIdx); // store away the stored value, need it later
assert returnBufferIdx != -1;
emitLoad(Object.class, returnBufferIdx);
Class<?> valueLayoutType = emitLoadLayoutConstant(storeType);
emitConst(retBufOffset);
emitLoad(storeType, valueIdx);
String descriptor = methodType(void.class, valueLayoutType, long.class, storeType).descriptorString();
emitInvokeInterface(MemorySegment.class, "set", descriptor);
retBufOffset += abi.arch.typeSize(vmStore.storage().type());
}
}
}
private void emitVMLoad(Binding.VMLoad vmLoad) {
Class<?> loadType = vmLoad.type();
if (callingSequence.forDowncall()) {
// processing return
if (!callingSequence.needsReturnBuffer()) {
emitRestoreReturnValue(loadType);
} else {
assert returnBufferIdx != -1;
emitLoad(Object.class, returnBufferIdx);
Class<?> valueLayoutType = emitLoadLayoutConstant(loadType);
emitConst(retBufOffset);
String descriptor = methodType(loadType, valueLayoutType, long.class).descriptorString();
emitInvokeInterface(MemorySegment.class, "get", descriptor);
retBufOffset += abi.arch.typeSize(vmLoad.storage().type());
pushType(loadType);
}
} else {
// processing arg
emitGetInput();
}
}
private void emitDupBinding() {
Class<?> dupType = typeStack.peek();
emitDup(dupType);
pushType(dupType);
}
private void emitUnboxAddress() {
popType(Addressable.class, ASSERT_ASSIGNABLE);
emitInvokeInterface(Addressable.class, "address", ADDRESS_DESC);
emitToRawLongValue();
pushType(long.class);
}
private void emitBufferLoad(Binding.BufferLoad bufferLoad) {
Class<?> loadType = bufferLoad.type();
long offset = bufferLoad.offset();
popType(MemorySegment.class);
Class<?> valueLayoutType = emitLoadLayoutConstant(loadType);
emitConst(offset);
String descriptor = methodType(loadType, valueLayoutType, long.class).descriptorString();
emitInvokeInterface(MemorySegment.class, "get", descriptor);
pushType(loadType);
}
private void emitCopyBuffer(Binding.Copy copy) {
long size = copy.size();
long alignment = copy.alignment();
popType(MemorySegment.class);
// operand/srcSegment is on the stack
// generating a call to:
// MemorySegment::copy(MemorySegment srcSegment, long srcOffset, MemorySegment dstSegment, long dstOffset, long bytes)
emitConst(0L);
// create the dstSegment by allocating it. Similar to:
// context.allocator().allocate(size, alignment)
emitLoadInternalAllocator();
emitAllocateCall(size, alignment);
emitDup(Object.class);
int storeIdx = newLocal(Object.class);
emitStore(Object.class, storeIdx);
emitConst(0L);
emitConst(size);
emitInvokeStatic(MemorySegment.class, "copy", COPY_DESC);
emitLoad(Object.class, storeIdx);
pushType(MemorySegment.class);
}
private void emitAllocateCall(long size, long alignment) {
emitConst(size);
emitConst(alignment);
emitInvokeInterface(SegmentAllocator.class, "allocate", ALLOCATE_DESC);
}
private Class<?> emitLoadLayoutConstant(Class<?> type) {
Class<?> valueLayoutType = valueLayoutTypeFor(type);
String valueLayoutConstantName = valueLayoutConstantFor(type);
emitGetStatic(BindingSpecializer.Runtime.class, valueLayoutConstantName, valueLayoutType.descriptorString());
return valueLayoutType;
}
private static String valueLayoutConstantFor(Class<?> type) {
if (type == boolean.class) {
return "JAVA_BOOLEAN_UNALIGNED";
} else if (type == byte.class) {
return "JAVA_BYTE_UNALIGNED";
} else if (type == short.class) {
return "JAVA_SHORT_UNALIGNED";
} else if (type == char.class) {
return "JAVA_CHAR_UNALIGNED";
} else if (type == int.class) {
return "JAVA_INT_UNALIGNED";
} else if (type == long.class) {
return "JAVA_LONG_UNALIGNED";
} else if (type == float.class) {
return "JAVA_FLOAT_UNALIGNED";
} else if (type == double.class) {
return "JAVA_DOUBLE_UNALIGNED";
} else if (type == MemoryAddress.class) {
return "ADDRESS_UNALIGNED";
} else {
throw new IllegalStateException("Unknown type: " + type);
}
}
private static Class<?> valueLayoutTypeFor(Class<?> type) {
if (type == boolean.class) {
return ValueLayout.OfBoolean.class;
} else if (type == byte.class) {
return ValueLayout.OfByte.class;
} else if (type == short.class) {
return ValueLayout.OfShort.class;
} else if (type == char.class) {
return ValueLayout.OfChar.class;
} else if (type == int.class) {
return ValueLayout.OfInt.class;
} else if (type == long.class) {
return ValueLayout.OfLong.class;
} else if (type == float.class) {
return ValueLayout.OfFloat.class;
} else if (type == double.class) {
return ValueLayout.OfDouble.class;
} else if (type == MemoryAddress.class) {
return ValueLayout.OfAddress.class;
} else {
throw new IllegalStateException("Unknown type: " + type);
}
}
private void emitInvokeStatic(Class<?> owner, String methodName, String descriptor) {
mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(owner), methodName, descriptor, owner.isInterface());
}
private void emitInvokeInterface(Class<?> owner, String methodName, String descriptor) {
mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(owner), methodName, descriptor, true);
}
private void emitInvokeVirtual(Class<?> owner, String methodName, String descriptor) {
mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(owner), methodName, descriptor, false);
}
private void emitGetStatic(Class<?> owner, String fieldName, String descriptor) {
mv.visitFieldInsn(GETSTATIC, Type.getInternalName(owner), fieldName, descriptor);
}
private void emitCheckCast(Class<?> cls) {
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(cls));
}
private void emitDup(Class<?> type) {
if (type == double.class || type == long.class) {
mv.visitInsn(DUP2);
} else {
mv.visitInsn(Opcodes.DUP);
}
}
/*
* Low-level emit helpers.
*/
private void emitConstZero(Class<?> type) {
emitConst(switch (Type.getType(type).getSort()) {
case Type.BOOLEAN, Type.BYTE, Type.SHORT, Type.CHAR, Type.INT -> 0;
case Type.LONG -> 0L;
case Type.FLOAT -> 0F;
case Type.DOUBLE -> 0D;
case Type.OBJECT -> null;
default -> throw new IllegalArgumentException("Unknown type: " + type);
});
}
private void emitConst(Object con) {
if (con == null) {
mv.visitInsn(Opcodes.ACONST_NULL);
return;
}
if (con instanceof Integer i) {
emitIconstInsn(i);
return;
}
if (con instanceof Byte b) {
emitIconstInsn(b);
return;
}
if (con instanceof Short s) {
emitIconstInsn(s);
return;
}
if (con instanceof Character c) {
emitIconstInsn(c);
return;
}
if (con instanceof Long l) {
long x = l;
short sx = (short)x;
if (x == sx) {
if (sx >= 0 && sx <= 1) {
mv.visitInsn(Opcodes.LCONST_0 + (int) sx);
} else {
emitIconstInsn((int) x);
mv.visitInsn(Opcodes.I2L);
}
return;
}
}
if (con instanceof Float f) {
float x = f;
short sx = (short)x;
if (x == sx) {
if (sx >= 0 && sx <= 2) {
mv.visitInsn(Opcodes.FCONST_0 + (int) sx);
} else {
emitIconstInsn((int) x);
mv.visitInsn(Opcodes.I2F);
}
return;
}
}
if (con instanceof Double d) {
double x = d;
short sx = (short)x;
if (x == sx) {
if (sx >= 0 && sx <= 1) {
mv.visitInsn(Opcodes.DCONST_0 + (int) sx);
} else {
emitIconstInsn((int) x);
mv.visitInsn(Opcodes.I2D);
}
return;
}
}
if (con instanceof Boolean b) {
emitIconstInsn(b ? 1 : 0);
return;
}
// fall through:
mv.visitLdcInsn(con);
}
private void emitIconstInsn(int cst) {
if (cst >= -1 && cst <= 5) {
mv.visitInsn(Opcodes.ICONST_0 + cst);
} else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) {
mv.visitIntInsn(Opcodes.BIPUSH, cst);
} else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) {
mv.visitIntInsn(Opcodes.SIPUSH, cst);
} else {
mv.visitLdcInsn(cst);
}
}
private void emitLoad(Class<?> type, int index) {
int opcode = Type.getType(type).getOpcode(ILOAD);
mv.visitVarInsn(opcode, index);
}
private void emitStore(Class<?> type, int index) {
int opcode = Type.getType(type).getOpcode(ISTORE);
mv.visitVarInsn(opcode, index);
}
private void emitReturn(Class<?> type) {
int opcode = Type.getType(type).getOpcode(IRETURN);
mv.visitInsn(opcode);
}
// constants that are accessed from the generated bytecode
// see emitLoadLayoutConstant
static class Runtime {
// unaligned constants
static final ValueLayout.OfBoolean JAVA_BOOLEAN_UNALIGNED = JAVA_BOOLEAN;
static final ValueLayout.OfByte JAVA_BYTE_UNALIGNED = JAVA_BYTE;
static final ValueLayout.OfShort JAVA_SHORT_UNALIGNED = JAVA_SHORT.withBitAlignment(8);
static final ValueLayout.OfChar JAVA_CHAR_UNALIGNED = JAVA_CHAR.withBitAlignment(8);
static final ValueLayout.OfInt JAVA_INT_UNALIGNED = JAVA_INT.withBitAlignment(8);
static final ValueLayout.OfLong JAVA_LONG_UNALIGNED = JAVA_LONG.withBitAlignment(8);
static final ValueLayout.OfFloat JAVA_FLOAT_UNALIGNED = JAVA_FLOAT.withBitAlignment(8);
static final ValueLayout.OfDouble JAVA_DOUBLE_UNALIGNED = JAVA_DOUBLE.withBitAlignment(8);
static final ValueLayout.OfAddress ADDRESS_UNALIGNED = ADDRESS.withBitAlignment(8);
}
}

View File

@ -31,7 +31,9 @@ import java.util.List;
import java.util.stream.Stream;
public class CallingSequence {
private final MethodType mt;
private final boolean forUpcall;
private final MethodType callerMethodType;
private final MethodType calleeMethodType;
private final FunctionDescriptor desc;
private final boolean needsReturnBuffer;
private final long returnBufferSize;
@ -40,10 +42,12 @@ public class CallingSequence {
private final List<Binding> returnBindings;
private final List<List<Binding>> argumentBindings;
public CallingSequence(MethodType mt, FunctionDescriptor desc,
public CallingSequence(boolean forUpcall, MethodType callerMethodType, MethodType calleeMethodType, FunctionDescriptor desc,
boolean needsReturnBuffer, long returnBufferSize, long allocationSize,
List<List<Binding>> argumentBindings, List<Binding> returnBindings) {
this.mt = mt;
this.forUpcall = forUpcall;
this.callerMethodType = callerMethodType;
this.calleeMethodType = calleeMethodType;
this.desc = desc;
this.needsReturnBuffer = needsReturnBuffer;
this.returnBufferSize = returnBufferSize;
@ -52,7 +56,21 @@ public class CallingSequence {
this.argumentBindings = argumentBindings;
}
public int argumentCount() {
/**
* An important distinction is that downcalls have 1 recipe per caller parameter and
* each callee parameter corresponds to a VM_STORE. Upcalls have 1 recipe per callee parameter and
* each caller parameter corresponds to a VM_LOAD.
*
* The VM_STOREs are then implemented by the leaf handle for downcalls, and vice versa, the wrapper
* stub that wraps an upcall handle implements the VM_LOADS. In both cases the register values are
* communicated through Java primitives.
*
* The 'argumentBindingsCount' below corresponds to the number of recipes, so it is the
* caller parameter count for downcalls, and the callee parameter count for upcalls.
*
* @return the number of binding recipes in this calling sequence
*/
public int argumentBindingsCount() {
return argumentBindings.size();
}
@ -68,41 +86,117 @@ public class CallingSequence {
return returnBindings;
}
public String asString() {
StringBuilder sb = new StringBuilder();
sb.append("CallingSequence: {\n");
sb.append(" MethodType: ").append(mt);
sb.append(" FunctionDescriptor: ").append(desc);
sb.append(" Argument Bindings:\n");
for (int i = 0; i < mt.parameterCount(); i++) {
sb.append(" ").append(i).append(": ").append(argumentBindings.get(i)).append("\n");
}
if (mt.returnType() != void.class) {
sb.append(" ").append("Return: ").append(returnBindings).append("\n");
}
sb.append("}\n");
return sb.toString();
public boolean forUpcall() {
return forUpcall;
}
public MethodType methodType() {
return mt;
public boolean forDowncall() {
return !forUpcall;
}
/**
* Returns the caller method type, which is the high-level method type
* for downcalls (the type of the downcall method handle)
* and the low-level method type (all primitives, VM facing) for upcalls.
*
* Note that for downcalls a single parameter in this method type corresponds
* to a single argument binding recipe in this calling sequence, but it may
* correspond to multiple parameters in the callee method type (for instance
* if a struct is split into multiple register values).
*
* @return the caller method type.
*/
public MethodType callerMethodType() {
return callerMethodType;
}
/**
* Returns the callee method type, which is the low-level method type
* (all primitives, VM facing) for downcalls and the high-level method type
* for upcalls (also the method type of the user-supplied target MH).
*
* Note that for upcalls a single parameter in this method type corresponds
* to a single argument binding recipe in this calling sequence, but it may
* correspond to multiple parameters in the caller method type (for instance
* if a struct is reconstructed from multiple register values).
*
* @return the callee method type.
*/
public MethodType calleeMethodType() {
return calleeMethodType;
}
public FunctionDescriptor functionDesc() {
return desc;
}
/**
* Whether this calling sequence needs a return buffer.
*
* A return buffer is used to support functions that return values
* in multiple registers, which is not possible to do just with Java primitives
* (we can only return 1 value in Java, meaning only 1 register value).
*
* To emulate these multi-register returns, we instead use a pre-allocated buffer
* (the return buffer) from/into which the return values are loaded/stored.
*
* For downcalls, we allocate the buffer in Java code, and pass the address down
* to the VM stub, which stores the returned register values into this buffer.
* VM_LOADs in the binding recipe for the return value then load the value from this buffer.
*
* For upcalls, the VM stub allocates a buffer (on the stack), and passes the address
* to the Java method handle it calls. VM_STOREs in the return binding recipe then
* store values into this buffer, after which the VM stub moves the values from the buffer
* into the right register.
*
* @return whether this calling sequence needs a return buffer.
*/
public boolean needsReturnBuffer() {
return needsReturnBuffer;
}
/**
* The size of the return buffer, if one is needed.
*
* {@see #needsReturnBuffer}
*
* @return the return buffer size
*/
public long returnBufferSize() {
return returnBufferSize;
}
/**
* The amount of bytes this calling sequence needs to allocate during an invocation.
*
* Includes the return buffer size as well as space for any buffer copies in the recipes.
*
* @return the allocation size
*/
public long allocationSize() {
return allocationSize;
}
public boolean hasReturnBindings() {
return !returnBindings.isEmpty();
}
public String asString() {
StringBuilder sb = new StringBuilder();
sb.append("CallingSequence: {\n");
sb.append(" callerMethodType: ").append(callerMethodType);
sb.append(" calleeMethodType: ").append(calleeMethodType);
sb.append(" FunctionDescriptor: ").append(desc);
sb.append(" Argument Bindings:\n");
for (int i = 0; i < argumentBindingsCount(); i++) {
sb.append(" ").append(i).append(": ").append(argumentBindings.get(i)).append("\n");
}
if (!returnBindings.isEmpty()) {
sb.append(" ").append("Return: ").append(returnBindings).append("\n");
}
sb.append("}\n");
return sb.toString();
}
}

View File

@ -40,6 +40,7 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import static java.lang.invoke.MethodType.methodType;
import static jdk.internal.foreign.abi.Binding.Tag.*;
public class CallingSequenceBuilder {
@ -92,6 +93,8 @@ public class CallingSequenceBuilder {
boolean needsReturnBuffer = needsReturnBuffer();
long returnBufferSize = needsReturnBuffer ? computeReturnBuferSize() : 0;
long allocationSize = computeAllocationSize() + returnBufferSize;
MethodType callerMethodType;
MethodType calleeMethodType;
if (!forUpcall) {
addArgumentBinding(0, Addressable.class, ValueLayout.ADDRESS, List.of(
Binding.unboxAddress(Addressable.class),
@ -101,13 +104,48 @@ public class CallingSequenceBuilder {
Binding.unboxAddress(MemorySegment.class),
Binding.vmStore(abi.retBufAddrStorage(), long.class)));
}
} else if (needsReturnBuffer) { // forUpcall == true
addArgumentBinding(0, MemorySegment.class, ValueLayout.ADDRESS, List.of(
Binding.vmLoad(abi.retBufAddrStorage(), long.class),
Binding.boxAddress(),
Binding.toSegment(returnBufferSize)));
callerMethodType = mt;
calleeMethodType = computeCalleeTypeForDowncall();
} else { // forUpcall == true
if (needsReturnBuffer) {
addArgumentBinding(0, MemorySegment.class, ValueLayout.ADDRESS, List.of(
Binding.vmLoad(abi.retBufAddrStorage(), long.class),
Binding.boxAddress(),
Binding.toSegment(returnBufferSize)));
}
callerMethodType = computeCallerTypeForUpcall();
calleeMethodType = mt;
}
return new CallingSequence(mt, desc, needsReturnBuffer, returnBufferSize, allocationSize, inputBindings, outputBindings);
return new CallingSequence(forUpcall, callerMethodType, calleeMethodType, desc, needsReturnBuffer,
returnBufferSize, allocationSize, inputBindings, outputBindings);
}
private MethodType computeCallerTypeForUpcall() {
return computeTypeHelper(Binding.VMLoad.class, Binding.VMStore.class);
}
private MethodType computeCalleeTypeForDowncall() {
return computeTypeHelper(Binding.VMStore.class, Binding.VMLoad.class);
}
private MethodType computeTypeHelper(Class<? extends Binding.Move> inputVMClass,
Class<? extends Binding.Move> outputVMClass) {
Class<?>[] paramTypes = inputBindings.stream()
.flatMap(List::stream)
.filter(inputVMClass::isInstance)
.map(inputVMClass::cast)
.map(Binding.Move::type)
.toArray(Class<?>[]::new);
Binding.Move[] retMoves = outputBindings.stream()
.filter(outputVMClass::isInstance)
.map(outputVMClass::cast)
.toArray(Binding.Move[]::new);
Class<?> returnType = retMoves.length == 1 ? retMoves[0].type() : void.class;
return methodType(returnType, paramTypes);
}
private long computeAllocationSize() {

View File

@ -25,11 +25,12 @@
package jdk.internal.foreign.abi;
import jdk.internal.ref.CleanerFactory;
import java.lang.invoke.MethodType;
import java.lang.ref.Cleaner;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* This class describes a 'native invoker', which is used as an appendix argument to linkToNative calls.
@ -42,7 +43,8 @@ public class NativeEntryPoint {
private final MethodType methodType;
private final long invoker; // read by VM
private static final Map<CacheKey, Long> INVOKER_CACHE = new ConcurrentHashMap<>();
private static final Cleaner CLEANER = CleanerFactory.cleaner();
private static final SoftReferenceCache<CacheKey, NativeEntryPoint> INVOKER_CACHE = new SoftReferenceCache<>();
private record CacheKey(MethodType methodType, ABIDescriptor abi,
List<VMStorage> argMoves, List<VMStorage> retMoves,
boolean needsReturnBuffer) {}
@ -63,16 +65,25 @@ public class NativeEntryPoint {
assert (!needsReturnBuffer || methodType.parameterType(1) == long.class) : "return buffer address expected";
CacheKey key = new CacheKey(methodType, abi, Arrays.asList(argMoves), Arrays.asList(returnMoves), needsReturnBuffer);
long invoker = INVOKER_CACHE.computeIfAbsent(key, k ->
makeInvoker(methodType, abi, argMoves, returnMoves, needsReturnBuffer));
return new NativeEntryPoint(methodType, invoker);
return INVOKER_CACHE.get(key, k -> {
long invoker = makeInvoker(methodType, abi, argMoves, returnMoves, needsReturnBuffer);
NativeEntryPoint nep = new NativeEntryPoint(methodType, invoker);
CLEANER.register(nep, () -> freeInvoker(invoker));
return nep;
});
}
private static native long makeInvoker(MethodType methodType, ABIDescriptor abi,
VMStorage[] encArgMoves, VMStorage[] encRetMoves,
boolean needsReturnBuffer);
private static native boolean freeInvoker0(long invoker);
private static void freeInvoker(long invoker) {
if (!freeInvoker0(invoker)) {
throw new InternalError("Could not free invoker");
}
}
public MethodType type() {
return methodType;
}

View File

@ -24,28 +24,21 @@
*/
package jdk.internal.foreign.abi;
import java.lang.foreign.Addressable;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.ValueLayout;
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import sun.security.action.GetPropertyAction;
import java.lang.foreign.Addressable;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SegmentAllocator;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.foldArguments;
import static java.lang.invoke.MethodHandles.identity;
import static java.lang.invoke.MethodHandles.insertArguments;
@ -63,10 +56,7 @@ public class ProgrammableInvoker {
private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess();
private static final MethodHandle MH_INVOKE_INTERP_BINDINGS;
private static final MethodHandle MH_WRAP_ALLOCATOR;
private static final MethodHandle MH_ALLOCATE_RETURN_BUFFER;
private static final MethodHandle MH_CHECK_SYMBOL;
private static final MethodHandle EMPTY_OBJECT_ARRAY_HANDLE = MethodHandles.constant(Object[].class, new Object[0]);
static {
@ -74,12 +64,8 @@ public class ProgrammableInvoker {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MH_INVOKE_INTERP_BINDINGS = lookup.findVirtual(ProgrammableInvoker.class, "invokeInterpBindings",
methodType(Object.class, SegmentAllocator.class, Object[].class, InvocationData.class));
MH_WRAP_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofAllocator",
methodType(Binding.Context.class, SegmentAllocator.class));
MH_CHECK_SYMBOL = lookup.findStatic(SharedUtils.class, "checkSymbol",
methodType(void.class, Addressable.class));
MH_ALLOCATE_RETURN_BUFFER = lookup.findStatic(ProgrammableInvoker.class, "allocateReturnBuffer",
methodType(MemorySegment.class, Binding.Context.class, long.class));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
@ -90,17 +76,15 @@ public class ProgrammableInvoker {
public ProgrammableInvoker(ABIDescriptor abi, CallingSequence callingSequence) {
this.abi = abi;
assert callingSequence.forDowncall();
this.callingSequence = callingSequence;
}
public MethodHandle getBoundMethodHandle() {
Binding.VMStore[] argMoves = argMoveBindingsStream(callingSequence).toArray(Binding.VMStore[]::new);
Class<?>[] argMoveTypes = Arrays.stream(argMoves).map(Binding.VMStore::type).toArray(Class<?>[]::new);
Binding.VMLoad[] retMoves = retMoveBindings(callingSequence);
Class<?> returnType = retMoves.length == 1 ? retMoves[0].type() : void.class;
MethodType leafType = methodType(returnType, argMoveTypes);
MethodType leafType = callingSequence.calleeMethodType();
NativeEntryPoint nep = NativeEntryPoint.make(
abi,
@ -112,14 +96,14 @@ public class ProgrammableInvoker {
MethodHandle handle = JLIA.nativeMethodHandle(nep);
if (USE_SPEC) {
handle = specialize(handle);
handle = BindingSpecializer.specialize(handle, callingSequence, abi);
} else {
Map<VMStorage, Integer> argIndexMap = SharedUtils.indexMap(argMoves);
Map<VMStorage, Integer> retIndexMap = SharedUtils.indexMap(retMoves);
InvocationData invData = new InvocationData(handle, argIndexMap, retIndexMap);
handle = insertArguments(MH_INVOKE_INTERP_BINDINGS.bindTo(this), 2, invData);
MethodType interpType = callingSequence.methodType();
MethodType interpType = callingSequence.callerMethodType();
if (callingSequence.needsReturnBuffer()) {
// Return buffer is supplied by invokeInterpBindings
assert interpType.parameterType(0) == MemorySegment.class;
@ -139,10 +123,6 @@ public class ProgrammableInvoker {
return handle;
}
private static MemorySegment allocateReturnBuffer(Binding.Context context, long size) {
return context.allocator().allocate(size);
}
// Funnel from type to Object[]
private static MethodHandle makeCollectorHandle(MethodType type) {
return type.parameterCount() == 0
@ -172,97 +152,6 @@ public class ProgrammableInvoker {
return Arrays.stream(moves).map(Binding.Move::storage).toArray(VMStorage[]::new);
}
private MethodHandle specialize(MethodHandle leafHandle) {
MethodType highLevelType = callingSequence.methodType();
int argInsertPos = 0;
int argContextPos = 0;
MethodHandle specializedHandle = dropArguments(leafHandle, argContextPos, Binding.Context.class);
for (int i = 0; i < highLevelType.parameterCount(); i++) {
List<Binding> bindings = callingSequence.argumentBindings(i);
argInsertPos += ((int) bindings.stream().filter(Binding.VMStore.class::isInstance).count()) + 1;
// We interpret the bindings in reverse since we have to construct a MethodHandle from the bottom up
for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j);
if (binding.tag() == Binding.Tag.VM_STORE) {
argInsertPos--;
} else {
specializedHandle = binding.specialize(specializedHandle, argInsertPos, argContextPos);
}
}
}
if (highLevelType.returnType() != void.class) {
MethodHandle returnFilter = identity(highLevelType.returnType());
int retBufPos = -1;
long retBufReadOffset = -1;
int retContextPos = 0;
int retInsertPos = 1;
if (callingSequence.needsReturnBuffer()) {
retBufPos = 0;
retBufReadOffset = callingSequence.returnBufferSize();
retContextPos++;
retInsertPos++;
returnFilter = dropArguments(returnFilter, retBufPos, MemorySegment.class);
}
returnFilter = dropArguments(returnFilter, retContextPos, Binding.Context.class);
List<Binding> bindings = callingSequence.returnBindings();
for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j);
if (callingSequence.needsReturnBuffer() && binding.tag() == Binding.Tag.VM_LOAD) {
// spacial case this, since we need to update retBufReadOffset as well
Binding.VMLoad load = (Binding.VMLoad) binding;
ValueLayout layout = MemoryLayout.valueLayout(load.type(), ByteOrder.nativeOrder()).withBitAlignment(8);
// since we iterate the bindings in reverse, we have to compute the offset in reverse as well
retBufReadOffset -= abi.arch.typeSize(load.storage().type());
MethodHandle loadHandle = MethodHandles.insertCoordinates(MethodHandles.memorySegmentViewVarHandle(layout), 1, retBufReadOffset)
.toMethodHandle(VarHandle.AccessMode.GET);
returnFilter = MethodHandles.collectArguments(returnFilter, retInsertPos, loadHandle);
assert returnFilter.type().parameterType(retInsertPos - 1) == MemorySegment.class;
assert returnFilter.type().parameterType(retInsertPos - 2) == MemorySegment.class;
returnFilter = SharedUtils.mergeArguments(returnFilter, retBufPos, retInsertPos);
// to (... MemorySegment, MemorySegment, <primitive>, ...)
// from (... MemorySegment, MemorySegment, ...)
retInsertPos -= 2; // set insert pos back to the first MS (later DUP binding will merge the 2 MS)
} else {
returnFilter = binding.specialize(returnFilter, retInsertPos, retContextPos);
if (callingSequence.needsReturnBuffer() && binding.tag() == Binding.Tag.BUFFER_STORE) {
// from (... MemorySegment, ...)
// to (... MemorySegment, MemorySegment, <primitive>, ...)
retInsertPos += 2; // set insert pos to <primitive>
assert returnFilter.type().parameterType(retInsertPos - 1) == MemorySegment.class;
assert returnFilter.type().parameterType(retInsertPos - 2) == MemorySegment.class;
}
}
}
// (R, Context (ret)) -> (MemorySegment?, Context (ret), MemorySegment?, Context (arg), ...)
specializedHandle = MethodHandles.collectArguments(returnFilter, retInsertPos, specializedHandle);
if (callingSequence.needsReturnBuffer()) {
// (MemorySegment, Context (ret), Context (arg), MemorySegment, ...) -> (MemorySegment, Context (ret), Context (arg), ...)
specializedHandle = SharedUtils.mergeArguments(specializedHandle, retBufPos, retBufPos + 3);
// allocate the return buffer from the binding context, and then merge the 2 allocator args
MethodHandle retBufAllocHandle = MethodHandles.insertArguments(MH_ALLOCATE_RETURN_BUFFER, 1, callingSequence.returnBufferSize());
// (MemorySegment, Context (ret), Context (arg), ...) -> (Context (arg), Context (ret), Context (arg), ...)
specializedHandle = MethodHandles.filterArguments(specializedHandle, retBufPos, retBufAllocHandle);
// (Context (arg), Context (ret), Context (arg), ...) -> (Context (ret), Context (arg), ...)
specializedHandle = SharedUtils.mergeArguments(specializedHandle, argContextPos + 1, retBufPos); // +1 to skip return context
}
// (Context (ret), Context (arg), ...) -> (SegmentAllocator, Context (arg), ...)
specializedHandle = MethodHandles.filterArguments(specializedHandle, 0, MH_WRAP_ALLOCATOR);
} else {
specializedHandle = MethodHandles.dropArguments(specializedHandle, 0, SegmentAllocator.class);
}
// now bind the internal context parameter
argContextPos++; // skip over the return SegmentAllocator (inserted by the above code)
specializedHandle = SharedUtils.wrapWithAllocator(specializedHandle, argContextPos, callingSequence.allocationSize(), false);
return specializedHandle;
}
private record InvocationData(MethodHandle leaf, Map<VMStorage, Integer> argIndexMap, Map<VMStorage, Integer> retIndexMap) {}
Object invokeInterpBindings(SegmentAllocator allocator, Object[] args, InvocationData invData) throws Throwable {

View File

@ -27,24 +27,20 @@ package jdk.internal.foreign.abi;
import sun.security.action.GetPropertyAction;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodHandles.exactInvoker;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.lookup;
import static java.lang.invoke.MethodType.methodType;
import static jdk.internal.foreign.abi.SharedUtils.mergeArguments;
import static sun.security.action.GetBooleanAction.privilegedGetProperty;
public class ProgrammableUpcallHandler {
@ -66,21 +62,20 @@ public class ProgrammableUpcallHandler {
}
public static MemorySegment make(ABIDescriptor abi, MethodHandle target, CallingSequence callingSequence, MemorySession session) {
assert callingSequence.forUpcall();
Binding.VMLoad[] argMoves = argMoveBindings(callingSequence);
Binding.VMStore[] retMoves = retMoveBindings(callingSequence);
Class<?> llReturn = retMoves.length == 1 ? retMoves[0].type() : void.class;
Class<?>[] llParams = Arrays.stream(argMoves).map(Binding.Move::type).toArray(Class<?>[]::new);
MethodType llType = methodType(llReturn, llParams);
MethodType llType = callingSequence.callerMethodType();
MethodHandle doBindings;
if (USE_SPEC) {
doBindings = specializedBindingHandle(target, callingSequence, llReturn, abi);
doBindings = BindingSpecializer.specialize(target, callingSequence, abi);
assert doBindings.type() == llType;
} else {
Map<VMStorage, Integer> argIndices = SharedUtils.indexMap(argMoves);
Map<VMStorage, Integer> retIndices = SharedUtils.indexMap(retMoves);
int spreaderCount = callingSequence.methodType().parameterCount();
int spreaderCount = callingSequence.calleeMethodType().parameterCount();
if (callingSequence.needsReturnBuffer()) {
spreaderCount--; // return buffer is dropped from the argument list
}
@ -125,68 +120,6 @@ public class ProgrammableUpcallHandler {
.toArray(Binding.VMStore[]::new);
}
private static MethodHandle specializedBindingHandle(MethodHandle target, CallingSequence callingSequence,
Class<?> llReturn, ABIDescriptor abi) {
MethodType highLevelType = callingSequence.methodType();
MethodHandle specializedHandle = target; // initial
// we handle returns first since IMR adds an extra parameter that needs to be specialized as well
if (llReturn != void.class || callingSequence.needsReturnBuffer()) {
int retAllocatorPos = -1; // assumed not needed
int retInsertPos;
MethodHandle filter;
if (callingSequence.needsReturnBuffer()) {
retInsertPos = 1;
filter = empty(methodType(void.class, MemorySegment.class));
} else {
retInsertPos = 0;
filter = identity(llReturn);
}
long retBufWriteOffset = callingSequence.returnBufferSize();
List<Binding> bindings = callingSequence.returnBindings();
for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j);
if (callingSequence.needsReturnBuffer() && binding.tag() == Binding.Tag.VM_STORE) {
Binding.VMStore store = (Binding.VMStore) binding;
ValueLayout layout = MemoryLayout.valueLayout(store.type(), ByteOrder.nativeOrder()).withBitAlignment(8);
// since we iterate the bindings in reverse, we have to compute the offset in reverse as well
retBufWriteOffset -= abi.arch.typeSize(store.storage().type());
MethodHandle storeHandle = MethodHandles.insertCoordinates(MethodHandles.memorySegmentViewVarHandle(layout), 1, retBufWriteOffset)
.toMethodHandle(VarHandle.AccessMode.SET);
filter = collectArguments(filter, retInsertPos, storeHandle);
filter = mergeArguments(filter, retInsertPos - 1, retInsertPos);
} else {
filter = binding.specialize(filter, retInsertPos, retAllocatorPos);
}
}
specializedHandle = collectArguments(filter, retInsertPos, specializedHandle);
}
int argAllocatorPos = 0;
int argInsertPos = 1;
specializedHandle = dropArguments(specializedHandle, argAllocatorPos, Binding.Context.class);
for (int i = 0; i < highLevelType.parameterCount(); i++) {
MethodHandle filter = identity(highLevelType.parameterType(i));
int filterAllocatorPos = 0;
int filterInsertPos = 1; // +1 for allocator
filter = dropArguments(filter, filterAllocatorPos, Binding.Context.class);
List<Binding> bindings = callingSequence.argumentBindings(i);
for (int j = bindings.size() - 1; j >= 0; j--) {
Binding binding = bindings.get(j);
filter = binding.specialize(filter, filterInsertPos, filterAllocatorPos);
}
specializedHandle = MethodHandles.collectArguments(specializedHandle, argInsertPos, filter);
specializedHandle = mergeArguments(specializedHandle, argAllocatorPos, argInsertPos + filterAllocatorPos);
argInsertPos += filter.type().parameterCount() - 1; // -1 for allocator
}
specializedHandle = SharedUtils.wrapWithAllocator(specializedHandle, argAllocatorPos, callingSequence.allocationSize(), true);
return specializedHandle;
}
private record InvocationData(MethodHandle leaf,
Map<VMStorage, Integer> argIndexMap,
Map<VMStorage, Integer> retIndexMap,
@ -197,10 +130,10 @@ public class ProgrammableUpcallHandler {
private static Object invokeInterpBindings(Object[] lowLevelArgs, InvocationData invData) throws Throwable {
Binding.Context allocator = invData.callingSequence.allocationSize() != 0
? Binding.Context.ofBoundedAllocator(invData.callingSequence.allocationSize())
: Binding.Context.ofScope();
: Binding.Context.ofSession();
try (allocator) {
/// Invoke interpreter, got array of high-level arguments back
Object[] highLevelArgs = new Object[invData.callingSequence.methodType().parameterCount()];
Object[] highLevelArgs = new Object[invData.callingSequence.calleeMethodType().parameterCount()];
for (int i = 0; i < highLevelArgs.length; i++) {
highLevelArgs[i] = BindingInterpreter.box(invData.callingSequence.argumentBindings(i),
(storage, type) -> lowLevelArgs[invData.argIndexMap.get(storage)], allocator);

View File

@ -24,6 +24,18 @@
*/
package jdk.internal.foreign.abi;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.CABI;
import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.Scoped;
import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
import java.lang.foreign.Addressable;
import java.lang.foreign.Linker;
import java.lang.foreign.FunctionDescriptor;
@ -36,55 +48,22 @@ import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.VaList;
import java.lang.foreign.ValueLayout;
import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker;
import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker;
import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.foreign.MemorySessionImpl;
import jdk.internal.foreign.Scoped;
import jdk.internal.foreign.CABI;
import jdk.internal.foreign.MemoryAddressImpl;
import jdk.internal.foreign.Utils;
import jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker;
import jdk.internal.vm.annotation.ForceInline;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.lang.ref.Reference;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static java.lang.invoke.MethodHandles.collectArguments;
import static java.lang.invoke.MethodHandles.constant;
import static java.lang.invoke.MethodHandles.dropArguments;
import static java.lang.invoke.MethodHandles.dropReturn;
import static java.lang.invoke.MethodHandles.empty;
import static java.lang.invoke.MethodHandles.foldArguments;
import static java.lang.invoke.MethodHandles.identity;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodHandles.permuteArguments;
import static java.lang.invoke.MethodHandles.tryFinally;
import static java.lang.foreign.ValueLayout.*;
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.methodType;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_CHAR;
import static java.lang.foreign.ValueLayout.JAVA_DOUBLE;
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;
public class SharedUtils {
@ -94,13 +73,7 @@ public class SharedUtils {
private static final MethodHandle MH_ALLOC_BUFFER;
private static final MethodHandle MH_BASEADDRESS;
private static final MethodHandle MH_BUFFER_COPY;
private static final MethodHandle MH_MAKE_CONTEXT_NO_ALLOCATOR;
private static final MethodHandle MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR;
private static final MethodHandle MH_CLOSE_CONTEXT;
private static final MethodHandle MH_REACHBILITY_FENCE;
private static final MethodHandle MH_HANDLE_UNCAUGHT_EXCEPTION;
private static final MethodHandle ACQUIRE_MH;
private static final MethodHandle RELEASE_MH;
static {
try {
@ -111,20 +84,8 @@ public class SharedUtils {
methodType(MemoryAddress.class));
MH_BUFFER_COPY = lookup.findStatic(SharedUtils.class, "bufferCopy",
methodType(MemoryAddress.class, MemoryAddress.class, MemorySegment.class));
MH_MAKE_CONTEXT_NO_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofScope",
methodType(Binding.Context.class));
MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR = lookup.findStatic(Binding.Context.class, "ofBoundedAllocator",
methodType(Binding.Context.class, long.class));
MH_CLOSE_CONTEXT = lookup.findVirtual(Binding.Context.class, "close",
methodType(void.class));
MH_REACHBILITY_FENCE = lookup.findStatic(Reference.class, "reachabilityFence",
methodType(void.class, Object.class));
MH_HANDLE_UNCAUGHT_EXCEPTION = lookup.findStatic(SharedUtils.class, "handleUncaughtException",
methodType(void.class, Throwable.class));
ACQUIRE_MH = MethodHandles.lookup().findStatic(SharedUtils.class, "acquire",
MethodType.methodType(void.class, Scoped[].class));
RELEASE_MH = MethodHandles.lookup().findStatic(SharedUtils.class, "release",
MethodType.methodType(void.class, Scoped[].class));
} catch (ReflectiveOperationException e) {
throw new BootstrapMethodError(e);
}
@ -235,6 +196,10 @@ public class SharedUtils {
if (dropReturn) { // no handling for return value, need to drop it
target = dropReturn(target);
} else {
// adjust return type so it matches the inferred type of the effective
// function descriptor
target = target.asType(target.type().changeReturnType(Addressable.class));
}
return target;
@ -349,186 +314,6 @@ public class SharedUtils {
}
}
static MethodHandle wrapWithAllocator(MethodHandle specializedHandle,
int allocatorPos, long allocationSize,
boolean upcall) {
// insert try-finally to close the NativeScope used for Binding.Copy
MethodHandle closer;
int insertPos;
if (specializedHandle.type().returnType() == void.class) {
if (!upcall) {
closer = empty(methodType(void.class, Throwable.class)); // (Throwable) -> void
} else {
closer = MH_HANDLE_UNCAUGHT_EXCEPTION;
}
insertPos = 1;
} else {
closer = identity(specializedHandle.type().returnType()); // (V) -> V
if (!upcall) {
closer = dropArguments(closer, 0, Throwable.class); // (Throwable, V) -> V
} else {
closer = collectArguments(closer, 0, MH_HANDLE_UNCAUGHT_EXCEPTION); // (Throwable, V) -> V
}
insertPos = 2;
}
// downcalls get the leading SegmentAllocator param as well
if (!upcall) {
closer = dropArguments(closer, insertPos++, SegmentAllocator.class); // (Throwable, V?, SegmentAllocator, Addressable) -> V/void
}
closer = collectArguments(closer, insertPos, MH_CLOSE_CONTEXT); // (Throwable, V?, SegmentAllocator?, BindingContext) -> V/void
MethodHandle contextFactory;
if (allocationSize > 0) {
contextFactory = MethodHandles.insertArguments(MH_MAKE_CONTEXT_BOUNDED_ALLOCATOR, 0, allocationSize);
} else if (upcall) {
contextFactory = MH_MAKE_CONTEXT_NO_ALLOCATOR;
} else {
// this path is probably never used now, since ProgrammableInvoker never calls this routine with bufferCopySize == 0
contextFactory = constant(Binding.Context.class, Binding.Context.DUMMY);
}
specializedHandle = tryFinally(specializedHandle, closer);
specializedHandle = collectArguments(specializedHandle, allocatorPos, contextFactory);
return specializedHandle;
}
@ForceInline
@SuppressWarnings("fallthrough")
public static void acquire(Scoped[] args) {
MemorySessionImpl scope4 = null;
MemorySessionImpl scope3 = null;
MemorySessionImpl scope2 = null;
MemorySessionImpl scope1 = null;
MemorySessionImpl scope0 = null;
switch (args.length) {
default:
// slow path, acquire all remaining addressable parameters in isolation
for (int i = 5 ; i < args.length ; i++) {
acquire(args[i].sessionImpl());
}
// fast path, acquire only scopes not seen in other parameters
case 5:
scope4 = args[4].sessionImpl();
acquire(scope4);
case 4:
scope3 = args[3].sessionImpl();
if (scope3 != scope4)
acquire(scope3);
case 3:
scope2 = args[2].sessionImpl();
if (scope2 != scope3 && scope2 != scope4)
acquire(scope2);
case 2:
scope1 = args[1].sessionImpl();
if (scope1 != scope2 && scope1 != scope3 && scope1 != scope4)
acquire(scope1);
case 1:
scope0 = args[0].sessionImpl();
if (scope0 != scope1 && scope0 != scope2 && scope0 != scope3 && scope0 != scope4)
acquire(scope0);
case 0: break;
}
}
@ForceInline
@SuppressWarnings("fallthrough")
public static void release(Scoped[] args) {
MemorySessionImpl scope4 = null;
MemorySessionImpl scope3 = null;
MemorySessionImpl scope2 = null;
MemorySessionImpl scope1 = null;
MemorySessionImpl scope0 = null;
switch (args.length) {
default:
// slow path, release all remaining addressable parameters in isolation
for (int i = 5 ; i < args.length ; i++) {
release(args[i].sessionImpl());
}
// fast path, release only scopes not seen in other parameters
case 5:
scope4 = args[4].sessionImpl();
release(scope4);
case 4:
scope3 = args[3].sessionImpl();
if (scope3 != scope4)
release(scope3);
case 3:
scope2 = args[2].sessionImpl();
if (scope2 != scope3 && scope2 != scope4)
release(scope2);
case 2:
scope1 = args[1].sessionImpl();
if (scope1 != scope2 && scope1 != scope3 && scope1 != scope4)
release(scope1);
case 1:
scope0 = args[0].sessionImpl();
if (scope0 != scope1 && scope0 != scope2 && scope0 != scope3 && scope0 != scope4)
release(scope0);
case 0: break;
}
}
@ForceInline
private static void acquire(MemorySessionImpl session) {
session.acquire0();
}
@ForceInline
private static void release(MemorySessionImpl session) {
session.release0();
}
/*
* This method adds a try/finally block to a downcall method handle, to make sure that all by-reference
* parameters (including the target address of the native function) are kept alive for the duration of
* the downcall.
*/
public static MethodHandle wrapDowncall(MethodHandle downcallHandle, FunctionDescriptor descriptor) {
boolean hasReturn = descriptor.returnLayout().isPresent();
MethodHandle tryBlock = downcallHandle;
MethodHandle cleanup = hasReturn ?
MethodHandles.identity(downcallHandle.type().returnType()) :
MethodHandles.empty(MethodType.methodType(void.class));
int addressableCount = 0;
List<UnaryOperator<MethodHandle>> adapters = new ArrayList<>();
for (int i = 0 ; i < downcallHandle.type().parameterCount() ; i++) {
Class<?> ptype = downcallHandle.type().parameterType(i);
if (ptype == Addressable.class) {
addressableCount++;
} else {
int pos = i;
adapters.add(mh -> dropArguments(mh, pos, ptype));
}
}
if (addressableCount > 0) {
cleanup = dropArguments(cleanup, 0, Throwable.class);
MethodType adapterType = MethodType.methodType(void.class);
for (int i = 0 ; i < addressableCount ; i++) {
adapterType = adapterType.appendParameterTypes(Addressable.class);
}
MethodHandle acquireHandle = ACQUIRE_MH.asCollector(Scoped[].class, addressableCount).asType(adapterType);
MethodHandle releaseHandle = RELEASE_MH.asCollector(Scoped[].class, addressableCount).asType(adapterType);
for (UnaryOperator<MethodHandle> adapter : adapters) {
acquireHandle = adapter.apply(acquireHandle);
releaseHandle = adapter.apply(releaseHandle);
}
tryBlock = foldArguments(tryBlock, acquireHandle);
cleanup = collectArguments(cleanup, hasReturn ? 2 : 1, releaseHandle);
return tryFinally(tryBlock, cleanup);
} else {
return downcallHandle;
}
}
public static void checkExceptions(MethodHandle target) {
Class<?>[] exceptions = JLIA.exceptionTypes(target);
if (exceptions != null && exceptions.length != 0) {

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.foreign.abi;
import java.lang.ref.SoftReference;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
class SoftReferenceCache<K, V> {
private final Map<K, Node> cache = new ConcurrentHashMap<>();
public V get(K key, Function<K, V> valueFactory) {
return cache
.computeIfAbsent(key, k -> new Node()) // short lock (has to be according to ConcurrentHashMap)
.get(key, valueFactory); // long lock, but just for the particular key
}
private class Node {
private volatile SoftReference<V> ref;
public Node() {
}
public V get(K key, Function<K, V> valueFactory) {
V result;
if (ref == null || (result = ref.get()) == null) {
synchronized (this) { // don't let threads race on the valueFactory::apply call
if (ref == null || (result = ref.get()) == null) {
result = valueFactory.apply(key); // keep alive
ref = new SoftReference<>(result);
}
}
}
return result;
}
}
}

View File

@ -25,33 +25,25 @@
*/
package jdk.internal.foreign.abi.aarch64.linux;
import java.lang.foreign.Linker;
import jdk.internal.foreign.abi.AbstractLinker;
import jdk.internal.foreign.abi.aarch64.CallArranger;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.foreign.VaList;
import jdk.internal.foreign.SystemLookup;
import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.foreign.abi.aarch64.CallArranger;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
/**
* ABI implementation based on ARM document "Procedure Call Standard for
* the ARM 64-bit Architecture".
*/
public final class LinuxAArch64Linker implements Linker {
public final class LinuxAArch64Linker extends AbstractLinker {
private static LinuxAArch64Linker instance;
static final long ADDRESS_SIZE = 64; // bits
public static LinuxAArch64Linker getInstance() {
if (instance == null) {
instance = new LinuxAArch64Linker();
@ -60,25 +52,13 @@ public final class LinuxAArch64Linker implements Linker {
}
@Override
public final MethodHandle downcallHandle(FunctionDescriptor function) {
Objects.requireNonNull(function);
MethodType type = SharedUtils.inferMethodType(function, false);
MethodHandle handle = CallArranger.LINUX.arrangeDowncall(type, function);
handle = SharedUtils.maybeInsertAllocator(handle);
return SharedUtils.wrapDowncall(handle, function);
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function) {
return CallArranger.LINUX.arrangeDowncall(inferredMethodType, function);
}
@Override
public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, MemorySession session) {
Objects.requireNonNull(session);
Objects.requireNonNull(target);
Objects.requireNonNull(function);
SharedUtils.checkExceptions(target);
MethodType type = SharedUtils.inferMethodType(function, true);
if (!type.equals(target.type())) {
throw new IllegalArgumentException("Wrong method handle type: " + target.type());
}
return CallArranger.LINUX.arrangeUpcall(target, target.type(), function, session);
protected MemorySegment arrangeUpcall(MethodHandle target, MethodType targetType, FunctionDescriptor function, MemorySession scope) {
return CallArranger.LINUX.arrangeUpcall(target, targetType, function, scope);
}
public static VaList newVaList(Consumer<VaList.Builder> actions, MemorySession session) {
@ -94,9 +74,4 @@ public final class LinuxAArch64Linker implements Linker {
public static VaList emptyVaList() {
return LinuxAArch64VaList.empty();
}
@Override
public SystemLookup defaultLookup() {
return SystemLookup.getInstance();
}
}

View File

@ -25,33 +25,25 @@
*/
package jdk.internal.foreign.abi.aarch64.macos;
import java.lang.foreign.Linker;
import jdk.internal.foreign.abi.AbstractLinker;
import jdk.internal.foreign.abi.aarch64.CallArranger;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.foreign.VaList;
import jdk.internal.foreign.SystemLookup;
import jdk.internal.foreign.abi.SharedUtils;
import jdk.internal.foreign.abi.aarch64.CallArranger;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
/**
* ABI implementation for macOS on Apple silicon. Based on AAPCS with
* changes to va_list and passing arguments on the stack.
*/
public final class MacOsAArch64Linker implements Linker {
public final class MacOsAArch64Linker extends AbstractLinker {
private static MacOsAArch64Linker instance;
static final long ADDRESS_SIZE = 64; // bits
public static MacOsAArch64Linker getInstance() {
if (instance == null) {
instance = new MacOsAArch64Linker();
@ -60,24 +52,13 @@ public final class MacOsAArch64Linker implements Linker {
}
@Override
public final MethodHandle downcallHandle(FunctionDescriptor function) {
Objects.requireNonNull(function);
MethodType type = SharedUtils.inferMethodType(function, false);
MethodHandle handle = CallArranger.MACOS.arrangeDowncall(type, function);
handle = SharedUtils.maybeInsertAllocator(handle);
return SharedUtils.wrapDowncall(handle, function);
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function) {
return CallArranger.MACOS.arrangeDowncall(inferredMethodType, function);
}
@Override
public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, MemorySession session) {
Objects.requireNonNull(session);
Objects.requireNonNull(target);
Objects.requireNonNull(function);
MethodType type = SharedUtils.inferMethodType(function, true);
if (!type.equals(target.type())) {
throw new IllegalArgumentException("Wrong method handle type: " + target.type());
}
return CallArranger.MACOS.arrangeUpcall(target, target.type(), function, session);
protected MemorySegment arrangeUpcall(MethodHandle target, MethodType targetType, FunctionDescriptor function, MemorySession scope) {
return CallArranger.MACOS.arrangeUpcall(target, targetType, function, scope);
}
public static VaList newVaList(Consumer<VaList.Builder> actions, MemorySession session) {
@ -93,9 +74,4 @@ public final class MacOsAArch64Linker implements Linker {
public static VaList emptyVaList() {
return MacOsAArch64VaList.empty();
}
@Override
public SystemLookup defaultLookup() {
return SystemLookup.getInstance();
}
}

View File

@ -57,6 +57,8 @@ import static jdk.internal.foreign.abi.x64.X86_64Architecture.*;
* This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns.
*/
public class CallArranger {
public static final int MAX_INTEGER_ARGUMENT_REGISTERS = 6;
public static final int MAX_VECTOR_ARGUMENT_REGISTERS = 8;
private static final ABIDescriptor CSysV = abiFor(
new VMStorage[] { rdi, rsi, rdx, rcx, r8, r9, rax },
new VMStorage[] { xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7 },
@ -159,8 +161,8 @@ public class CallArranger {
private int maxRegisterArguments(int type) {
return type == StorageClasses.INTEGER ?
SysVx64Linker.MAX_INTEGER_ARGUMENT_REGISTERS :
SysVx64Linker.MAX_VECTOR_ARGUMENT_REGISTERS;
MAX_INTEGER_ARGUMENT_REGISTERS :
MAX_VECTOR_ARGUMENT_REGISTERS;
}
VMStorage stackAlloc() {
@ -188,14 +190,14 @@ public class CallArranger {
}
long nIntegerReg = typeClass.nIntegerRegs();
if (this.nIntegerReg + nIntegerReg > SysVx64Linker.MAX_INTEGER_ARGUMENT_REGISTERS) {
if (this.nIntegerReg + nIntegerReg > MAX_INTEGER_ARGUMENT_REGISTERS) {
//not enough registers - pass on stack
return typeClass.classes.stream().map(c -> stackAlloc()).toArray(VMStorage[]::new);
}
long nVectorReg = typeClass.nVectorRegs();
if (this.nVectorReg + nVectorReg > SysVx64Linker.MAX_VECTOR_ARGUMENT_REGISTERS) {
if (this.nVectorReg + nVectorReg > MAX_VECTOR_ARGUMENT_REGISTERS) {
//not enough registers - pass on stack
return typeClass.classes.stream().map(c -> stackAlloc()).toArray(VMStorage[]::new);
}

View File

@ -25,36 +25,23 @@
package jdk.internal.foreign.abi.x64.sysv;
import java.lang.foreign.Linker;
import jdk.internal.foreign.abi.AbstractLinker;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.foreign.VaList;
import jdk.internal.foreign.SystemLookup;
import jdk.internal.foreign.abi.SharedUtils;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
/**
* ABI implementation based on System V ABI AMD64 supplement v.0.99.6
*/
public final class SysVx64Linker implements Linker {
public static final int MAX_INTEGER_ARGUMENT_REGISTERS = 6;
public static final int MAX_INTEGER_RETURN_REGISTERS = 2;
public static final int MAX_VECTOR_ARGUMENT_REGISTERS = 8;
public static final int MAX_VECTOR_RETURN_REGISTERS = 2;
public static final int MAX_X87_RETURN_REGISTERS = 2;
public final class SysVx64Linker extends AbstractLinker {
private static SysVx64Linker instance;
static final long ADDRESS_SIZE = 64; // bits
public static SysVx64Linker getInstance() {
if (instance == null) {
instance = new SysVx64Linker();
@ -62,34 +49,22 @@ public final class SysVx64Linker implements Linker {
return instance;
}
public static VaList newVaList(Consumer<VaList.Builder> actions, MemorySession session) {
SysVVaList.Builder builder = SysVVaList.builder(session);
@Override
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function) {
return CallArranger.arrangeDowncall(inferredMethodType, function);
}
@Override
protected MemorySegment arrangeUpcall(MethodHandle target, MethodType targetType, FunctionDescriptor function, MemorySession scope) {
return CallArranger.arrangeUpcall(target, targetType, function, scope);
}
public static VaList newVaList(Consumer<VaList.Builder> actions, MemorySession scope) {
SysVVaList.Builder builder = SysVVaList.builder(scope);
actions.accept(builder);
return builder.build();
}
@Override
public final MethodHandle downcallHandle(FunctionDescriptor function) {
Objects.requireNonNull(function);
MethodType type = SharedUtils.inferMethodType(function, false);
MethodHandle handle = CallArranger.arrangeDowncall(type, function);
handle = SharedUtils.maybeInsertAllocator(handle);
return SharedUtils.wrapDowncall(handle, function);
}
@Override
public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, MemorySession session) {
Objects.requireNonNull(session);
Objects.requireNonNull(target);
Objects.requireNonNull(function);
SharedUtils.checkExceptions(target);
MethodType type = SharedUtils.inferMethodType(function, true);
if (!type.equals(target.type())) {
throw new IllegalArgumentException("Wrong method handle type: " + target.type());
}
return CallArranger.arrangeUpcall(target, target.type(), function, session);
}
public static VaList newVaListOfAddress(MemoryAddress ma, MemorySession session) {
return SysVVaList.ofAddress(ma, session);
}
@ -97,9 +72,4 @@ public final class SysVx64Linker implements Linker {
public static VaList emptyVaList() {
return SysVVaList.empty();
}
@Override
public SystemLookup defaultLookup() {
return SystemLookup.getInstance();
}
}

View File

@ -56,6 +56,7 @@ import static jdk.internal.foreign.abi.x64.X86_64Architecture.*;
* This includes taking care of synthetic arguments like pointers to return buffers for 'in-memory' returns.
*/
public class CallArranger {
public static final int MAX_REGISTER_ARGUMENTS = 4;
private static final int STACK_SLOT_SIZE = 8;
private static final ABIDescriptor CWindows = X86_64Architecture.abiFor(
@ -160,7 +161,7 @@ public class CallArranger {
}
VMStorage nextStorage(int type, MemoryLayout layout) {
if (nRegs >= Windowsx64Linker.MAX_REGISTER_ARGUMENTS) {
if (nRegs >= MAX_REGISTER_ARGUMENTS) {
assert forArguments : "no stack returns";
// stack
long alignment = Math.max(SharedUtils.alignment(layout, true), STACK_SLOT_SIZE);

View File

@ -24,39 +24,23 @@
*/
package jdk.internal.foreign.abi.x64.windows;
import java.lang.foreign.Linker;
import jdk.internal.foreign.abi.AbstractLinker;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.foreign.VaList;
import jdk.internal.foreign.SystemLookup;
import jdk.internal.foreign.abi.SharedUtils;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
/**
* ABI implementation based on Windows ABI AMD64 supplement v.0.99.6
*/
public final class Windowsx64Linker implements Linker {
public static final int MAX_INTEGER_ARGUMENT_REGISTERS = 4;
public static final int MAX_INTEGER_RETURN_REGISTERS = 1;
public static final int MAX_VECTOR_ARGUMENT_REGISTERS = 4;
public static final int MAX_VECTOR_RETURN_REGISTERS = 1;
public static final int MAX_REGISTER_ARGUMENTS = 4;
public static final int MAX_REGISTER_RETURNS = 1;
public final class Windowsx64Linker extends AbstractLinker {
private static Windowsx64Linker instance;
static final long ADDRESS_SIZE = 64; // bits
public static Windowsx64Linker getInstance() {
if (instance == null) {
instance = new Windowsx64Linker();
@ -64,34 +48,22 @@ public final class Windowsx64Linker implements Linker {
return instance;
}
public static VaList newVaList(Consumer<VaList.Builder> actions, MemorySession session) {
WinVaList.Builder builder = WinVaList.builder(session);
@Override
protected MethodHandle arrangeDowncall(MethodType inferredMethodType, FunctionDescriptor function) {
return CallArranger.arrangeDowncall(inferredMethodType, function);
}
@Override
protected MemorySegment arrangeUpcall(MethodHandle target, MethodType targetType, FunctionDescriptor function, MemorySession scope) {
return CallArranger.arrangeUpcall(target, targetType, function, scope);
}
public static VaList newVaList(Consumer<VaList.Builder> actions, MemorySession scope) {
WinVaList.Builder builder = WinVaList.builder(scope);
actions.accept(builder);
return builder.build();
}
@Override
public final MethodHandle downcallHandle(FunctionDescriptor function) {
Objects.requireNonNull(function);
MethodType type = SharedUtils.inferMethodType(function, false);
MethodHandle handle = CallArranger.arrangeDowncall(type, function);
handle = SharedUtils.maybeInsertAllocator(handle);
return SharedUtils.wrapDowncall(handle, function);
}
@Override
public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, MemorySession session) {
Objects.requireNonNull(session);
Objects.requireNonNull(target);
Objects.requireNonNull(function);
SharedUtils.checkExceptions(target);
MethodType type = SharedUtils.inferMethodType(function, true);
if (!type.equals(target.type())) {
throw new IllegalArgumentException("Wrong method handle type: " + target.type());
}
return CallArranger.arrangeUpcall(target, target.type(), function, session);
}
public static VaList newVaListOfAddress(MemoryAddress ma, MemorySession session) {
return WinVaList.ofAddress(ma, session);
}
@ -99,9 +71,5 @@ public final class Windowsx64Linker implements Linker {
public static VaList emptyVaList() {
return WinVaList.empty();
}
@Override
public SystemLookup defaultLookup() {
return SystemLookup.getInstance();
}
}

View File

@ -32,9 +32,9 @@ import static org.testng.Assert.assertEquals;
public class CallArrangerTestBase {
public static void checkArgumentBindings(CallingSequence callingSequence, Binding[][] argumentBindings) {
assertEquals(callingSequence.argumentCount(), argumentBindings.length);
assertEquals(callingSequence.argumentBindingsCount(), argumentBindings.length);
for (int i = 0; i < callingSequence.argumentCount(); i++) {
for (int i = 0; i < callingSequence.argumentBindingsCount(); i++) {
List<Binding> actual = callingSequence.argumentBindings(i);
Binding[] expected = argumentBindings[i];
assertEquals(actual, Arrays.asList(expected));

View File

@ -63,7 +63,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -87,7 +87,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -117,7 +117,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -139,7 +139,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -199,7 +199,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -230,7 +230,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertTrue(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), MethodType.methodType(void.class, Addressable.class, MemoryAddress.class));
assertEquals(callingSequence.callerMethodType(), MethodType.methodType(void.class, Addressable.class, MemoryAddress.class));
assertEquals(callingSequence.functionDesc(), FunctionDescriptor.ofVoid(ADDRESS, C_POINTER));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -254,7 +254,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, MemorySegment.class, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, MemorySegment.class, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -283,7 +283,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, MemorySegment.class, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, MemorySegment.class, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -321,7 +321,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -375,7 +375,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -404,7 +404,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fdExpected);
// This is identical to the non-variadic calling sequence
@ -427,7 +427,7 @@ public class TestAarch64CallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fdExpected);
// The two variadic arguments should be allocated on the stack

View File

@ -33,7 +33,6 @@
* @run testng TestSysVCallArranger
*/
import java.lang.foreign.Addressable;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemoryAddress;
import java.lang.foreign.MemoryLayout;
@ -44,6 +43,7 @@ import jdk.internal.foreign.abi.x64.sysv.CallArranger;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.lang.foreign.Addressable;
import java.lang.invoke.MethodType;
import static java.lang.foreign.ValueLayout.ADDRESS;
@ -64,7 +64,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -92,7 +92,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -123,7 +123,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -153,7 +153,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -183,7 +183,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -208,7 +208,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -239,7 +239,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -274,7 +274,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -331,7 +331,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -374,7 +374,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -396,7 +396,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -455,7 +455,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, MemorySegment.class, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.appendParameterTypes(long.class).insertParameterTypes(0, MemorySegment.class, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.appendArgumentLayouts(C_LONG).insertArgumentLayouts(0, ADDRESS, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -487,7 +487,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertTrue(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), MethodType.methodType(void.class, Addressable.class, MemoryAddress.class, long.class));
assertEquals(callingSequence.callerMethodType(), MethodType.methodType(void.class, Addressable.class, MemoryAddress.class, long.class));
assertEquals(callingSequence.functionDesc(), FunctionDescriptor.ofVoid(ADDRESS, C_POINTER, C_LONG));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -511,7 +511,7 @@ public class TestSysVCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt);
assertEquals(callingSequence.calleeMethodType(), mt);
assertEquals(callingSequence.functionDesc(), fd);
checkArgumentBindings(callingSequence, new Binding[][]{

View File

@ -62,7 +62,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -79,7 +79,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -101,7 +101,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -125,7 +125,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -156,7 +156,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -193,7 +193,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fdExpected);
checkArgumentBindings(callingSequence, new Binding[][]{
@ -227,7 +227,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -257,7 +257,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -288,7 +288,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -309,7 +309,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -333,7 +333,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertTrue(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), MethodType.methodType(void.class, Addressable.class, MemoryAddress.class));
assertEquals(callingSequence.callerMethodType(), MethodType.methodType(void.class, Addressable.class, MemoryAddress.class));
assertEquals(callingSequence.functionDesc(), FunctionDescriptor.ofVoid(ADDRESS, C_POINTER));
checkArgumentBindings(callingSequence, new Binding[][]{
@ -362,7 +362,7 @@ public class TestWindowsCallArranger extends CallArrangerTestBase {
assertFalse(bindings.isInMemoryReturn);
CallingSequence callingSequence = bindings.callingSequence;
assertEquals(callingSequence.methodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.callerMethodType(), mt.insertParameterTypes(0, Addressable.class));
assertEquals(callingSequence.functionDesc(), fd.insertArgumentLayouts(0, ADDRESS));
checkArgumentBindings(callingSequence, new Binding[][]{

View File

@ -0,0 +1,71 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.bench.java.lang.foreign;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.lang.foreign.Linker;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.MemorySession;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.concurrent.TimeUnit;
import static java.lang.invoke.MethodHandles.lookup;
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Fork(value = 3, jvmArgsAppend = { "--enable-native-access=ALL-UNNAMED", "--enable-preview" })
public class LinkUpcall extends CLayouts {
static final Linker LINKER = Linker.nativeLinker();
static final MethodHandle BLANK;
static final FunctionDescriptor BLANK_DESC = FunctionDescriptor.ofVoid();
static {
try {
BLANK = lookup().findStatic(LinkUpcall.class, "blank", MethodType.methodType(void.class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
@Benchmark
public MemorySegment link_blank() {
return LINKER.upcallStub(BLANK, BLANK_DESC, MemorySession.openImplicit());
}
static void blank() {}
}