All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jnr.ffi.provider.jffi.NativeClosureProxy Maven / Gradle / Ivy

There is a newer version: 0.3.1
Show newest version
package jnr.ffi.provider.jffi;

import jnr.ffi.NativeLong;
import jnr.ffi.NativeType;
import jnr.ffi.Pointer;
import jnr.ffi.mapper.FromNativeContext;
import jnr.ffi.mapper.FromNativeConverter;
import jnr.ffi.mapper.ToNativeContext;
import jnr.ffi.mapper.ToNativeConverter;
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 static jnr.ffi.provider.jffi.AsmUtil.*;
import static jnr.ffi.provider.jffi.CodegenUtils.*;
import static jnr.ffi.provider.jffi.NumberUtil.*;
import static org.objectweb.asm.Opcodes.*;

/**
 *
 */
public abstract class NativeClosureProxy {
    protected final NativeRuntime 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 NativeRuntime runtime;
        private Constructor constructor;
        private Object[] objectFields;
        private Method invokeMethod;

        Factory(NativeRuntime runtime, Constructor 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(NativeRuntime runtime, Method callMethod,
                            ToNativeType resultType, FromNativeType[] parameterTypes) {
        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(closureProxyClassName, closureClassVisitor);

        closureClassVisitor.visit(V1_5, 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].nativeType);
        }

        Class nativeResultClass = getNativeClass(resultType.nativeType);

        SkinnyMethodAdapter mv = new SkinnyMethodAdapter(closureClassVisitor.visitMethod(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()) {
                Class unboxedType = unboxedType(parameterClass);
                convertPrimitive(mv, nativeParameterClasses[i], unboxedType, parameterType.nativeType);
                boxValue(mv, parameterClass, unboxedType);
            } else {
                convertPrimitive(mv, nativeParameterClasses[i], parameterClass, parameterType.nativeType);
            }

            // If there is a parameter converter, use it to convert the parameter to its java form
            FromNativeConverter fromNativeConverter = parameterTypes[i].fromNativeConverter;
            if (fromNativeConverter != null) {
                if (parameterClass.isPrimitive()) {
                    boxValue(mv, boxedType(parameterClass), parameterClass);
                }
                mv.aload(0);
                mv.getfield(builder.getClassNamePath(), builder.getFromNativeConverterName(fromNativeConverter), ci(FromNativeConverter.class));
                mv.swap();
                mv.aconst_null();
                mv.invokeinterface(FromNativeConverter.class, "fromNative",
                        Object.class, Object.class, FromNativeContext.class);
                if (parameterType.getDeclaredType().isPrimitive()) {
                    Class boxedType = getBoxedClass(parameterType.getDeclaredType());
                    mv.checkcast(p(boxedType));
                    unboxNumber(mv, boxedType, parameterType.getDeclaredType(), parameterType.nativeType);
                } else {
                    mv.checkcast(p(parameterTypes[i].getDeclaredType()));
                }
            }
        }

        // 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());
        }


        ToNativeConverter toNativeConverter = resultType.toNativeConverter;
        if (toNativeConverter != null) {

            mv.aload(0);
            mv.getfield(builder.getClassNamePath(), builder.getToNativeConverterName(toNativeConverter), ci(ToNativeConverter.class));
            mv.swap();
            if (resultType.getDeclaredType().isPrimitive()) {
                boxValue(mv, getBoxedClass(resultType.getDeclaredType()), resultType.getDeclaredType());
            }
            mv.aconst_null();
            mv.invokeinterface(ToNativeConverter.class, "toNative",
                    Object.class, Object.class, ToNativeContext.class);
            mv.checkcast(p(toNativeConverter.nativeType()));
        }



        if (!resultType.effectiveJavaType().isPrimitive()) {
            if (Number.class.isAssignableFrom(resultType.effectiveJavaType())) {
                AsmUtil.unboxNumber(mv, resultType.effectiveJavaType(), nativeResultClass, resultType.nativeType);

            } 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.visitMethod(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();
            }
            AsmClassLoader asm = new AsmClassLoader(cl);
            Class klass = asm.defineClass(c(closureProxyClassName), closureImpBytes);
            Constructor constructor
                    = klass.getConstructor(NativeRuntime.class, Object[].class);

            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 || NativeLong.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 || NativeLong.class == type
                || Pointer.class == type
                || String.class == type
                /*
                || CharSequence.class == type
                || Buffer.class.isAssignableFrom(type)
                || Struct.class.isAssignableFrom(type)
                || Enum.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);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy