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.foreign.Scope;
  27 import java.lang.invoke.MethodHandles;
  28 import java.lang.invoke.MethodHandles.Lookup;
  29 import java.lang.invoke.MethodType;
  30 import java.lang.reflect.Method;
  31 import java.util.ArrayList;
  32 import java.util.Collections;
  33 import java.util.HashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.function.Consumer;
  37 import java.util.logging.Level;
  38 
  39 import com.sun.tools.jextract.parser.MacroParser;
  40 import com.sun.tools.jextract.tree.Tree;
  41 import jdk.internal.org.objectweb.asm.FieldVisitor;
  42 import jdk.internal.org.objectweb.asm.ClassWriter;
  43 import jdk.internal.org.objectweb.asm.MethodVisitor;
  44 import jdk.internal.org.objectweb.asm.Type;
  45 import com.sun.tools.jextract.tree.EnumTree;
  46 import com.sun.tools.jextract.tree.FieldTree;
  47 import com.sun.tools.jextract.tree.FunctionTree;
  48 import com.sun.tools.jextract.tree.MacroTree;
  49 import com.sun.tools.jextract.tree.VarTree;
  50 
  51 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
  52 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL;
  53 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE;
  54 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  55 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE;
  56 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  57 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN;
  58 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS;
  59 import static jdk.internal.org.objectweb.asm.Opcodes.CHECKCAST;
  60 import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC;
  61 import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
  62 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE;
  63 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
  64 import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
  65 import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
  66 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
  67 import static jdk.internal.org.objectweb.asm.Opcodes.V1_8;
  68 
  69 /**
  70  * This extended factory generates a class with only static methods and fields. A native
  71  * library interface instance (of the given header file) is kept as a static private field.
  72  * One static method is generated for every library interface method. Enum and macro constants
  73  * are mapped to static final fields. By importing the "static forwarder" class, the user code
  74  * looks more or less like C code. Libraries.bind and header interface usage is hidden.
  75  */
  76 final class AsmCodeFactoryExt extends AsmCodeFactory {
  77     private final String headerClassNameDesc;
  78     private final String forwarderClassName;
  79     private final ClassWriter cw;
  80     // suffix for static forwarder class name
  81     // field name for the header interface instance.
  82     private static final String STATICS_LIBRARY_FIELD_NAME = "_theLibrary";
  83 
  84     private final List<Consumer<MethodVisitor>> constantInitializers = new ArrayList<>();
  85     private final List<EnumFactory> enumFactories = new ArrayList<>();
  86 
  87     AsmCodeFactoryExt(Context ctx, HeaderFile header) {
  88         super(ctx, header);
  89         log.print(Level.INFO, () -> "Instantiate StaticForwarderGenerator for " + header.path);
  90         this.headerClassNameDesc = "L" + headerClassName + ";";
  91         this.forwarderClassName = Utils.toInternalName(header.pkgName, header.staticForwarderClsName);
  92         this.cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
  93         this.cw.visit(V1_8, ACC_PUBLIC | ACC_FINAL, getClassName(),
  94             null, "java/lang/Object", null);
  95         scopeAccessor();
  96     }
  97 
  98     private class EnumFactory {
  99         private final EnumTree enumTree;
 100         private final String enumClassName;
 101 
 102         EnumFactory(EnumTree enumTree) {
 103             log.print(Level.INFO, () -> "Instantiate EnumFactory for " + enumTree.name());
 104             this.enumTree = enumTree;
 105             this.enumClassName = AsmCodeFactoryExt.this.getClassName() + "$" + enumTree.name();
 106 
 107         }
 108 
 109         String getEnumName() {
 110             return enumTree.name();
 111         }
 112 
 113         String getClassName() {
 114             return enumClassName;
 115         }
 116 
 117         byte[] getClassBytes() {
 118             return generate();
 119         }
 120 
 121         private byte[] generate() {
 122             ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
 123             cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, enumClassName,
 124                 null, "java/lang/Object", null);
 125             cw.visitInnerClass(enumClassName, AsmCodeFactoryExt.this.getClassName(), enumTree.name(), ACC_PUBLIC | ACC_FINAL);
 126             enumTree.constants().forEach(constant -> {
 127                 String name = constant.name();
 128                 JType type = headerFile.dictionary().lookup(constant.type());
 129                 Object constantValue = makeConstantValue(type, constant.enumConstant().get());
 130                 FieldVisitor fv = cw.visitField(ACC_PUBLIC | ACC_STATIC | ACC_FINAL, name, type.getDescriptor(),
 131                         type.getSignature(false), constantValue);
 132                 fv.visitEnd();
 133             });
 134 
 135             cw.visitEnd();
 136             return cw.toByteArray();
 137         }
 138     }
 139 
 140     @Override
 141     public Boolean visitVar(VarTree varTree, JType jt) {
 142         if (super.visitVar(varTree, jt)) {
 143             String fieldName = varTree.name();
 144             assert !fieldName.isEmpty();
 145 
 146             emitStaticForwarder(fieldName + "$get",
 147                 "()" + jt.getDescriptor(), "()" + jt.getSignature(false), false);
 148             jt.visitInner(cw);
 149 
 150             emitStaticForwarder(fieldName + "$set",
 151                 "(" + jt.getDescriptor() + ")V",
 152                 "(" + jt.getSignature(true) + ")V", false);
 153             JType ptrType = JType.GenericType.ofPointer(jt);
 154             emitStaticForwarder(fieldName + "$ptr",
 155                 "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature(false), false);
 156             ptrType.visitInner(cw);
 157 
 158             return true;
 159         } else {
 160             return false;
 161         }
 162     }
 163 
 164     @Override
 165     public Boolean visitEnum(EnumTree enumTree, JType jt) {
 166         if (super.visitEnum(enumTree, jt)) {
 167             if (enumTree.name().isEmpty()) {
 168                 enumTree.constants().forEach(constant -> addConstant(constant.name(),
 169                     headerFile.dictionary().lookup(constant.type()),
 170                     constant.enumConstant().get()));
 171             } else {
 172                 EnumFactory ef = new EnumFactory(enumTree);
 173                 enumFactories.add(ef);
 174                 cw.visitInnerClass(ef.getClassName(), getClassName(), ef.getEnumName(),
 175                     ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE);
 176             }
 177             return true;
 178         } else {
 179             return false;
 180         }
 181     }
 182 
 183     @Override
 184     public Boolean visitFunction(FunctionTree funcTree, JType jt) {
 185         if (super.visitFunction(funcTree, jt)) {
 186             assert (jt instanceof JType.Function);
 187             JType.Function fn = (JType.Function)jt;
 188             log.print(Level.FINE, () -> "Add method: " + fn.getSignature(false));
 189             emitStaticForwarder(funcTree.name(), fn.getDescriptor(), fn.getSignature(false), fn.isVarArgs);
 190             fn.visitInner(cw);
 191             return true;
 192         } else {
 193             return false;
 194         }
 195     }
 196 
 197     @Override
 198     public Boolean visitMacro(MacroTree macroTree, JType jt) {
 199         if (super.visitMacro(macroTree, jt)) {
 200             String name = macroTree.name();
 201             MacroParser.Macro macro = macroTree.macro().get();
 202             log.print(Level.FINE, () -> "Adding macro " + name);
 203             addConstant(Utils.toMacroName(name), macro.type(), macro.value());
 204             return true;
 205         } else {
 206             return false;
 207         }
 208     }
 209 
 210     private void addConstant(String name, JType type, Object value) {
 211         Object constantValue = makeConstantValue(type, value);
 212         FieldVisitor fv = cw.visitField(ACC_PUBLIC | ACC_STATIC | ACC_FINAL, name, type.getDescriptor(),
 213                 type.getSignature(false), constantValue);
 214         fv.visitEnd();
 215         if (constantValue == null) {
 216             constantInitializers.add(mv -> {
 217                 // load library interface (static) field
 218                 String desc = type.getDescriptor();
 219                 mv.visitFieldInsn(GETSTATIC, getClassName(),
 220                         STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc);
 221                 mv.visitMethodInsn(INVOKEINTERFACE, headerClassName, name, "()" + desc, true);
 222                 mv.visitFieldInsn(PUTSTATIC, getClassName(), name, desc);
 223             });
 224         }
 225     }
 226 
 227     private Object makeConstantValue(JType type, Object value) {
 228         switch (type.getDescriptor()) {
 229             case "Z":
 230                 return ((long)value) != 0;
 231             case "C":
 232                 return (char)(long)value;
 233             case "B":
 234                 return (byte)(long)value;
 235             case "S":
 236                 return (short)(long)value;
 237             case "I":
 238                 return (int)(long)value;
 239             case "F":
 240                 return (float)(double)value;
 241             case "J": case "D":
 242                 return value;
 243             default:
 244                 return null;
 245         }
 246     }
 247 
 248     @Override
 249     public Map<String, byte[]> generateNativeHeader(List<Tree> decls) {
 250         Map<String, byte[]> results = new HashMap<>();
 251         results.putAll(super.generateNativeHeader(decls));
 252         staticsInitializer();
 253         results.put(getClassName(), getClassBytes());
 254         for (EnumFactory ef : enumFactories) {
 255             results.put(ef.getClassName(), ef.getClassBytes());
 256         }
 257         return Collections.unmodifiableMap(results);
 258     }
 259 
 260     // Internals only below this point
 261 
 262     private String getClassName() {
 263         return forwarderClassName;
 264     }
 265 
 266     // return the generated static forwarder class bytes
 267     private byte[] getClassBytes() {
 268         cw.visitEnd();
 269         return cw.toByteArray();
 270     }
 271 
 272     // emit library interface static field and <clinit> initializer for that field
 273     private void staticsInitializer() {
 274         // library interface field
 275         FieldVisitor fv = cw.visitField(ACC_PRIVATE|ACC_STATIC|ACC_FINAL,
 276             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc, null, null);
 277         fv.visitEnd();
 278 
 279         // <clinit> to bind library interface field
 280         MethodVisitor mv = cw.visitMethod(ACC_STATIC,
 281             "<clinit>", "()V", null, null);
 282         mv.visitCode();
 283 
 284         // MethodHandles.lookup()
 285         Method lookupMethod = null;
 286         try {
 287             lookupMethod = MethodHandles.class.getMethod("lookup");
 288         } catch (NoSuchMethodException nsme) {
 289             throw new RuntimeException(nsme);
 290         }
 291         mv.visitMethodInsn(INVOKESTATIC,
 292             Type.getInternalName(MethodHandles.class), "lookup",
 293             Type.getMethodDescriptor(lookupMethod), false);
 294 
 295         // ldc library-interface-class
 296         mv.visitLdcInsn(Type.getObjectType(headerClassName));
 297 
 298         // Libraries.bind(lookup, class);
 299         Method bindMethod = null;
 300         try {
 301             bindMethod = Libraries.class.getMethod("bind", Lookup.class, Class.class);
 302         } catch (NoSuchMethodException nsme) {
 303             throw new RuntimeException(nsme);
 304         }
 305         mv.visitMethodInsn(INVOKESTATIC,
 306             Type.getInternalName(Libraries.class), "bind",
 307             Type.getMethodDescriptor(bindMethod), false);
 308 
 309         // store it in library interface field
 310         mv.visitTypeInsn(CHECKCAST, headerClassName);
 311         mv.visitFieldInsn(PUTSTATIC, getClassName(),
 312             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc);
 313 
 314         constantInitializers.forEach(init -> init.accept(mv));
 315 
 316         mv.visitInsn(RETURN);
 317         mv.visitMaxs(0, 0);
 318         mv.visitEnd();
 319     }
 320 
 321     private void scopeAccessor() {
 322         String scopeAccessorDesc = MethodType.methodType(Scope.class).descriptorString();
 323         MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "scope", scopeAccessorDesc, null, null);
 324         mv.visitCode();
 325 
 326         // load library interface (static) field
 327         mv.visitFieldInsn(GETSTATIC, getClassName(),
 328             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc);
 329 
 330         String libraryScopeDesc = MethodType.methodType(Scope.class, Object.class).descriptorString();
 331         mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Libraries.class), "libraryScope", libraryScopeDesc, false);
 332         mv.visitInsn(ARETURN);
 333         mv.visitMaxs(1,1);
 334         mv.visitEnd();
 335     }
 336 
 337     // emit static forwarder method for a specific library interface method
 338     private void emitStaticForwarder(String name, String desc, String signature, boolean isVarArgs) {
 339         int accessFlags = ACC_PUBLIC | ACC_STATIC;
 340         if (isVarArgs) {
 341             accessFlags |= ACC_VARARGS;
 342         }
 343 
 344         MethodVisitor mv = cw.visitMethod(accessFlags, name, desc, signature, null);
 345         mv.visitCode();
 346 
 347         // load library interface (static) field
 348         mv.visitFieldInsn(GETSTATIC, getClassName(),
 349             STATICS_LIBRARY_FIELD_NAME, headerClassNameDesc);
 350 
 351         // forward the call to the interface
 352         Type[] argTypes = Type.getArgumentTypes(desc);
 353         Type retType = Type.getReturnType(desc);
 354 
 355         int loadIdx = 0;
 356         for (int i = 0; i < argTypes.length; i++) {
 357             mv.visitVarInsn(argTypes[i].getOpcode(ILOAD), loadIdx);
 358             loadIdx += argTypes[i].getSize();
 359         }
 360         mv.visitMethodInsn(INVOKEINTERFACE, headerClassName, name, desc, true);
 361         mv.visitInsn(retType.getOpcode(IRETURN));
 362 
 363         mv.visitMaxs(0, 0);
 364         mv.visitEnd();
 365     }
 366 }