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