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:
parent
d8b0b32f9f
commit
ee45a0ac63
@ -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; }
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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[][]{
|
||||
|
@ -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[][]{
|
||||
|
@ -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() {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user