8254231: Implementation of Foreign Linker API (Incubator)

Reviewed-by: coleenp, ihse, dholmes, vlivanov
This commit is contained in:
Maurizio Cimadamore 2020-11-23 11:00:38 +00:00
parent 53f38353e0
commit 0fb31dbf3a
212 changed files with 67390 additions and 179 deletions

View file

@ -605,6 +605,10 @@ public abstract class MethodHandle implements Constable {
/*non-public*/
static native @PolymorphicSignature Object linkToInterface(Object... args) throws Throwable;
/** TODO */
@IntrinsicCandidate
/*non-public*/ static native @PolymorphicSignature Object linkToNative(Object... args) throws Throwable;
/**
* Performs a variable arity invocation, passing the arguments in the given array
* to the method handle, as if via an inexact {@link #invoke invoke} from a call site

View file

@ -27,6 +27,7 @@ package java.lang.invoke;
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.invoke.NativeEntryPoint;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.internal.reflect.CallerSensitive;
@ -1774,6 +1775,11 @@ abstract class MethodHandleImpl {
return VarHandles.makeMemoryAddressViewHandle(carrier, skipAlignmentMaskCheck, alignmentMask, order);
}
@Override
public MethodHandle nativeMethodHandle(NativeEntryPoint nep, MethodHandle fallback) {
return NativeMethodHandle.make(nep, fallback);
}
@Override
public VarHandle filterValue(VarHandle target, MethodHandle filterToTarget, MethodHandle filterFromTarget) {
return VarHandles.filterValue(target, filterToTarget, filterFromTarget);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2020, 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
@ -86,7 +86,8 @@ final class MethodTypeForm {
LF_TF = 18, // tryFinally
LF_LOOP = 19, // loop
LF_INVSPECIAL_IFC = 20, // DMH invokeSpecial of (private) interface method
LF_LIMIT = 21;
LF_INVNATIVE = 21, // NMH invokeNative
LF_LIMIT = 22;
/** Return the type corresponding uniquely (1-1) to this MT-form.
* It might have any primitive returns or arguments, but will have no references except Object.

View file

@ -0,0 +1,174 @@
/*
* Copyright (c) 2020, 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 java.lang.invoke;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.invoke.NativeEntryPoint;
import static java.lang.invoke.LambdaForm.*;
import static java.lang.invoke.MethodHandleNatives.Constants.LM_TRUSTED;
import static java.lang.invoke.MethodHandleNatives.Constants.REF_invokeStatic;
import static java.lang.invoke.MethodHandleStatics.newInternalError;
/**
* This class models a method handle to a native function. A native method handle is made up of a {@link NativeEntryPoint},
* which is used to capture the characteristics of the native call (such as calling convention to be used,
* or whether a native transition is required) and a <em>fallback</em> method handle, which can be used
* when intrinsification of this method handle is not possible.
*/
/*non-public*/ class NativeMethodHandle extends MethodHandle {
final NativeEntryPoint nep;
final MethodHandle fallback;
private NativeMethodHandle(MethodType type, LambdaForm form, MethodHandle fallback, NativeEntryPoint nep) {
super(type, form);
this.fallback = fallback;
this.nep = nep;
}
/**
* Creates a new native method handle with given {@link NativeEntryPoint} and <em>fallback</em> method handle.
*/
public static MethodHandle make(NativeEntryPoint nep, MethodHandle fallback) {
MethodType type = nep.type();
if (!allTypesPrimitive(type))
throw new IllegalArgumentException("Type must only contain primitives: " + type);
if (type != fallback.type())
throw new IllegalArgumentException("Type of fallback must match");
LambdaForm lform = preparedLambdaForm(type);
return new NativeMethodHandle(type, lform, fallback, nep);
}
private static boolean allTypesPrimitive(MethodType type) {
if (!type.returnType().isPrimitive())
return false;
for (Class<?> pType : type.parameterArray()) {
if (!pType.isPrimitive())
return false;
}
return true;
}
private static final MemberName.Factory IMPL_NAMES = MemberName.getFactory();
private static LambdaForm preparedLambdaForm(MethodType mtype) {
int id = MethodTypeForm.LF_INVNATIVE;
mtype = mtype.basicType();
LambdaForm lform = mtype.form().cachedLambdaForm(id);
if (lform != null) return lform;
lform = makePreparedLambdaForm(mtype);
return mtype.form().setCachedLambdaForm(id, lform);
}
private static LambdaForm makePreparedLambdaForm(MethodType mtype) {
MethodType linkerType = mtype.insertParameterTypes(0, MethodHandle.class)
.appendParameterTypes(Object.class);
MemberName linker = new MemberName(MethodHandle.class, "linkToNative", linkerType, REF_invokeStatic);
try {
linker = IMPL_NAMES.resolveOrFail(REF_invokeStatic, linker, null, LM_TRUSTED, NoSuchMethodException.class);
} catch (ReflectiveOperationException ex) {
throw newInternalError(ex);
}
final int NMH_THIS = 0;
final int ARG_BASE = 1;
final int ARG_LIMIT = ARG_BASE + mtype.parameterCount();
int nameCursor = ARG_LIMIT;
final int GET_FALLBACK = nameCursor++;
final int GET_NEP = nameCursor++;
final int LINKER_CALL = nameCursor++;
LambdaForm.Name[] names = arguments(nameCursor - ARG_LIMIT, mtype.invokerType());
assert (names.length == nameCursor);
names[GET_FALLBACK] = new LambdaForm.Name(Lazy.NF_internalFallback, names[NMH_THIS]);
names[GET_NEP] = new LambdaForm.Name(Lazy.NF_internalNativeEntryPoint, names[NMH_THIS]);
Object[] outArgs = new Object[linkerType.parameterCount()];
// Need to pass fallback here so we can call it without destroying the receiver register!!
outArgs[0] = names[GET_FALLBACK];
System.arraycopy(names, ARG_BASE, outArgs, 1, mtype.parameterCount());
outArgs[outArgs.length - 1] = names[GET_NEP];
names[LINKER_CALL] = new LambdaForm.Name(linker, outArgs);
LambdaForm lform = new LambdaForm(ARG_LIMIT, names, LAST_RESULT);
// This is a tricky bit of code. Don't send it through the LF interpreter.
lform.compileToBytecode();
return lform;
}
final
@Override
MethodHandle copyWith(MethodType mt, LambdaForm lf) {
assert (this.getClass() == NativeMethodHandle.class); // must override in subclasses
return new NativeMethodHandle(mt, lf, fallback, nep);
}
@Override
BoundMethodHandle rebind() {
return BoundMethodHandle.makeReinvoker(this);
}
@ForceInline
static Object internalNativeEntryPoint(Object mh) {
return ((NativeMethodHandle)mh).nep;
}
@ForceInline
static MethodHandle internalFallback(Object mh) {
return ((NativeMethodHandle)mh).fallback;
}
/**
* Pre-initialized NamedFunctions for bootstrapping purposes.
* Factored in an inner class to delay initialization until first usage.
*/
private static class Lazy {
static final NamedFunction
NF_internalNativeEntryPoint;
static final NamedFunction
NF_internalFallback;
static {
try {
Class<NativeMethodHandle> THIS_CLASS = NativeMethodHandle.class;
NamedFunction[] nfs = new NamedFunction[]{
NF_internalNativeEntryPoint = new NamedFunction(
THIS_CLASS.getDeclaredMethod("internalNativeEntryPoint", Object.class)),
NF_internalFallback = new NamedFunction(
THIS_CLASS.getDeclaredMethod("internalFallback", Object.class))
};
for (NamedFunction nf : nfs) {
// Each nf must be statically invocable or we get tied up in our bootstraps.
assert (InvokerBytecodeGenerator.isStaticallyInvocable(nf.member)) : nf;
nf.resolve();
}
} catch (ReflectiveOperationException ex) {
throw newInternalError(ex);
}
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2020, 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
@ -25,6 +25,8 @@
package jdk.internal.access;
import jdk.internal.invoke.NativeEntryPoint;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
@ -119,4 +121,15 @@ public interface JavaLangInvokeAccess {
* Used by {@code jdk.incubator.foreign.MemoryHandles}.
*/
VarHandle insertCoordinates(VarHandle target, int pos, Object... values);
/**
* Returns a native method handle with given arguments as fallback and steering info.
*
* Will allow JIT to intrinsify.
*
* @param nep the native entry point
* @param fallback the fallback handle
* @return the native method handle
*/
MethodHandle nativeMethodHandle(NativeEntryPoint nep, MethodHandle fallback);
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020, 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.access.foreign;
public interface NativeLibraryProxy {
long lookup(String name) throws NoSuchMethodException;
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020, 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.invoke;
public interface ABIDescriptorProxy {
int shadowSpaceBytes();
}

View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2020, 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.invoke;
import java.lang.invoke.MethodType;
import java.util.Objects;
/**
* This class describes a native call, including arguments/return shuffle moves, PC entry point and
* various other info which are relevant when the call will be intrinsified by C2.
*/
public class NativeEntryPoint {
static {
registerNatives();
}
private final long addr;
private final int shadowSpace;
// encoded as VMRegImpl*
private final long[] argMoves;
private final long[] returnMoves;
private final boolean needTransition;
private final MethodType methodType; // C2 sees erased version (byte -> int), so need this explicitly
private final String name;
private NativeEntryPoint(long addr, int shadowSpace, long[] argMoves, long[] returnMoves,
boolean needTransition, MethodType methodType, String name) {
this.addr = addr;
this.shadowSpace = shadowSpace;
this.argMoves = Objects.requireNonNull(argMoves);
this.returnMoves = Objects.requireNonNull(returnMoves);
this.needTransition = needTransition;
this.methodType = methodType;
this.name = name;
}
public static NativeEntryPoint make(long addr, String name, ABIDescriptorProxy abi, VMStorageProxy[] argMoves, VMStorageProxy[] returnMoves,
boolean needTransition, MethodType methodType) {
if (returnMoves.length > 1) {
throw new IllegalArgumentException("Multiple register return not supported");
}
return new NativeEntryPoint(
addr, abi.shadowSpaceBytes(), encodeVMStorages(argMoves), encodeVMStorages(returnMoves), needTransition, methodType, name);
}
private static long[] encodeVMStorages(VMStorageProxy[] moves) {
long[] out = new long[moves.length];
for (int i = 0; i < moves.length; i++) {
out[i] = vmStorageToVMReg(moves[i].type(), moves[i].index());
}
return out;
}
private static native long vmStorageToVMReg(int type, int index);
public MethodType type() {
return methodType;
}
private static native void registerNatives();
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020, 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.invoke;
public interface VMStorageProxy {
int type();
int index();
}

View file

@ -384,6 +384,20 @@ public final class NativeLibraries {
}
}
public static final NativeLibrary defaultLibrary = new NativeLibraryImpl(Object.class, "<default>", true, true) {
@Override
boolean open() {
throw new UnsupportedOperationException("Cannot load default library");
}
@Override
public long find(String name) {
return NativeLibraries.findEntryInProcess(name);
}
};
/*
* The run() method will be invoked when this class loader becomes
* phantom reachable to unload the native library.
@ -464,4 +478,5 @@ public final class NativeLibraries {
private static native void unload(String name, boolean isBuiltin, boolean isJNI, long handle);
private static native String findBuiltinLib(String name);
private static native long findEntry0(NativeLibraryImpl lib, String name);
private static native long findEntryInProcess(String name);
}

View file

@ -164,7 +164,8 @@ module java.base {
exports jdk.internal.loader to
java.instrument,
java.logging,
java.naming;
java.naming,
jdk.incubator.foreign;
exports jdk.internal.jmod to
jdk.compiler,
jdk.jlink;
@ -351,6 +352,8 @@ module java.base {
java.prefs;
exports sun.util.resources to
jdk.localedata;
exports jdk.internal.invoke to
jdk.incubator.foreign;
// the service types defined by the APIs in this module

View file

@ -246,6 +246,29 @@ Java_jdk_internal_loader_NativeLibraries_findEntry0
return res;
}
/*
* Class: jdk_internal_loader_NativeLibraries
* Method: findEntryInProcess
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL
Java_jdk_internal_loader_NativeLibraries_findEntryInProcess
(JNIEnv *env, jclass cls, jstring name)
{
const char *cname;
jlong res;
if (!initIDs(env))
return jlong_zero;
cname = (*env)->GetStringUTFChars(env, name, 0);
if (cname == 0)
return jlong_zero;
res = ptr_to_jlong(findEntryInProcess(cname));
(*env)->ReleaseStringUTFChars(env, name, cname);
return res;
}
/*
* Class: jdk_internal_loader_NativeLibraries
* Method: findBuiltinLib

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2020, 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
@ -333,6 +333,8 @@ JNIEXPORT void InitializeEncoding(JNIEnv *env, const char *name);
void* getProcessHandle();
void* findEntryInProcess(const char* name);
void buildJniFunctionName(const char *sym, const char *cname,
char *jniEntryName);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2020, 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
@ -26,6 +26,7 @@
#include <errno.h>
#include <string.h>
#include "jvm.h"
#include "jni.h"
#include "jni_util.h"
#include "dlfcn.h"
@ -50,6 +51,10 @@ void* getProcessHandle() {
return procHandle;
}
void* findEntryInProcess(const char* name) {
return JVM_FindLibraryEntry(RTLD_DEFAULT, name);
}
void buildJniFunctionName(const char *sym, const char *cname,
char *jniEntryName) {
strcpy(jniEntryName, sym);

View file

@ -26,6 +26,7 @@
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <psapi.h>
#include <locale.h>
#include "jni.h"
@ -35,6 +36,31 @@ void* getProcessHandle() {
return (void*)GetModuleHandle(NULL);
}
/*
* Windows doesn't have an RTLD_DEFAULT equivalent, so in stead we have to
* iterate over all the modules loaded by the process to implement the
* default library behaviour.
*/
void* findEntryInProcess(const char* name) {
HANDLE hProcess = GetCurrentProcess();
HMODULE hMods[1024];
DWORD cbNeeded; // array size in bytes
// first come, first served
if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
HMODULE mod = hMods[i];
FARPROC proc = GetProcAddress(mod, name);
if(proc != NULL) {
return proc;
}
}
}
return NULL;
}
/*
* Windows symbols can be simple like JNI_OnLoad or __stdcall format
* like _JNI_OnLoad@8. We need to handle both.