Package org.perl6.nqp.runtime

Source Code of org.perl6.nqp.runtime.NativeCallOps

package org.perl6.nqp.runtime;

import java.lang.reflect.Constructor;

import java.util.Arrays;
import java.util.HashMap;

import com.sun.jna.Callback;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import static org.perl6.nqp.runtime.CallSiteDescriptor.*;

import org.perl6.nqp.sixmodel.REPR;
import org.perl6.nqp.sixmodel.REPRRegistry;
import org.perl6.nqp.sixmodel.StorageSpec;
import org.perl6.nqp.sixmodel.SixModelObject;
import org.perl6.nqp.sixmodel.reprs.CArrayInstance;
import org.perl6.nqp.sixmodel.reprs.CPointerInstance;
import org.perl6.nqp.sixmodel.reprs.CStrInstance;
import org.perl6.nqp.sixmodel.reprs.CStructInstance;
import org.perl6.nqp.sixmodel.reprs.CStructREPRData;
import org.perl6.nqp.sixmodel.reprs.NativeCall.ArgType;
import org.perl6.nqp.sixmodel.reprs.NativeCallInstance;
import org.perl6.nqp.sixmodel.reprs.NativeCallBody;
import org.perl6.nqp.sixmodel.reprs.Refreshable;

public final class NativeCallOps {
    public static long init() {
        /* Nothing to do here. The REPRs are all registered over in
         * REPRRegistry.java. */
        return 1L;
    }

    public static long build(SixModelObject target, String libname, String symbol, String convention, SixModelObject arguments, SixModelObject returns, ThreadContext tc) {
        NativeCallBody call = getNativeCallBody(tc, target);

        try {
            /* Load the library and locate the symbol. */
            /* TODO: Error handling! */
            NativeLibrary library = libname == null || libname.equals("")
                ? NativeLibrary.getProcess()
                : NativeLibrary.getInstance(libname);
            call.entry_point = library.getFunction(symbol);
   
            /* TODO: Set the calling convention. */
   
            /* Set up the argument types. */
            int n = (int) arguments.elems(tc);
            call.arg_types = new ArgType[n];
            call.arg_info  = new SixModelObject[n];
            for (int i = 0; i < n; i++) {
                SixModelObject info = arguments.at_pos_boxed(tc, i);
                call.arg_types[i] = getArgType(tc, info, false);

                if (call.arg_types[i] == ArgType.CALLBACK)
                    call.arg_info[i] = info.at_key_boxed(tc, "callback_args");
            }
   
            call.ret_type = getArgType(tc, returns, true);
   
            return 1L;
        }
        catch (Throwable t) {
            throw ExceptionHandling.dieInternal(tc, t);
        }
    }

    public static SixModelObject call(SixModelObject returns, SixModelObject callObject, SixModelObject arguments, ThreadContext tc) {
        NativeCallBody call = getNativeCallBody(tc, callObject);

        try {
            /* Convert arguments into array of appropriate objects. */
            int n = (int) arguments.elems(tc);
            /* TODO: Make sure n == call.arg_types.length? */
            Object[] cArgs = new Object[n];
            for (int i = 0; i < n; i++) {
                /* TODO: Converting sixmodel objects to appropriate JNA types. */
                cArgs[i] = toJNAType(tc, arguments.at_pos_boxed(tc, i), call.arg_types[i], call.arg_info[i]);
            }

            /* The actual foreign function call. */
            Object returned = call.entry_point.invoke(javaType(tc, call.ret_type, returns), cArgs);

            for (int i = 0; i < arguments.elems(tc); i++) {
                refresh(arguments.at_pos_boxed(tc, i), tc);
            }

            /* Wrap returned in the appropriate REPR type. */
            return toNQPType(tc, call.ret_type, returns, returned);
        }
        catch (ControlException e) { throw e; }
        catch (Throwable t) {
            throw ExceptionHandling.dieInternal(tc, t);
        }
    }

    public static long refresh(SixModelObject obj, ThreadContext tc) {
        obj = Ops.decont(obj, tc);
        if(!(obj instanceof Refreshable)) return 1L;

        ((Refreshable) obj).refresh(tc);

        return 1L;
    }

    public static SixModelObject nativecallglobal(String libname, String symbol, SixModelObject target_spec, SixModelObject target_type, ThreadContext tc) {
        try {
            /* Load the library and locate the symbol. */
            /* TODO: Error handling! */
            NativeLibrary library = libname == null || libname.equals("")
                ? NativeLibrary.getProcess()
                : NativeLibrary.getInstance(libname);
            Pointer entry_point = library.getGlobalVariableAddress(symbol);

            StorageSpec ss = target_spec.st.REPR.get_storage_spec(tc, target_spec.st);
            if (ss.boxed_primitive == StorageSpec.BP_STR)
                entry_point = entry_point.getPointer(0);

            return castNativeCall(tc, target_spec, target_type, entry_point);
        }
        catch (Throwable t) {
            throw ExceptionHandling.dieInternal(tc, t);
        }
    }

    public static SixModelObject nativecallcast(SixModelObject target_spec, SixModelObject target_type, SixModelObject source, ThreadContext tc) {
        Pointer o = null;

        if (source instanceof CPointerInstance) { // TODO Care about CPointer type object
            o = ((CPointerInstance)source).pointer;
        }
        else {
            throw ExceptionHandling.dieInternal(tc,
                "Native call expected object with CPointer representation, but got something else");
        }

        return castNativeCall(tc, target_spec, target_type, o);
    }
    public static SixModelObject castNativeCall(ThreadContext tc, SixModelObject target_spec, SixModelObject target_type, Pointer o) {
        if (o == null)
            return target_type;

        ArgType target        = ArgType.INT;
        SixModelObject nqpobj = target_type.st.REPR.allocate(tc, target_type.st);
        StorageSpec        ss = target_spec.st.REPR.get_storage_spec(tc, target_spec.st);

        switch (ss.boxed_primitive) {
            case StorageSpec.BP_INT:
                switch (ss.bits) {
                    case 8:
                        nqpobj.set_int(tc, o.getByte(0));
                        break;
                    case 16:
                        nqpobj.set_int(tc, o.getShort(0));
                        break;
                    case 32:
                        nqpobj.set_int(tc, o.getInt(0));
                        break;
                    case 64:
                        nqpobj.set_int(tc, o.getLong(0));
                        break;
                    default:
                        throw ExceptionHandling.dieInternal(tc,
                            String.format("Cannot cast to %d bits integer", ss.bits));
                }
                break;
            case StorageSpec.BP_NUM:
                switch (ss.bits) {
                    case 32:
                        nqpobj.set_num(tc, o.getFloat(0));
                        break;
                    case 64:
                        nqpobj.set_num(tc, o.getDouble(0));
                        break;
                    default:
                        throw ExceptionHandling.dieInternal(tc,
                            String.format("Cannot cast to %d bits number", ss.bits));
                }
                break;
            case StorageSpec.BP_STR:
                /* TODO: Handle encodings. */
                nqpobj.set_str(tc, o.getString(0));
                break;
            default:
                if (target_type instanceof org.perl6.nqp.sixmodel.reprs.CStrInstance) {
                    /* TODO: Handle encodings. */
                    nqpobj.set_str(tc, o.getString(0));
                }
                else if (nqpobj instanceof org.perl6.nqp.sixmodel.reprs.CPointerInstance) {
                    CPointerInstance cpointer = (CPointerInstance) nqpobj;
                    cpointer.pointer          = o;
                }
                else if (nqpobj instanceof org.perl6.nqp.sixmodel.reprs.CArrayInstance) {
                    CArrayInstance carray = (CArrayInstance) nqpobj;
                    carray.storage        = o;
                    carray.managed        = false;
                }
                else if (nqpobj instanceof org.perl6.nqp.sixmodel.reprs.CStructInstance) {
                    Class<?>    structClass = ((CStructREPRData)target_type.st.REPRData).structureClass;
                    CStructInstance cstruct = (CStructInstance)nqpobj;
                    cstruct.storage         = Structure.newInstance(structClass, o);
                }
                else {
                    throw ExceptionHandling.dieInternal(tc,
                        String.format("Don't know how to cast to %s", nqpobj));
                }
        }

        return nqpobj;
    }

    private static REPR ncrepr = REPRRegistry.getByName("NativeCall");
    private static NativeCallBody getNativeCallBody(ThreadContext tc, SixModelObject target) {
        NativeCallBody call;
        if (target instanceof NativeCallInstance) {
            call = ((NativeCallInstance)target).body;
        }
        else {
            call = (NativeCallBody)target.get_boxing_of(tc, ncrepr.ID);
            if (call == null) {
                call = new NativeCallBody();
                target.set_boxing_of(tc, ncrepr.ID, call);
            }
        }
        return call;
    }

    private static Class<?> javaType(ThreadContext tc, ArgType target, SixModelObject smoType) {
        switch (target) {
        case VOID:
            return Void.class;
        case CHAR:
            return Byte.class;
        case SHORT:
            return Short.class;
        case INT:
            return Integer.class;
        case LONG:
            return Long.class;
        case FLOAT:
            return Float.class;
        case DOUBLE:
            return Double.class;
        case ASCIISTR:
        case UTF8STR:
        case UTF16STR:
            /* TODO: Handle encodings. */
            return String.class;
        case CPOINTER:
        case CARRAY:
            return Pointer.class;
        case CSTRUCT:
            return ((CStructREPRData) smoType.st.REPRData).structureClass;
        default:
            throw ExceptionHandling.dieInternal(tc, String.format("Don't know correct Java class for %s arguments yet", target));
        }
    }

    public static Object toJNAType(ThreadContext tc, SixModelObject o, ArgType target, SixModelObject info) {
        o = Ops.decont(o, tc);
        if (Ops.isconcrete(o, tc) == 0) return null;

        switch (target) {
        case CHAR:
            return new Byte((byte) o.get_int(tc));
        case SHORT:
            return new Short((short) o.get_int(tc));
        case INT:
            return new Integer((int) o.get_int(tc));
        case LONG:
            return new Long((long) o.get_int(tc));
        case FLOAT:
            return new Float((float) o.get_num(tc));
        case DOUBLE:
            return new Double((double) o.get_num(tc));
        case ASCIISTR:
        case UTF8STR:
        case UTF16STR: {
            /* TODO: Handle encodings. */
            SixModelObject meth = Ops.findmethod(o, "cstr", tc);
            if (meth != null) {
                Ops.invokeDirect(tc, meth, new CallSiteDescriptor(new byte[] { ARG_OBJ }, null), new Object[] { o });
                CStrInstance cstr = (CStrInstance) Ops.decont(Ops.result_o(tc.resultFrame()), tc);
                return cstr.cstr;
            }
            else {
                return o.get_str(tc);
            }
        }
        case CPOINTER:
            return ((CPointerInstance) o).pointer;
        case CARRAY:
            return ((CArrayInstance) o).storage;
        case CSTRUCT:
            return ((CStructInstance) o).storage;
        case CALLBACK:
            return callbackHandlerFor(o, info, tc);
        default:
            throw ExceptionHandling.dieInternal(tc, String.format("Don't know how to convert %s arguments to JNA yet", target));
        }
    }

    public static SixModelObject toNQPType(ThreadContext tc, ArgType target, SixModelObject type, Object o) {
        SixModelObject nqpobj = null;
        if (target != ArgType.VOID)
            nqpobj = type.st.REPR.allocate(tc, type.st);

        switch (target) {
        case VOID:
            return type;
        case CHAR: {
            byte val = ((Byte) o).byteValue();
            nqpobj.set_int(tc, val);
            break;
        }
        case SHORT: {
            short val = ((Short) o).shortValue();
            nqpobj.set_int(tc, val);
            break;
        }
        case INT: {
            int val = ((Integer) o).intValue();
            nqpobj.set_int(tc, val);
            break;
        }
        case LONG: {
            long val = ((Long) o).longValue();
            nqpobj.set_int(tc, val);
            break;
        }
        case FLOAT: {
            float val = ((Float) o).floatValue();
            nqpobj.set_num(tc, val);
            break;
        }
        case DOUBLE: {
            double val = ((Double) o).floatValue();
            nqpobj.set_num(tc, val);
            break;
        }
        case ASCIISTR:
        case UTF8STR:
        case UTF16STR:
            /* TODO: Handle encodings. */
            if (o != null) {
                nqpobj.set_str(tc, (String) o);
            }
            else {
                nqpobj = type;
            }
            break;
        case CPOINTER: {
            CPointerInstance cpointer = (CPointerInstance) nqpobj;
            cpointer.pointer = (Pointer) o;
            break;
        }
        case CARRAY: {
            CArrayInstance carray = (CArrayInstance) nqpobj;
            carray.storage = (Pointer) o;
            carray.managed = false;
            break;
        }
        case CSTRUCT: {
            CStructInstance cstruct = (CStructInstance) nqpobj;
            cstruct.storage = (Structure) o;
            break;
        }
        default:
            throw ExceptionHandling.dieInternal(tc, String.format("Don't know how to convert %s arguments to NQP yet", target));
        }

        return nqpobj;
    }

    private static ArgType getArgType(ThreadContext tc, SixModelObject info, boolean isReturn) {
        String type_name = info.at_key_boxed(tc, "type").get_str(tc);

        ArgType type = ArgType.VOID;
        try {
            type = ArgType.valueOf(ArgType.class, type_name.toUpperCase());
        }
        catch (IllegalArgumentException e) {
            throw ExceptionHandling.dieInternal(tc, String.format("Unknown type '%s' used for native call", type_name));
        }

        if (!isReturn && type == ArgType.VOID) {
            throw ExceptionHandling.dieInternal(tc, "Can only use 'void' type on native call return values");
        }

        return type;
    }

    static int typeId = 0;
    static String handlerName = Type.getInternalName(CallbackHandler.class);
    static private HashMap<SixModelObject, CallbackHandler> callbackHandlers = new HashMap<SixModelObject, CallbackHandler>();
    static private HashMap<String, Class<CallbackHandler>> callbackClasses = new HashMap<String, Class<CallbackHandler>>();
    @SuppressWarnings("unchecked")
    private static CallbackHandler callbackHandlerFor(SixModelObject function, SixModelObject infos, ThreadContext tc) {
        CallbackHandler handler = callbackHandlers.get(function);
        if (handler != null) return handler;

        /* Extract the information we need from the list of infos. The first
         * element of the list is the return type and any following items are
         * the argument types. We process the arguments first, since a JVM
         * method signature has the form "($arguments)$returns".
         *
         * At the same time, we collect the data needed for the callback when
         * it's time to call back into NQP: type objects for argument and
         * return types, and the ArgTypes for all of them.
         */
        int num_info = (int) infos.elems(tc);
        SixModelObject[] argumentTypes = new SixModelObject[num_info-1];
        ArgType[] argumentInfo = new ArgType[num_info-1];
        boolean isVoid = infos.at_pos_boxed(tc, 0).at_key_boxed(tc, "type").get_str(tc).equals("void");
        StringBuilder sb = new StringBuilder("(");
        for(int i = 1; i < num_info; i++) {
            SixModelObject info = infos.at_pos_boxed(tc, i);
            SixModelObject type = info.at_key_boxed(tc, "typeobj");
            sb.append(Type.getDescriptor(javaType(tc, getArgType(tc, info, false), type)));
            argumentTypes[i-1] = type;
            argumentInfo[i-1] = getArgType(tc, info, false);
        }
        sb.append(")");

        SixModelObject info = infos.at_pos_boxed(tc, 0);
        SixModelObject returnType = info.at_key_boxed(tc, "typeobj");
        ArgType returnInfo = getArgType(tc, info, true);
        Class<?> javaReturn = null;
        if (!isVoid) javaReturn = javaType(tc, getArgType(tc, info, true), returnType);
        sb.append(isVoid? "V": Type.getDescriptor(javaReturn));
        String sig = sb.toString();
        Class<CallbackHandler> handlerClass = callbackClasses.get(sig);

        if (handlerClass == null) {
            int typeNo = typeId++;

            /* We need to generate two separate pieces two work with callbacks
             * in JNA: an interface, which specifies which method to call and
             * its signature, and a class implementing that interface that
             * does the actual work.
             *
             * To keep codegen to a minimum, we'll only ever have a single
             * class implementing each interface (one for each type signature
             * used), with the classes delegating to the correct NQP function.
             */

            ClassWriter ifaceWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
            String ifaceName = "__CallbackInterface__" + typeNo;

            // public interface $interfaceName extends com.sun.jna.Callback { ... }
            ifaceWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE,
                    ifaceName, null, "java/lang/Object", new String[] { Type.getInternalName(Callback.class) });
            // public $sig[0] callback($sig[1..*]);
            MethodVisitor ifaceMeth = ifaceWriter.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_ABSTRACT, "callback", sig, null, null);
            ifaceMeth.visitEnd();

            ifaceWriter.visitEnd();
            byte[] ifaceCompiled = ifaceWriter.toByteArray();
            Class<?> iface = tc.gc.byteClassLoader.defineClass(ifaceName, ifaceCompiled);

            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
            String className = "__CallbackHandler__" + typeNo;

            // public class $className extends CallbackHandler implements $ifaceName { ... }
            classWriter.visit(Opcodes.V1_7, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, className, null,
                    handlerName, new String[] { Type.getInternalName(iface) });

            // public $className(GlobalContext gc, SixModelObject function) { super(gc, function); }
            //String ctorSig = "(Lorg/perl6/nqp/runtime/GlobalContext;Lorg/perl6/nqp/sixmodel/SixModelObject;)V";
            String ctorSig = String.format("(%s%s%s%s%s)V",
                    Type.getDescriptor(GlobalContext.class), Type.getDescriptor(SixModelObject.class),
                    Type.getDescriptor(ArgType.class),
                    Type.getDescriptor(SixModelObject[].class), Type.getDescriptor(ArgType[].class));
            MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", ctorSig, null, null);
            constructor.visitCode();
            constructor.visitVarInsn(Opcodes.ALOAD, 0);
            constructor.visitVarInsn(Opcodes.ALOAD, 1);
            constructor.visitVarInsn(Opcodes.ALOAD, 2);
            constructor.visitVarInsn(Opcodes.ALOAD, 3);
            constructor.visitVarInsn(Opcodes.ALOAD, 4);
            constructor.visitVarInsn(Opcodes.ALOAD, 5);
            constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, handlerName, "<init>", ctorSig);
            constructor.visitInsn(Opcodes.RETURN);
            constructor.visitMaxs(6, 6);
            constructor.visitEnd();

            // public $sig[0] callback($sig[1..*]) { ... }
            MethodVisitor callback = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "callback", sig, null, null);
            //   Object[] args = new Object[$argCount];
            callback.visitLdcInsn(num_info-1);
            callback.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

            //  args[$i] = $sig[$i+1];
            for (int i = 0; i < num_info-1; i++) {
                callback.visitInsn(Opcodes.DUP); // Dup the array, since we'll store to it
                callback.visitLdcInsn(i);
                callback.visitVarInsn(Opcodes.ALOAD, i+1);
                callback.visitInsn(Opcodes.AASTORE);
            }

            //  callFunction(args);
            callback.visitVarInsn(Opcodes.ALOAD, 0);
            callback.visitInsn(Opcodes.SWAP);
            callback.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, "callFunction", "([Ljava/lang/Object;)Ljava/lang/Object;");

            if (isVoid) {
                callback.visitInsn(Opcodes.POP);
                callback.visitInsn(Opcodes.RETURN);
            }
            else {
                callback.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(javaReturn));
                callback.visitInsn(Opcodes.ARETURN);
            }

            callback.visitMaxs(4, num_info);
            callback.visitEnd();

            classWriter.visitEnd();
            byte[] classCompiled = classWriter.toByteArray();
            /* Uncomment to dump generated class to file:
            try {
                java.io.FileOutputStream fos = new java.io.FileOutputStream(new java.io.File(className + ".class"));
                fos.write(classCompiled);
                fos.close();
            } catch (java.io.IOException e) {
            }
            */
            handlerClass = (Class<CallbackHandler>) tc.gc.byteClassLoader.defineClass(className, classCompiled);
            callbackClasses.put(sig, handlerClass);
        }

        try {
            Constructor<CallbackHandler> ctor = handlerClass.getConstructor(GlobalContext.class, SixModelObject.class,
                    ArgType.class,
                    SixModelObject[].class, ArgType[].class);
            handler = ctor.newInstance(tc.gc, function,
                    returnInfo,
                    argumentTypes, argumentInfo);
        }
        catch (Exception e) {
            throw ExceptionHandling.dieInternal(tc, e);
        }
        callbackHandlers.put(function, handler);
        return handler;
    }

    public static abstract class CallbackHandler implements Callback {
        public GlobalContext gc;
        public SixModelObject function;

        public ArgType returnInfo;

        public SixModelObject[] argumentTypes;
        public ArgType[] argumentInfo;

        public CallSiteDescriptor callsite;

        protected CallbackHandler(GlobalContext gc, SixModelObject function,
                ArgType returnInfo,
                SixModelObject[] argumentTypes, ArgType[] argumentInfo) {
            this.gc = gc;
            this.function = function;

            this.returnInfo = returnInfo;

            this.argumentTypes = argumentTypes;
            this.argumentInfo = argumentInfo;

            byte[] desc = new byte[argumentTypes.length];
            Arrays.fill(desc, ARG_OBJ);
            this.callsite = new CallSiteDescriptor(desc, null);
        }

        protected Object callFunction(Object... args) {
            ThreadContext tc = gc.getCurrentThreadContext();

            /* TODO: Make sure args.length == argumentTypes.length */

            for (int i = 0; i < args.length; i++) {
                args[i] = toNQPType(tc, argumentInfo[i], argumentTypes[i], args[i]);
            }
            Ops.invokeDirect(tc, function, callsite, args);

            if (returnInfo == ArgType.VOID) {
                return null;
            }
            else {
                return toJNAType(tc, Ops.decont(Ops.result_o(tc.resultFrame()), tc), returnInfo, null);
            }
        }
    }
}
TOP

Related Classes of org.perl6.nqp.runtime.NativeCallOps

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.