1 /*
   2  * Copyright (c) 2014, 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 com.sun.tools.jextract;
  24 
  25 import java.foreign.Libraries;
  26 import java.lang.invoke.MethodHandles;
  27 import java.lang.invoke.MethodHandles.Lookup;
  28 import java.lang.reflect.Method;
  29 import java.util.logging.Logger;
  30 import jdk.internal.org.objectweb.asm.FieldVisitor;
  31 import jdk.internal.org.objectweb.asm.ClassWriter;
  32 import jdk.internal.org.objectweb.asm.MethodVisitor;
  33 import jdk.internal.org.objectweb.asm.Type;
  34 import com.sun.tools.jextract.tree.EnumTree;
  35 import com.sun.tools.jextract.tree.FieldTree;
  36 import com.sun.tools.jextract.tree.FunctionTree;
  37 import com.sun.tools.jextract.tree.MacroTree;
  38 import com.sun.tools.jextract.tree.SimpleTreeVisitor;
  39 import com.sun.tools.jextract.tree.Tree;
  40 import com.sun.tools.jextract.tree.VarTree;
  41 
  42 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
  43 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  44 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE;
  45 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  46 import static jdk.internal.org.objectweb.asm.Opcodes.CHECKCAST;
  47 import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC;
  48 import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
  49 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE;
  50 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
  51 import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
  52 import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
  53 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
  54 import static jdk.internal.org.objectweb.asm.Opcodes.V1_8;
  55 
  56 /**
  57  * This class generates a class with only static methods and fields. A native
  58  * library interface instance (of the given header file) is kept as a static
  59  * private field. One static method is generated for every library interface
  60  * method. Enum and macro constants are mapped to static final fields. By importing
  61  * the "static forwarder" class, the user code looks more or less like C code.
  62  * Libraries.bind and header interface usage is hidden.
  63  */
  64 final class StaticForwarderGenerator extends SimpleTreeVisitor<Void, JType> {
  65     private final HeaderFile headerFile;
  66     private final String headerClassName;
  67     private final String headerClassNameDesc;
  68     private final ClassWriter cw;
  69     private final Logger logger = Logger.getLogger(getClass().getPackage().getName());
  70 
  71     // suffix for static forwarder class name
  72     private static final String STATICS_CLASS_NAME_SUFFIX = "_h";
  73     // field name for the header interface instance.
  74     private static final String STATICS_LIBRARY_FIELD_NAME = "_theLibrary";
  75 
  76     StaticForwarderGenerator(HeaderFile header) {
  77         logger.info(() -> "Instantiate StaticForwarderGenerator for " + header.path);
  78         this.headerFile = header;
  79         this.headerClassName = Utils.toInternalName(headerFile.pkgName, headerFile.clsName);
  80         this.headerClassNameDesc = "L" + headerClassName + ";";
  81         this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
  82         this.cw.visit(V1_8, ACC_PUBLIC | ACC_FINAL, getClassName(),
  83             null, "java/lang/Object", null);
  84         staticsInitializer();
  85     }
  86 
  87     // not fully qualified
  88     String getSimpleClassName() {
  89         return headerFile.clsName + STATICS_CLASS_NAME_SUFFIX;
  90     }
  91 
  92     String getClassName() {
  93         return headerClassName + STATICS_CLASS_NAME_SUFFIX;
  94     }
  95 
  96     // return the generated static forwarder class bytes
  97     byte[] getClassBytes() {
  98         cw.visitEnd();
  99         return cw.toByteArray();
 100     }
 101 
 102     @Override
 103     public Void visitVar(VarTree varTree, JType jt) {
 104         String fieldName = varTree.name();
 105         assert !fieldName.isEmpty();
 106 
 107         emitStaticForwarder(fieldName + "$get",
 108                 "()" + jt.getDescriptor(), "()" + jt.getSignature());
 109 
 110         emitStaticForwarder(fieldName + "$set",
 111                 "(" + jt.getDescriptor() + ")V",
 112                 "(" + JType.getPointerVoidAsWildcard(jt) + ")V");
 113         JType ptrType = new PointerType(jt);
 114         emitStaticForwarder(fieldName + "$ptr",
 115                 "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature());
 116 
 117         return null;
 118     }
 119 
 120     @Override
 121     public Void visitEnum(EnumTree enumTree, JType jt) {
 122         enumTree.constants().forEach(constant -> addEnumConstant(constant));
 123         return null;
 124     }
 125 
 126     @Override
 127     public Void visitFunction(FunctionTree funcTree, JType jt) {
 128         assert (jt instanceof JType.Function);
 129         JType.Function fn = (JType.Function)jt;
 130         String uniqueName = funcTree.name() + "." + fn.getDescriptor();
 131         logger.fine(() -> "Add method: " + fn.getSignature());
 132         emitStaticForwarder(funcTree.name(), fn.getDescriptor(), fn.getSignature());
 133         return null;
 134     }
 135 
 136     public Void visitMacro(MacroTree macroTree, JType jt) {
 137         if (!macroTree.isConstant()) {
 138             logger.fine(() -> "Skipping unrecognized object-like macro " + macroTree.name());
 139             return null;
 140         }
 141         String name = macroTree.name();
 142         Object value = macroTree.value().get();
 143         logger.fine(() -> "Adding macro " + name);
 144         Class<?> macroType = Utils.unboxIfNeeded(value.getClass());
 145         String sig = Type.getType(macroType).getDescriptor();
 146         FieldVisitor fv = cw.visitField(ACC_PUBLIC | ACC_STATIC, name, sig, null, value);
 147         fv.visitEnd();
 148         return null;
 149     }
 150 
 151     @Override
 152     public Void visitTree(Tree tree, JType jt) {
 153         logger.warning(() -> "Unsupported declaration tree:");
 154         logger.warning(() -> tree.toString());
 155         return null;
 156     }
 157 
 158     // Internals only below this point
 159 
 160     // map each C enum constant as a static final field of the static forwarder class
 161     private void addEnumConstant(FieldTree fieldTree) {
 162         assert (fieldTree.isEnumConstant());
 163         String name = fieldTree.name();
 164         String desc = headerFile.globalLookup(fieldTree.type()).getDescriptor();
 165         if (desc.length() != 1) {
 166             throw new AssertionError("expected single char descriptor: " + desc);
 167         }
 168         FieldVisitor fv = null;
 169         switch (desc.charAt(0)) {
 170             case 'J':
 171                 long lvalue = fieldTree.enumConstant().get();
 172                 fv = cw.visitField(ACC_PUBLIC | ACC_STATIC, name, desc, null, lvalue);
 173                 break;
 174             case 'I':
 175                 int ivalue = fieldTree.enumConstant().get().intValue();
 176                 fv = cw.visitField(ACC_PUBLIC | ACC_STATIC, name, desc, null, ivalue);
 177                 break;
 178             default:
 179                 throw new AssertionError("should not reach here");
 180         }
 181         fv.visitEnd();
 182     }
 183 
 184     // emit library interface static field and <clinit> initializer for that field
 185     private void staticsInitializer() {
 186         // library interface field
 187         FieldVisitor fv = cw.visitField(ACC_PRIVATE|ACC_STATIC|ACC_FINAL,
 188             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc, null, null);
 189         fv.visitEnd();
 190 
 191         // <clinit> to bind library interface field
 192         MethodVisitor mv = cw.visitMethod(ACC_STATIC,
 193             "<clinit>", "()V", null, null);
 194         mv.visitCode();
 195 
 196         // MethodHandles.lookup()
 197         Method lookupMethod = null;
 198         try {
 199             lookupMethod = MethodHandles.class.getMethod("lookup");
 200         } catch (NoSuchMethodException nsme) {
 201             throw new RuntimeException(nsme);
 202         }
 203         mv.visitMethodInsn(INVOKESTATIC,
 204             Type.getInternalName(MethodHandles.class), "lookup",
 205             Type.getMethodDescriptor(lookupMethod), false);
 206 
 207         // ldc library-interface-class
 208         mv.visitLdcInsn(Type.getObjectType(headerClassName));
 209 
 210         // Libraries.bind(lookup, class);
 211         Method bindMethod = null;
 212         try {
 213             bindMethod = Libraries.class.getMethod("bind", Lookup.class, Class.class);
 214         } catch (NoSuchMethodException nsme) {
 215             throw new RuntimeException(nsme);
 216         }
 217         mv.visitMethodInsn(INVOKESTATIC,
 218             Type.getInternalName(Libraries.class), "bind",
 219             Type.getMethodDescriptor(bindMethod), false);
 220 
 221         // store it in library interface field
 222         mv.visitTypeInsn(CHECKCAST, headerClassName);
 223         mv.visitFieldInsn(PUTSTATIC, getClassName(),
 224             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc);
 225 
 226         mv.visitInsn(RETURN);
 227         mv.visitMaxs(0, 0);
 228         mv.visitEnd();
 229     }
 230 
 231     // emit static forwarder method for a specific library interface method
 232     private void emitStaticForwarder(String name, String desc, String signature) {
 233         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC,
 234             name, desc, signature, null);
 235         mv.visitCode();
 236 
 237         // load library interface (static) field
 238         mv.visitFieldInsn(GETSTATIC, getClassName(),
 239             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc);
 240 
 241         // forward the call to the interface
 242         Type[] argTypes = Type.getArgumentTypes(desc);
 243         Type retType = Type.getReturnType(desc);
 244 
 245         int loadIdx = 0;
 246         for (int i = 0; i < argTypes.length; i++) {
 247             mv.visitVarInsn(argTypes[i].getOpcode(ILOAD), loadIdx);
 248             loadIdx += argTypes[i].getSize();
 249         }
 250         mv.visitMethodInsn(INVOKEINTERFACE, headerClassName, name, desc, true);
 251         mv.visitInsn(retType.getOpcode(IRETURN));
 252 
 253         mv.visitMaxs(0, 0);
 254         mv.visitEnd();
 255     }
 256 }