1 /*
   2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.internal.nicl;
  24 
  25 import jdk.internal.nicl.types.DescriptorParser;
  26 import jdk.internal.org.objectweb.asm.MethodVisitor;
  27 import jdk.internal.org.objectweb.asm.Type;
  28 
  29 import java.lang.invoke.MethodHandle;
  30 import java.lang.invoke.MethodHandles;
  31 import java.lang.invoke.MethodType;
  32 import java.lang.reflect.Method;
  33 import java.nicl.layout.Function;
  34 import java.nicl.layout.Layout;
  35 import java.nicl.metadata.*;
  36 import java.nicl.types.LayoutType;
  37 import java.nicl.types.Pointer;
  38 import java.nicl.types.Struct;
  39 
  40 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  41 
  42 class HeaderImplGenerator extends BinderClassGenerator {
  43 
  44     // lookup helper to use for looking up symbols
  45     private final SymbolLookup lookup;
  46 
  47     HeaderImplGenerator(Class<?> hostClass, String implClassName, Class<?> c, SymbolLookup lookup) {
  48         super(hostClass, implClassName, new Class<?>[] { c });
  49         this.lookup = lookup;
  50     }
  51 
  52     enum AccessorMethodType {
  53         get, set, ptr;
  54 
  55         MethodType getMethodType(Class<?> c) {
  56             switch (this) {
  57                 case get: return MethodType.methodType(c);
  58                 case set: return MethodType.methodType(void.class, c);
  59                 case ptr: return MethodType.methodType(Pointer.class);
  60             }
  61 
  62             throw new IllegalArgumentException("Unhandled type: " + this);
  63         }
  64     }
  65 
  66     @Override
  67     protected void generateDefaultConstructor(BinderClassWriter cw) {
  68         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
  69         mv.visitCode();
  70         mv.visitVarInsn(ALOAD, 0);
  71         mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
  72         mv.visitInsn(RETURN);
  73         mv.visitMaxs(1,1);
  74         mv.visitEnd();
  75     }
  76 
  77     @Override
  78     protected void generateMethodImplementation(BinderClassWriter cw, Method method) {
  79         if (method.isAnnotationPresent(NativeType.class) && !Util.isFunction(method)) {
  80             generateGlobalVariableMethods(cw, method, getSymbolName(method, getGetterBaseName(method)));
  81         } else if (method.isAnnotationPresent(C.class) && method.isAnnotationPresent(CallingConvention.class)) {
  82             MethodType methodType = Util.methodTypeFor(method);
  83             Function function = Util.functionof(method);
  84             NativeInvoker invoker;
  85             try {
  86                 invoker = new NativeInvoker(function, methodType, method.isVarArgs(), lookup, getSymbolName(method), method.toString(), method.getGenericReturnType());
  87             } catch (NoSuchMethodException | IllegalAccessException e) {
  88                 throw new IllegalStateException(e);
  89             }
  90             addMethodFromHandle(cw, method.getName(), methodType, method.isVarArgs(), invoker.getBoundMethodHandle());
  91         } else {
  92             super.generateMethodImplementation(cw, method);
  93         }
  94     }
  95 
  96     private void generateGlobalVariableMethods(BinderClassWriter cw, Method method, String symbolName) {
  97         Class<?> c = method.getReturnType();
  98         java.lang.reflect.Type type = method.getGenericReturnType();
  99         Layout l = new DescriptorParser(method.getAnnotation(NativeType.class).layout()).parseLayout().findFirst().get();
 100         LayoutType<?> lt = Util.makeType(type, l);
 101 
 102         int dollarIndex = method.getName().indexOf("$");
 103         String methodBaseName = method.getName().substring(0, dollarIndex);
 104         Pointer<?> p;
 105 
 106         try {
 107             p = lookup.lookup(symbolName).getAddress().cast(lt);
 108         } catch (NoSuchMethodException e) {
 109             throw new IllegalStateException(e);
 110         }
 111 
 112         for (AccessorMethodType t : AccessorMethodType.values()) {
 113             String methodName = methodBaseName + "$" + t.name();
 114             MethodHandle target;
 115             switch (t) {
 116                 case get:
 117                     target = lt.getter();
 118                     target = target.bindTo(p).asType(MethodType.methodType(c));
 119                     break;
 120 
 121                 case set:
 122                     target = lt.setter();
 123                     target = target.bindTo(p).asType(MethodType.methodType(void.class, c));
 124                     break;
 125 
 126                 case ptr:
 127                     target = MethodHandles.constant(Pointer.class, p);
 128                     break;
 129 
 130                 default:
 131                     throw new InternalError("Unexpected access method type: " + t);
 132             }
 133 
 134             addMethodFromHandle(cw, methodName, t.getMethodType(c), false, target);
 135         }
 136     }
 137 
 138     private String getGetterBaseName(Method method) {
 139         String name = method.getName();
 140 
 141         if (!name.endsWith("$get")) {
 142             throw new IllegalArgumentException("Unexpected method name " + method.getName());
 143         }
 144 
 145         return name.substring(0, name.lastIndexOf("$"));
 146     }
 147 
 148     private String getSymbolName(Method method, String defaultName) {
 149         String name = method.getAnnotation(NativeType.class).name();
 150         if (NativeType.NO_NAME.equals(name)) {
 151             // FIXME: Make this an error (require name to be set)?
 152             return defaultName;
 153         } else {
 154             return name;
 155         }
 156     }
 157 
 158     private String getSymbolName(Method method) {
 159         return getSymbolName(method, method.getName());
 160     }
 161 
 162     // code generation helpers
 163 
 164     private void addMethodFromHandle(BinderClassWriter cw, String methodName, MethodType methodType, boolean isVarArgs, MethodHandle targetMethodHandle) {
 165         String descriptor = methodType.toMethodDescriptorString();
 166 
 167         int flags = ACC_PUBLIC;
 168         if (isVarArgs) {
 169             flags |= ACC_VARARGS;
 170         }
 171 
 172         MethodVisitor mv = cw.visitMethod(flags, methodName, descriptor, null, null);
 173 
 174         mv.visitCode();
 175 
 176         // push the method handle
 177         mv.visitLdcInsn(cw.makeConstantPoolPatch(targetMethodHandle));
 178         mv.visitTypeInsn(CHECKCAST, Type.getInternalName(MethodHandle.class));
 179 
 180         //copy arguments
 181         for (int i = 0, curSlot = 1; i < methodType.parameterCount(); i++) {
 182             Class<?> c = methodType.parameterType(i);
 183             mv.visitVarInsn(loadInsn(c), curSlot);
 184             curSlot += getSlotsForType(c);
 185         }
 186 
 187         //call MH
 188         mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(MethodHandle.class), "invoke",
 189                 targetMethodHandle.type().toMethodDescriptorString(), false);
 190 
 191         mv.visitInsn(returnInsn(methodType.returnType()));
 192 
 193         mv.visitMaxs(0, 0);
 194         mv.visitEnd();
 195     }
 196 
 197     //where
 198     private static int getSlotsForType(Class<?> c) {
 199         if (c == long.class || c == double.class) {
 200             return 2;
 201         }
 202         return 1;
 203     }
 204 }