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.layout.Layout; 26 import java.io.PrintWriter; 27 import java.nio.file.Path; 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.HashMap; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.logging.Logger; 36 37 import jdk.internal.clang.SourceLocation; 38 import jdk.internal.clang.Type; 39 import jdk.internal.org.objectweb.asm.AnnotationVisitor; 40 import jdk.internal.org.objectweb.asm.ClassVisitor; 41 import jdk.internal.org.objectweb.asm.ClassWriter; 42 import jdk.internal.org.objectweb.asm.MethodVisitor; 43 import com.sun.tools.jextract.tree.EnumTree; 44 import com.sun.tools.jextract.tree.FieldTree; 45 import com.sun.tools.jextract.tree.FunctionTree; 46 import com.sun.tools.jextract.tree.MacroTree; 47 import com.sun.tools.jextract.tree.SimpleTreeVisitor; 48 import com.sun.tools.jextract.tree.StructTree; 49 import com.sun.tools.jextract.tree.Tree; 50 import com.sun.tools.jextract.tree.TypedefTree; 51 import com.sun.tools.jextract.tree.VarTree; 52 53 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT; 54 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ANNOTATION; 55 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE; 56 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; 57 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; 58 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS; 59 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN; 60 import static jdk.internal.org.objectweb.asm.Opcodes.DRETURN; 61 import static jdk.internal.org.objectweb.asm.Opcodes.FRETURN; 62 import static jdk.internal.org.objectweb.asm.Opcodes.I2C; 63 import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN; 64 import static jdk.internal.org.objectweb.asm.Opcodes.LRETURN; 65 import static jdk.internal.org.objectweb.asm.Opcodes.V1_8; 66 67 /** 68 * Scan a header file and generate classes for entities defined in that header 69 * file. Tree visitor visit methods return true/false depending on whether a 70 * particular Tree is processed or skipped. 71 */ 72 class AsmCodeFactory extends SimpleTreeVisitor<Boolean, JType> { 73 private static final String ANNOTATION_PKG_PREFIX = "Ljava/foreign/annotations/"; 74 private static final String NATIVE_CALLBACK = ANNOTATION_PKG_PREFIX + "NativeCallback;"; 75 private static final String NATIVE_HEADER = ANNOTATION_PKG_PREFIX + "NativeHeader;"; 76 private static final String NATIVE_LOCATION = ANNOTATION_PKG_PREFIX + "NativeLocation;"; 77 private static final String NATIVE_STRUCT = ANNOTATION_PKG_PREFIX + "NativeStruct;"; 78 79 private final ClassWriter global_cw; 80 // to avoid duplicate generation of methods, field accessors, macros 81 private final Set<String> global_methods = new HashSet<>(); 82 private final Set<String> global_fields = new HashSet<>(); 83 private final Set<String> global_macros = new HashSet<>(); 84 private final List<String> headerDeclarations = new ArrayList<>(); 85 protected final String headerClassName; 86 protected final HeaderFile headerFile; 87 protected final Map<String, byte[]> types; 88 protected final List<String> libraryNames; 89 protected final List<String> libraryPaths; 90 protected final PrintWriter err; 91 protected final boolean noNativeLocations; 92 protected final Logger logger = Logger.getLogger(getClass().getPackage().getName()); 93 94 AsmCodeFactory(Context ctx, HeaderFile header) { 95 logger.info(() -> "Instantiate AsmCodeFactory for " + header.path); 96 this.headerFile = header; 97 this.headerClassName = Utils.toInternalName(headerFile.pkgName, headerFile.clsName); 98 this.global_cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 99 this.types = new HashMap<>(); 100 this.libraryNames = ctx.libraryNames; 101 this.libraryPaths = ctx.libraryPaths; 102 this.err = ctx.err; 103 this.noNativeLocations = ctx.noNativeLocations; 104 global_cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, 105 headerClassName, 106 null, "java/lang/Object", null); 107 } 108 109 public Map<String, byte[]> generateNativeHeader(List<Tree> decls) { 110 //generate all decls 111 decls.forEach(this::generateDecl); 112 //generate functional interfaces 113 headerFile.dictionary().functionalInterfaces() 114 .forEach(fi -> createFunctionalInterface((JType.FunctionalInterfaceType)fi)); 115 116 //generate header intf 117 AnnotationVisitor av = global_cw.visitAnnotation(NATIVE_HEADER, true); 118 av.visit("path", headerFile.path.toAbsolutePath().toString()); 119 if (!libraryNames.isEmpty()) { 120 AnnotationVisitor libNames = av.visitArray("libraries"); 121 for (String name : libraryNames) { 122 libNames.visit(null, name); 123 } 124 libNames.visitEnd(); 125 if (!libraryPaths.isEmpty()) { 126 AnnotationVisitor libPaths = av.visitArray("libraryPaths"); 127 for (String path : libraryPaths) { 128 libPaths.visit(null, path); 129 } 130 libPaths.visitEnd(); 131 } 132 } 133 av.visit("declarations", String.join(" ", headerDeclarations)); 134 av.visitEnd(); 135 global_cw.visitEnd(); 136 addClassIfNeeded(headerClassName, global_cw.toByteArray()); 137 return Collections.unmodifiableMap(types); 138 } 139 140 private void handleException(Exception ex) { 141 err.println(Main.format("cannot.write.class.file", headerFile.pkgName + "." + headerFile.clsName, ex)); 142 if (Main.DEBUG) { 143 ex.printStackTrace(err); 144 } 145 } 146 147 private void annotateNativeLocation(ClassVisitor cw, Tree tree) { 148 if (! noNativeLocations) { 149 AnnotationVisitor av = cw.visitAnnotation(NATIVE_LOCATION, true); 150 SourceLocation src = tree.location(); 151 SourceLocation.Location loc = src.getFileLocation(); 152 Path p = loc.path(); 153 av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString()); 154 av.visit("line", loc.line()); 155 av.visit("column", loc.column()); 156 av.visitEnd(); 157 } 158 } 159 160 private void addClassIfNeeded(String clsName, byte[] bytes) { 161 if (null != types.put(clsName, bytes)) { 162 err.println(Main.format("warn.class.overwritten", clsName)); 163 } 164 } 165 166 private static boolean isBitField(Tree tree) { 167 return tree instanceof FieldTree && ((FieldTree)tree).isBitField(); 168 } 169 170 /** 171 * 172 * @param cw ClassWriter for the struct 173 * @param tree The Tree 174 * @param parentType The struct type 175 */ 176 private boolean addField(ClassVisitor cw, Tree tree, Type parentType) { 177 String fieldName = tree.name(); 178 assert !fieldName.isEmpty(); 179 Type type = tree.type(); 180 JType jt = headerFile.dictionary().lookup(type); 181 assert (jt != null); 182 if (cw == global_cw) { 183 String uniqueName = fieldName + "." + jt.getDescriptor(); 184 if (! global_fields.add(uniqueName)) { 185 return false; // added already 186 } 187 } 188 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$get", 189 "()" + jt.getDescriptor(), "()" + jt.getSignature(false), null); 190 191 if (! noNativeLocations) { 192 AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true); 193 SourceLocation src = tree.location(); 194 SourceLocation.Location loc = src.getFileLocation(); 195 Path p = loc.path(); 196 av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString()); 197 av.visit("line", loc.line()); 198 av.visit("column", loc.column()); 199 av.visitEnd(); 200 } 201 202 mv.visitEnd(); 203 mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$set", 204 "(" + jt.getDescriptor() + ")V", 205 "(" + jt.getSignature(true) + ")V", null); 206 mv.visitEnd(); 207 if (tree instanceof VarTree || !isBitField(tree)) { 208 JType ptrType = JType.GenericType.ofPointer(jt); 209 mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$ptr", 210 "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature(false), null); 211 mv.visitEnd(); 212 } 213 214 return true; 215 } 216 217 @Override 218 public Boolean visitVar(VarTree varTree, JType jt) { 219 if (addField(global_cw, varTree, null)) { 220 Layout layout = varTree.layout(); 221 String descStr = decorateAsAccessor(varTree, layout).toString(); 222 addHeaderDecl(varTree.name(), descStr); 223 return true; 224 } else { 225 return false; 226 } 227 } 228 229 private void addHeaderDecl(String symbol, String desc) { 230 headerDeclarations.add(String.format("%s=%s", symbol, desc)); 231 } 232 233 private void addConstant(ClassWriter cw, FieldTree fieldTree) { 234 assert (fieldTree.isEnumConstant()); 235 String name = fieldTree.name(); 236 String desc = headerFile.dictionary().lookup(fieldTree.type()).getDescriptor(); 237 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, "()" + desc, null, null); 238 mv.visitCode(); 239 if (desc.length() != 1) { 240 throw new AssertionError("expected single char descriptor: " + desc); 241 } 242 switch (desc.charAt(0)) { 243 case 'J': 244 long lvalue = fieldTree.enumConstant().get(); 245 mv.visitLdcInsn(lvalue); 246 mv.visitInsn(LRETURN); 247 break; 248 case 'I': 249 int ivalue = fieldTree.enumConstant().get().intValue(); 250 mv.visitLdcInsn(ivalue); 251 mv.visitInsn(IRETURN); 252 break; 253 default: 254 throw new AssertionError("should not reach here"); 255 } 256 mv.visitMaxs(1, 1); 257 mv.visitEnd(); 258 } 259 260 @Override 261 public Boolean visitStruct(StructTree structTree, JType jt) { 262 //generate nested structs recursively 263 structTree.nestedTypes().forEach(this::generateDecl); 264 265 if (structTree.isAnonymous()) { 266 //skip anonymous 267 return false; 268 } 269 String nativeName = structTree.name(); 270 Type type = structTree.type(); 271 logger.fine(() -> "Create struct: " + nativeName); 272 273 String intf = Utils.toClassName(nativeName); 274 String name = headerClassName + "$" + intf; 275 276 logger.fine(() -> "Define class " + name + " for native type " + nativeName); 277 /* FIXME: Member interface is implicit static, also ASM.CheckClassAdapter is not 278 * taking static as a valid flag, so comment this out during development. 279 */ 280 global_cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE); 281 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 282 cw.visit(V1_8, ACC_PUBLIC /*| ACC_STATIC*/ | ACC_INTERFACE | ACC_ABSTRACT, 283 name, "Ljava/lang/Object;Ljava/foreign/memory/Struct<L" + name + ";>;", 284 "java/lang/Object", new String[] {"java/foreign/memory/Struct"}); 285 annotateNativeLocation(cw, structTree); 286 287 AnnotationVisitor av = cw.visitAnnotation(NATIVE_STRUCT, true); 288 Layout structLayout = structTree.layout(this::decorateAsAccessor); 289 av.visit("value", structLayout.toString()); 290 av.visitEnd(); 291 cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE); 292 293 // fields 294 structTree.fields().forEach(fieldTree -> addField(cw, fieldTree, type)); 295 // Write class 296 cw.visitEnd(); 297 addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray()); 298 return true; 299 } 300 301 Layout addGetterSetterName(Layout layout, String accessorName) { 302 return layout 303 .withAnnotation("get", accessorName + "$get") 304 .withAnnotation("set", accessorName + "$set"); 305 } 306 307 Layout decorateAsAccessor(VarTree varTree, Layout layout) { 308 return addGetterSetterName(layout, varTree.name()). 309 withAnnotation("ptr", varTree.name() + "$ptr"); 310 } 311 312 Layout decorateAsAccessor(FieldTree fieldTree, Layout layout) { 313 layout = addGetterSetterName(layout, fieldTree.name()); 314 if (!fieldTree.isBitField()) { 315 //no pointer accessors for bitfield! 316 layout = layout.withAnnotation("ptr", fieldTree.name() + "$ptr"); 317 } 318 return layout; 319 } 320 321 @Override 322 public Boolean visitEnum(EnumTree enumTree, JType jt) { 323 // define enum constants in global_cw 324 enumTree.constants().forEach(constant -> addConstant(global_cw, constant)); 325 326 if (enumTree.name().isEmpty()) { 327 // We are done with anonymous enum 328 return true; 329 } 330 331 // generate annotation class for named enum 332 createAnnotationCls(enumTree); 333 return true; 334 } 335 336 private void createAnnotationCls(Tree tree) { 337 String nativeName = tree.name(); 338 logger.fine(() -> "Create annotation for: " + nativeName); 339 340 String intf = Utils.toClassName(nativeName); 341 String name = headerClassName + "$" + intf; 342 343 logger.fine(() -> "Define class " + name + " for native type " + nativeName); 344 global_cw.visitInnerClass(name, headerClassName, intf, 345 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION); 346 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 347 String[] superAnno = { "java/lang/annotation/Annotation" }; 348 cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION, 349 name, null, "java/lang/Object", superAnno); 350 annotateNativeLocation(cw, tree); 351 AnnotationVisitor av = cw.visitAnnotation("Ljava/lang/annotation/Target;", true); 352 av.visitEnum("value", "Ljava/lang/annotation/ElementType;", "TYPE_USE"); 353 av.visitEnd(); 354 av = cw.visitAnnotation("Ljava/lang/annotation/Retention;", true); 355 av.visitEnum("value", "Ljava/lang/annotation/RetentionPolicy;", "RUNTIME"); 356 av.visitEnd(); 357 cw.visitInnerClass(name, headerClassName, intf, 358 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION); 359 // Write class 360 cw.visitEnd(); 361 addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray()); 362 } 363 364 private void createFunctionalInterface(JType.FunctionalInterfaceType fnif) { 365 JType.Function fn = fnif.getFunction(); 366 String intf; 367 String nativeName; 368 String nDesc = fnif.getFunction().getNativeDescriptor(); 369 intf = fnif.getSimpleName(); 370 nativeName = "anonymous function"; 371 logger.fine(() -> "Create FunctionalInterface " + intf); 372 373 final String name = headerClassName + "$" + intf; 374 375 logger.fine(() -> "Define class " + name + " for native type " + nativeName + nDesc); 376 global_cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE); 377 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 378 cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, 379 name, "Ljava/lang/Object;", 380 "java/lang/Object", new String[0]); 381 AnnotationVisitor av = cw.visitAnnotation( 382 "Ljava/lang/FunctionalInterface;", true); 383 av.visitEnd(); 384 av = cw.visitAnnotation(NATIVE_CALLBACK, true); 385 av.visit("value", nDesc); 386 av.visitEnd(); 387 cw.visitInnerClass(name, headerClassName, intf, ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE); 388 389 // add the method 390 391 int flags = ACC_PUBLIC | ACC_ABSTRACT; 392 if (fn.isVarArgs) { 393 flags |= ACC_VARARGS; 394 } 395 MethodVisitor mv = cw.visitMethod(flags, "fn", 396 fn.getDescriptor(), fn.getSignature(false), null); 397 mv.visitEnd(); 398 // Write class 399 cw.visitEnd(); 400 addClassIfNeeded(headerClassName + "$" + intf, cw.toByteArray()); 401 } 402 403 @Override 404 public Boolean visitTypedef(TypedefTree typedefTree, JType jt) { 405 createAnnotationCls(typedefTree); 406 return true; 407 } 408 409 @Override 410 public Boolean visitTree(Tree tree, JType jt) { 411 logger.warning(() -> "Unsupported declaration tree:"); 412 logger.warning(() -> tree.toString()); 413 return true; 414 } 415 416 @Override 417 public Boolean visitFunction(FunctionTree funcTree, JType jt) { 418 assert (jt instanceof JType.Function); 419 JType.Function fn = (JType.Function)jt; 420 String uniqueName = funcTree.name() + "." + fn.getDescriptor(); 421 if (! global_methods.add(uniqueName)) { 422 return false; // added already 423 } 424 logger.fine(() -> "Add method: " + fn.getSignature(false)); 425 int flags = ACC_PUBLIC | ACC_ABSTRACT; 426 if (fn.isVarArgs) { 427 flags |= ACC_VARARGS; 428 } 429 MethodVisitor mv = global_cw.visitMethod(flags, 430 funcTree.name(), fn.getDescriptor(), fn.getSignature(false), null); 431 final int arg_cnt = funcTree.numParams(); 432 for (int i = 0; i < arg_cnt; i++) { 433 String name = funcTree.paramName(i); 434 final int tmp = i; 435 logger.finer(() -> " arg " + tmp + ": " + name); 436 mv.visitParameter(name, 0); 437 } 438 439 if (! noNativeLocations) { 440 AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true); 441 SourceLocation src = funcTree.location(); 442 SourceLocation.Location loc = src.getFileLocation(); 443 Path p = loc.path(); 444 av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString()); 445 av.visit("line", loc.line()); 446 av.visit("column", loc.column()); 447 av.visitEnd(); 448 } 449 450 Type type = funcTree.type(); 451 final String descStr = Utils.getFunction(type).toString(); 452 addHeaderDecl(funcTree.name(), descStr); 453 454 mv.visitEnd(); 455 return true; 456 } 457 458 private AsmCodeFactory generateDecl(Tree tree) { 459 try { 460 logger.fine(() -> "Process tree " + tree.name()); 461 tree.accept(this, tree.isPreprocessing() ? null : headerFile.dictionary().lookup(tree.type())); 462 } catch (Exception ex) { 463 handleException(ex); 464 logger.warning("Tree causing above exception is: " + tree.name()); 465 logger.warning(() -> tree.toString()); 466 } 467 return this; 468 } 469 470 @Override 471 public Boolean visitMacro(MacroTree macroTree, JType jt) { 472 if (!macroTree.isConstant()) { 473 logger.fine(() -> "Skipping unrecognized object-like macro " + macroTree.name()); 474 return false; 475 } 476 String name = macroTree.name(); 477 Object value = macroTree.value().get(); 478 if (! global_macros.add(name)) { 479 return false; // added already 480 } 481 logger.fine(() -> "Adding macro " + name); 482 Class<?> macroType = Utils.unboxIfNeeded(value.getClass()); 483 484 String sig = jdk.internal.org.objectweb.asm.Type.getMethodDescriptor(jdk.internal.org.objectweb.asm.Type.getType(macroType)); 485 MethodVisitor mv = global_cw.visitMethod(ACC_PUBLIC, name, sig, sig, null); 486 487 if (! noNativeLocations) { 488 AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true); 489 SourceLocation src = macroTree.location(); 490 SourceLocation.Location loc = src.getFileLocation(); 491 Path p = loc.path(); 492 av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString()); 493 av.visit("line", loc.line()); 494 av.visit("column", loc.column()); 495 av.visitEnd(); 496 } 497 498 mv.visitCode(); 499 500 mv.visitLdcInsn(value); 501 if (macroType.equals(char.class)) { 502 mv.visitInsn(I2C); 503 mv.visitInsn(IRETURN); 504 } else if (macroType.equals(int.class)) { 505 mv.visitInsn(IRETURN); 506 } else if (macroType.equals(float.class)) { 507 mv.visitInsn(FRETURN); 508 } else if (macroType.equals(long.class)) { 509 mv.visitInsn(LRETURN); 510 } else if (macroType.equals(double.class)) { 511 mv.visitInsn(DRETURN); 512 } else if (macroType.equals(String.class)) { 513 mv.visitInsn(ARETURN); 514 } 515 mv.visitMaxs(0, 0); 516 mv.visitEnd(); 517 return true; 518 } 519 } --- EOF ---