jnr.ffi.provider.jffi.NativeClosureProxy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jnr-ffi Show documentation
Show all versions of jnr-ffi Show documentation
A library for invoking native functions from java
/*
* Copyright (C) 2012 Wayne Meissner
*
* This file is part of the JNR project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jnr.ffi.provider.jffi;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.PrintWriter;
import java.lang.ref.Reference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicLong;
import jnr.ffi.NativeType;
import jnr.ffi.Pointer;
import jnr.ffi.provider.FromNativeType;
import jnr.ffi.provider.ToNativeType;
import static jnr.ffi.provider.jffi.AsmUtil.emitFromNativeConversion;
import static jnr.ffi.provider.jffi.AsmUtil.emitReturnOp;
import static jnr.ffi.provider.jffi.AsmUtil.emitToNativeConversion;
import static jnr.ffi.provider.jffi.AsmUtil.unboxNumber;
import static jnr.ffi.provider.jffi.AsmUtil.unboxedType;
import static jnr.ffi.provider.jffi.CodegenUtils.c;
import static jnr.ffi.provider.jffi.CodegenUtils.ci;
import static jnr.ffi.provider.jffi.CodegenUtils.p;
import static jnr.ffi.provider.jffi.CodegenUtils.sig;
import static jnr.ffi.provider.jffi.NumberUtil.convertPrimitive;
import static jnr.ffi.provider.jffi.NumberUtil.sizeof;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.V1_8;
/**
*
*/
public abstract class NativeClosureProxy {
protected final jnr.ffi.Runtime runtime;
volatile Reference> closureReference;
protected NativeClosureProxy(NativeRuntime runtime) {
this.runtime = runtime;
}
protected Object getCallable() {
Object callable = closureReference != null ? closureReference.get() : null;
if (callable != null) {
return callable;
}
throw new NullPointerException("callable is null");
}
static class Factory {
private final jnr.ffi.Runtime runtime;
private final Constructor extends NativeClosureProxy> constructor;
private final Object[] objectFields;
private final Method invokeMethod;
Factory(jnr.ffi.Runtime runtime, Constructor extends NativeClosureProxy> constructor,
Method invokeMethod, Object[] objectFields) {
this.runtime = runtime;
this.constructor = constructor;
this.invokeMethod = invokeMethod;
this.objectFields = objectFields;
}
NativeClosureProxy newClosureProxy() {
try {
return constructor.newInstance(runtime, objectFields);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
Method getInvokeMethod() {
return invokeMethod;
}
}
public final static boolean DEBUG = Boolean.getBoolean("jnr.ffi.compile.dump");
private static final AtomicLong nextClassID = new AtomicLong(0);
static Factory newProxyFactory(jnr.ffi.Runtime runtime, Method callMethod,
ToNativeType resultType, FromNativeType[] parameterTypes, AsmClassLoader classLoader) {
final String closureProxyClassName = p(NativeClosureProxy.class) + "$$impl$$" + nextClassID.getAndIncrement();
final ClassWriter closureClassWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
final ClassVisitor closureClassVisitor = DEBUG ? AsmUtil.newCheckClassAdapter(closureClassWriter) : closureClassWriter;
AsmBuilder builder = new AsmBuilder(runtime, closureProxyClassName, closureClassVisitor, classLoader);
closureClassVisitor.visit(V1_8, ACC_PUBLIC | ACC_FINAL, closureProxyClassName, null, p(NativeClosureProxy.class),
new String[]{});
Class[] nativeParameterClasses = new Class[parameterTypes.length];
for (int i = 0; i < parameterTypes.length; i++) {
nativeParameterClasses[i] = getNativeClass(parameterTypes[i].getNativeType());
}
Class nativeResultClass = getNativeClass(resultType.getNativeType());
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(closureClassVisitor, ACC_PUBLIC | ACC_FINAL, "invoke",
sig(nativeResultClass, nativeParameterClasses),
null, null);
mv.start();
// Cast the callable instance to the correct class
mv.aload(0);
mv.invokevirtual(NativeClosureProxy.class, "getCallable", Object.class);
mv.checkcast(p(callMethod.getDeclaringClass()));
LocalVariable[] parameterVariables = AsmUtil.getParameterVariables(nativeParameterClasses);
// Construct callback method
LocalVariableAllocator localVariableAllocator = new LocalVariableAllocator(nativeParameterClasses);
for (int i = 0; i < parameterTypes.length; ++i) {
FromNativeType parameterType = parameterTypes[i];
Class parameterClass = parameterType.effectiveJavaType();
if (!isParameterTypeSupported(parameterClass)) {
throw new IllegalArgumentException("unsupported closure parameter type " + parameterTypes[i].getDeclaredType());
}
AsmUtil.load(mv, nativeParameterClasses[i], parameterVariables[i]);
if (!parameterClass.isPrimitive()) {
emitFromNativeConversion(builder, mv, parameterTypes[i], nativeParameterClasses[i]);
} else {
convertPrimitive(mv, nativeParameterClasses[i], parameterClass, parameterType.getNativeType());
}
}
// dispatch to java method
if (callMethod.getDeclaringClass().isInterface()) {
mv.invokeinterface(p(callMethod.getDeclaringClass()), callMethod.getName(), sig(callMethod.getReturnType(), callMethod.getParameterTypes()));
} else {
mv.invokevirtual(p(callMethod.getDeclaringClass()), callMethod.getName(), sig(callMethod.getReturnType(), callMethod.getParameterTypes()));
}
if (!isReturnTypeSupported(resultType.effectiveJavaType())) {
throw new IllegalArgumentException("unsupported closure return type " + resultType.getDeclaredType());
}
emitToNativeConversion(builder, mv, resultType);
if (!resultType.effectiveJavaType().isPrimitive()) {
if (Number.class.isAssignableFrom(resultType.effectiveJavaType())) {
AsmUtil.unboxNumber(mv, resultType.effectiveJavaType(), nativeResultClass, resultType.getNativeType());
} else if (Boolean.class.isAssignableFrom(resultType.effectiveJavaType())) {
AsmUtil.unboxBoolean(mv, nativeResultClass);
} else if (Pointer.class.isAssignableFrom(resultType.effectiveJavaType())) {
AsmUtil.unboxPointer(mv, nativeResultClass);
}
}
emitReturnOp(mv, nativeResultClass);
mv.visitMaxs(10, 10 + localVariableAllocator.getSpaceUsed());
mv.visitEnd();
SkinnyMethodAdapter closureInit = new SkinnyMethodAdapter(closureClassVisitor, ACC_PUBLIC, "",
sig(void.class, NativeRuntime.class, Object[].class),
null, null);
closureInit.start();
closureInit.aload(0);
closureInit.aload(1);
closureInit.invokespecial(p(NativeClosureProxy.class), "", sig(void.class, NativeRuntime.class));
AsmBuilder.ObjectField[] fields = builder.getObjectFieldArray();
Object[] fieldObjects = new Object[fields.length];
for (int i = 0; i < fieldObjects.length; i++) {
fieldObjects[i] = fields[i].value;
String fieldName = fields[i].name;
builder.getClassVisitor().visitField(ACC_PRIVATE | ACC_FINAL, fieldName, ci(fields[i].klass), null, null);
closureInit.aload(0);
closureInit.aload(2);
closureInit.pushInt(i);
closureInit.aaload();
if (fields[i].klass.isPrimitive()) {
Class unboxedType = unboxedType(fields[i].klass);
closureInit.checkcast(unboxedType);
unboxNumber(closureInit, unboxedType, fields[i].klass);
} else {
closureInit.checkcast(fields[i].klass);
}
closureInit.putfield(builder.getClassNamePath(), fieldName, ci(fields[i].klass));
}
closureInit.voidreturn();
closureInit.visitMaxs(10, 10);
closureInit.visitEnd();
closureClassVisitor.visitEnd();
try {
byte[] closureImpBytes = closureClassWriter.toByteArray();
if (DEBUG) {
ClassVisitor trace = AsmUtil.newTraceClassVisitor(new PrintWriter(System.err));
new ClassReader(closureImpBytes).accept(trace, 0);
}
ClassLoader cl = NativeClosureFactory.class.getClassLoader();
if (cl == null) {
cl = Thread.currentThread().getContextClassLoader();
}
if (cl == null) {
cl = ClassLoader.getSystemClassLoader();
}
Class extends NativeClosureProxy> klass = builder.getClassLoader().defineClass(c(closureProxyClassName), closureImpBytes);
Constructor extends NativeClosureProxy> constructor = null;
try {
constructor = klass.getConstructor(NativeRuntime.class, Object[].class);
} catch(NoSuchMethodException e) {
constructor = (Constructor extends NativeClosureProxy>) klass.getConstructors()[0];
}
return new Factory(runtime, constructor, klass.getMethod("invoke", nativeParameterClasses), fieldObjects);
} catch (Throwable ex) {
throw new RuntimeException(ex);
}
}
private static boolean isReturnTypeSupported(Class type) {
return type.isPrimitive()
|| boolean.class == type || Boolean.class == type
|| Byte.class == type
|| Short.class == type || Integer.class == type
|| Long.class == type || Float.class == type
|| Double.class == type
|| Pointer.class == type
;
}
private static boolean isParameterTypeSupported(Class type) {
return type.isPrimitive()
|| boolean.class == type || Boolean.class == type
|| Byte.class == type
|| Short.class == type || Integer.class == type
|| Long.class == type || Float.class == type
|| Double.class == type
|| Pointer.class == type
/*
|| CharSequence.class == type
|| Buffer.class.isAssignableFrom(type)
|| (type.isArray() && type.getComponentType().isPrimitive())
|| (type.isArray() && Struct.class.isAssignableFrom(type.getComponentType()))
|| (type.isArray() && Pointer.class.isAssignableFrom(type.getComponentType()))
|| (type.isArray() && CharSequence.class.isAssignableFrom(type.getComponentType()))
|| ByReference.class.isAssignableFrom(type)
*/
;
}
static Class getNativeClass(NativeType nativeType) {
switch (nativeType) {
case SCHAR:
case UCHAR:
return byte.class;
case SSHORT:
case USHORT:
return short.class;
case SINT:
case UINT:
return int.class;
case SLONG:
case ULONG:
case ADDRESS:
return sizeof(nativeType) <= 4 ? int.class : long.class;
case SLONGLONG:
case ULONGLONG:
return long.class;
case FLOAT:
return float.class;
case DOUBLE:
return double.class;
case VOID:
return void.class;
default:
throw new IllegalArgumentException("unsupported native type: " + nativeType);
}
}
}