73 * file. 74 */ 75 final class AsmCodeFactory extends SimpleTreeVisitor<Void, JType> { 76 private static final String ANNOTATION_PKG_PREFIX = "Ljava/foreign/annotations/"; 77 private static final String NATIVE_CALLBACK = ANNOTATION_PKG_PREFIX + "NativeCallback;"; 78 private static final String NATIVE_HEADER = ANNOTATION_PKG_PREFIX + "NativeHeader;"; 79 private static final String NATIVE_LOCATION = ANNOTATION_PKG_PREFIX + "NativeLocation;"; 80 private static final String NATIVE_STRUCT = ANNOTATION_PKG_PREFIX + "NativeStruct;"; 81 82 private final Context ctx; 83 private final ClassWriter global_cw; 84 // to avoid duplicate generation of methods, field accessors, macros 85 private final Set<String> global_methods = new HashSet<>(); 86 private final Set<String> global_fields = new HashSet<>(); 87 private final Set<String> global_macros = new HashSet<>(); 88 private final String internal_name; 89 private final HeaderFile owner; 90 private final Map<String, byte[]> types; 91 private final Logger logger = Logger.getLogger(getClass().getPackage().getName()); 92 private final List<String> headerDeclarations = new ArrayList<>(); 93 private transient boolean built = false; 94 95 AsmCodeFactory(Context ctx, HeaderFile header) { 96 this.ctx = ctx; 97 logger.info(() -> "Instantiate AsmCodeFactory for " + header.path); 98 this.owner = header; 99 this.internal_name = Utils.toInternalName(owner.pkgName, owner.clsName); 100 this.global_cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 101 this.types = new HashMap<>(); 102 global_cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, 103 internal_name, 104 null, "java/lang/Object", null); 105 } 106 107 private void generateNativeHeader() { 108 AnnotationVisitor av = global_cw.visitAnnotation(NATIVE_HEADER, true); 109 av.visit("path", owner.path.toAbsolutePath().toString()); 110 if (owner.libraries != null && !owner.libraries.isEmpty()) { 111 AnnotationVisitor libNames = av.visitArray("libraries"); 112 for (String name : owner.libraries) { 113 libNames.visit(null, name); 114 } 115 libNames.visitEnd(); 116 if (owner.libraryPaths != null && !owner.libraryPaths.isEmpty()) { 117 AnnotationVisitor libPaths = av.visitArray("libraryPaths"); 118 for (String path : owner.libraryPaths) { 119 libPaths.visit(null, path); 120 } 121 libPaths.visitEnd(); 122 } 123 } 124 av.visit("declarations", String.join(" ", headerDeclarations)); 172 if (cw == global_cw) { 173 String uniqueName = fieldName + "." + jt.getDescriptor(); 174 if (! global_fields.add(uniqueName)) { 175 return; // added already 176 } 177 } 178 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$get", 179 "()" + jt.getDescriptor(), "()" + jt.getSignature(), null); 180 181 AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true); 182 SourceLocation src = tree.location(); 183 SourceLocation.Location loc = src.getFileLocation(); 184 Path p = loc.path(); 185 av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString()); 186 av.visit("line", loc.line()); 187 av.visit("column", loc.column()); 188 av.visit("USR", tree.USR()); 189 av.visitEnd(); 190 191 mv.visitEnd(); 192 cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$set", 193 "(" + jt.getDescriptor() + ")V", 194 "(" + JType.getPointerVoidAsWildcard(jt) + ")V", null); 195 if (tree instanceof VarTree || !isBitField(tree)) { 196 JType ptrType = new PointerType(jt); 197 cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$ptr", 198 "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature(), null); 199 } 200 } 201 202 @Override 203 public Void visitVar(VarTree varTree, JType jt) { 204 addField(global_cw, varTree, null); 205 Layout layout = varTree.layout(); 206 String descStr = decorateAsAccessor(varTree, layout).toString(); 207 addHeaderDecl(varTree.name(), descStr); 208 return null; 209 } 210 211 private void addHeaderDecl(String symbol, String desc) { 212 headerDeclarations.add(String.format("%s=%s", symbol, desc)); 213 } 214 215 private void addConstant(ClassWriter cw, FieldTree fieldTree) { 216 assert (fieldTree.isEnumConstant()); 217 String name = fieldTree.name(); 218 String desc = owner.globalLookup(fieldTree.type()).getDescriptor(); 219 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, "()" + desc, null, null); 220 mv.visitCode(); 221 if (desc.length() != 1) { 222 throw new AssertionError("expected single char descriptor: " + desc); 223 } 224 switch (desc.charAt(0)) { 225 case 'J': 226 long lvalue = fieldTree.enumConstant().get(); 227 mv.visitLdcInsn(lvalue); 291 layout = addGetterSetterName(layout, fieldTree.name()); 292 if (!fieldTree.isBitField()) { 293 //no pointer accessors for bitfield! 294 layout = layout.withAnnotation("ptr", fieldTree.name() + "$ptr"); 295 } 296 return layout; 297 } 298 299 @Override 300 public Void visitEnum(EnumTree enumTree, JType jt) { 301 // define enum constants in global_cw 302 enumTree.constants().forEach(constant -> addConstant(global_cw, constant)); 303 304 if (enumTree.name().isEmpty()) { 305 // We are done with anonymous enum 306 return null; 307 } 308 309 // generate annotation class for named enum 310 createAnnotationCls(enumTree); 311 return null; 312 } 313 314 private void createAnnotationCls(Tree tree) { 315 String nativeName = tree.name(); 316 logger.fine(() -> "Create annotation for: " + nativeName); 317 318 String intf = Utils.toClassName(nativeName); 319 String name = internal_name + "$" + intf; 320 321 logger.fine(() -> "Define class " + name + " for native type " + nativeName); 322 global_cw.visitInnerClass(name, internal_name, intf, 323 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION); 324 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 325 String[] superAnno = { "java/lang/annotation/Annotation" }; 326 cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION, 327 name, null, "java/lang/Object", superAnno); 328 annotateNativeLocation(cw, tree); 329 Type type = tree.type().canonicalType(); 330 AnnotationVisitor av = cw.visitAnnotation("Ljava/lang/annotation/Target;", true); 463 mv.visitTypeAnnotation( 464 TypeReference.newFormalParameterReference(idx).getValue(), 465 null, alias.getAnnotationDescriptor(), true) 466 .visitEnd(); 467 } 468 } 469 idx++; 470 } 471 472 if (fn.returnType instanceof TypeAlias) { 473 TypeAlias alias = (TypeAlias) fn.returnType; 474 logger.finest(() -> " return type is an alias " + alias); 475 if (alias.getAnnotationDescriptor() != null) { 476 mv.visitTypeAnnotation( 477 TypeReference.newTypeReference(TypeReference.METHOD_RETURN).getValue(), 478 null, alias.getAnnotationDescriptor(), true) 479 .visitEnd(); 480 } 481 } 482 mv.visitEnd(); 483 return null; 484 } 485 486 protected AsmCodeFactory addType(JType jt, Tree tree) { 487 JType2 jt2 = null; 488 if (jt instanceof JType2) { 489 jt2 = (JType2) jt; 490 jt = jt2.getDelegate(); 491 } else { 492 logger.warning(() -> "Should have JType2 in addType"); 493 if (Main.DEBUG) { 494 new Throwable().printStackTrace(ctx.err); 495 } 496 } 497 if (tree == null) { 498 assert (jt2 != null); 499 if (jt instanceof JType.FnIf) { 500 createFunctionalInterface(null, (JType.FnIf) jt); 501 } 502 return this; 549 550 mv.visitCode(); 551 552 mv.visitLdcInsn(value); 553 if (macroType.equals(char.class)) { 554 mv.visitInsn(I2C); 555 mv.visitInsn(IRETURN); 556 } else if (macroType.equals(int.class)) { 557 mv.visitInsn(IRETURN); 558 } else if (macroType.equals(float.class)) { 559 mv.visitInsn(FRETURN); 560 } else if (macroType.equals(long.class)) { 561 mv.visitInsn(LRETURN); 562 } else if (macroType.equals(double.class)) { 563 mv.visitInsn(DRETURN); 564 } else if (macroType.equals(String.class)) { 565 mv.visitInsn(ARETURN); 566 } 567 mv.visitMaxs(0, 0); 568 mv.visitEnd(); 569 return null; 570 } 571 572 protected synchronized void produce() { 573 if (built) { 574 throw new IllegalStateException("Produce is called multiple times"); 575 } 576 built = true; 577 generateNativeHeader(); 578 try { 579 writeClassFile(global_cw, owner.clsName); 580 } catch (IOException ex) { 581 handleException(ex); 582 } 583 } 584 585 protected Map<String, byte[]> collect() { 586 // Ensure classes are produced 587 if (!built) produce(); 588 HashMap<String, byte[]> rv = new HashMap<>(); 589 // Not copying byte[] for efficiency, perhaps not a safe idea though 590 if (owner.pkgName.isEmpty()) { 591 types.forEach((clsName, bytecodes) -> { 592 rv.put(clsName, bytecodes); 593 }); 594 } else { 595 types.forEach((clsName, bytecodes) -> { 596 rv.put(owner.pkgName + "." + clsName, bytecodes); 597 }); 598 } 599 return Collections.unmodifiableMap(rv); 600 } 601 } | 73 * file. 74 */ 75 final class AsmCodeFactory extends SimpleTreeVisitor<Void, JType> { 76 private static final String ANNOTATION_PKG_PREFIX = "Ljava/foreign/annotations/"; 77 private static final String NATIVE_CALLBACK = ANNOTATION_PKG_PREFIX + "NativeCallback;"; 78 private static final String NATIVE_HEADER = ANNOTATION_PKG_PREFIX + "NativeHeader;"; 79 private static final String NATIVE_LOCATION = ANNOTATION_PKG_PREFIX + "NativeLocation;"; 80 private static final String NATIVE_STRUCT = ANNOTATION_PKG_PREFIX + "NativeStruct;"; 81 82 private final Context ctx; 83 private final ClassWriter global_cw; 84 // to avoid duplicate generation of methods, field accessors, macros 85 private final Set<String> global_methods = new HashSet<>(); 86 private final Set<String> global_fields = new HashSet<>(); 87 private final Set<String> global_macros = new HashSet<>(); 88 private final String internal_name; 89 private final HeaderFile owner; 90 private final Map<String, byte[]> types; 91 private final Logger logger = Logger.getLogger(getClass().getPackage().getName()); 92 private final List<String> headerDeclarations = new ArrayList<>(); 93 private final StaticForwarderGenerator staticForwardGen; 94 95 private transient boolean built = false; 96 97 AsmCodeFactory(Context ctx, HeaderFile header) { 98 this.ctx = ctx; 99 logger.info(() -> "Instantiate AsmCodeFactory for " + header.path); 100 this.owner = header; 101 this.internal_name = Utils.toInternalName(owner.pkgName, owner.clsName); 102 this.global_cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 103 this.types = new HashMap<>(); 104 global_cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE, 105 internal_name, 106 null, "java/lang/Object", null); 107 if (ctx.getGenStaticForwarder()) { 108 this.staticForwardGen = new StaticForwarderGenerator(header); 109 } else { 110 this.staticForwardGen = null; 111 } 112 } 113 114 private void generateNativeHeader() { 115 AnnotationVisitor av = global_cw.visitAnnotation(NATIVE_HEADER, true); 116 av.visit("path", owner.path.toAbsolutePath().toString()); 117 if (owner.libraries != null && !owner.libraries.isEmpty()) { 118 AnnotationVisitor libNames = av.visitArray("libraries"); 119 for (String name : owner.libraries) { 120 libNames.visit(null, name); 121 } 122 libNames.visitEnd(); 123 if (owner.libraryPaths != null && !owner.libraryPaths.isEmpty()) { 124 AnnotationVisitor libPaths = av.visitArray("libraryPaths"); 125 for (String path : owner.libraryPaths) { 126 libPaths.visit(null, path); 127 } 128 libPaths.visitEnd(); 129 } 130 } 131 av.visit("declarations", String.join(" ", headerDeclarations)); 179 if (cw == global_cw) { 180 String uniqueName = fieldName + "." + jt.getDescriptor(); 181 if (! global_fields.add(uniqueName)) { 182 return; // added already 183 } 184 } 185 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$get", 186 "()" + jt.getDescriptor(), "()" + jt.getSignature(), null); 187 188 AnnotationVisitor av = mv.visitAnnotation(NATIVE_LOCATION, true); 189 SourceLocation src = tree.location(); 190 SourceLocation.Location loc = src.getFileLocation(); 191 Path p = loc.path(); 192 av.visit("file", p == null ? "builtin" : p.toAbsolutePath().toString()); 193 av.visit("line", loc.line()); 194 av.visit("column", loc.column()); 195 av.visit("USR", tree.USR()); 196 av.visitEnd(); 197 198 mv.visitEnd(); 199 mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$set", 200 "(" + jt.getDescriptor() + ")V", 201 "(" + JType.getPointerVoidAsWildcard(jt) + ")V", null); 202 mv.visitEnd(); 203 if (tree instanceof VarTree || !isBitField(tree)) { 204 JType ptrType = new PointerType(jt); 205 mv = cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, fieldName + "$ptr", 206 "()" + ptrType.getDescriptor(), "()" + ptrType.getSignature(), null); 207 mv.visitEnd(); 208 } 209 } 210 211 @Override 212 public Void visitVar(VarTree varTree, JType jt) { 213 addField(global_cw, varTree, null); 214 Layout layout = varTree.layout(); 215 String descStr = decorateAsAccessor(varTree, layout).toString(); 216 addHeaderDecl(varTree.name(), descStr); 217 if (staticForwardGen != null) { 218 staticForwardGen.visitVar(varTree, jt); 219 } 220 return null; 221 } 222 223 private void addHeaderDecl(String symbol, String desc) { 224 headerDeclarations.add(String.format("%s=%s", symbol, desc)); 225 } 226 227 private void addConstant(ClassWriter cw, FieldTree fieldTree) { 228 assert (fieldTree.isEnumConstant()); 229 String name = fieldTree.name(); 230 String desc = owner.globalLookup(fieldTree.type()).getDescriptor(); 231 MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, "()" + desc, null, null); 232 mv.visitCode(); 233 if (desc.length() != 1) { 234 throw new AssertionError("expected single char descriptor: " + desc); 235 } 236 switch (desc.charAt(0)) { 237 case 'J': 238 long lvalue = fieldTree.enumConstant().get(); 239 mv.visitLdcInsn(lvalue); 303 layout = addGetterSetterName(layout, fieldTree.name()); 304 if (!fieldTree.isBitField()) { 305 //no pointer accessors for bitfield! 306 layout = layout.withAnnotation("ptr", fieldTree.name() + "$ptr"); 307 } 308 return layout; 309 } 310 311 @Override 312 public Void visitEnum(EnumTree enumTree, JType jt) { 313 // define enum constants in global_cw 314 enumTree.constants().forEach(constant -> addConstant(global_cw, constant)); 315 316 if (enumTree.name().isEmpty()) { 317 // We are done with anonymous enum 318 return null; 319 } 320 321 // generate annotation class for named enum 322 createAnnotationCls(enumTree); 323 if (staticForwardGen != null) { 324 staticForwardGen.visitEnum(enumTree, jt); 325 } 326 return null; 327 } 328 329 private void createAnnotationCls(Tree tree) { 330 String nativeName = tree.name(); 331 logger.fine(() -> "Create annotation for: " + nativeName); 332 333 String intf = Utils.toClassName(nativeName); 334 String name = internal_name + "$" + intf; 335 336 logger.fine(() -> "Define class " + name + " for native type " + nativeName); 337 global_cw.visitInnerClass(name, internal_name, intf, 338 ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION); 339 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 340 String[] superAnno = { "java/lang/annotation/Annotation" }; 341 cw.visit(V1_8, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE | ACC_ANNOTATION, 342 name, null, "java/lang/Object", superAnno); 343 annotateNativeLocation(cw, tree); 344 Type type = tree.type().canonicalType(); 345 AnnotationVisitor av = cw.visitAnnotation("Ljava/lang/annotation/Target;", true); 478 mv.visitTypeAnnotation( 479 TypeReference.newFormalParameterReference(idx).getValue(), 480 null, alias.getAnnotationDescriptor(), true) 481 .visitEnd(); 482 } 483 } 484 idx++; 485 } 486 487 if (fn.returnType instanceof TypeAlias) { 488 TypeAlias alias = (TypeAlias) fn.returnType; 489 logger.finest(() -> " return type is an alias " + alias); 490 if (alias.getAnnotationDescriptor() != null) { 491 mv.visitTypeAnnotation( 492 TypeReference.newTypeReference(TypeReference.METHOD_RETURN).getValue(), 493 null, alias.getAnnotationDescriptor(), true) 494 .visitEnd(); 495 } 496 } 497 mv.visitEnd(); 498 if (staticForwardGen != null) { 499 staticForwardGen.visitFunction(funcTree, jt); 500 } 501 return null; 502 } 503 504 protected AsmCodeFactory addType(JType jt, Tree tree) { 505 JType2 jt2 = null; 506 if (jt instanceof JType2) { 507 jt2 = (JType2) jt; 508 jt = jt2.getDelegate(); 509 } else { 510 logger.warning(() -> "Should have JType2 in addType"); 511 if (Main.DEBUG) { 512 new Throwable().printStackTrace(ctx.err); 513 } 514 } 515 if (tree == null) { 516 assert (jt2 != null); 517 if (jt instanceof JType.FnIf) { 518 createFunctionalInterface(null, (JType.FnIf) jt); 519 } 520 return this; 567 568 mv.visitCode(); 569 570 mv.visitLdcInsn(value); 571 if (macroType.equals(char.class)) { 572 mv.visitInsn(I2C); 573 mv.visitInsn(IRETURN); 574 } else if (macroType.equals(int.class)) { 575 mv.visitInsn(IRETURN); 576 } else if (macroType.equals(float.class)) { 577 mv.visitInsn(FRETURN); 578 } else if (macroType.equals(long.class)) { 579 mv.visitInsn(LRETURN); 580 } else if (macroType.equals(double.class)) { 581 mv.visitInsn(DRETURN); 582 } else if (macroType.equals(String.class)) { 583 mv.visitInsn(ARETURN); 584 } 585 mv.visitMaxs(0, 0); 586 mv.visitEnd(); 587 if (staticForwardGen != null) { 588 staticForwardGen.visitMacro(macroTree, jt); 589 } 590 return null; 591 } 592 593 protected synchronized void produce() { 594 if (built) { 595 throw new IllegalStateException("Produce is called multiple times"); 596 } 597 built = true; 598 generateNativeHeader(); 599 try { 600 writeClassFile(global_cw, owner.clsName); 601 } catch (IOException ex) { 602 handleException(ex); 603 } 604 if (staticForwardGen != null) { 605 types.put(staticForwardGen.getSimpleClassName(), staticForwardGen.getClassBytes()); 606 } 607 } 608 609 protected Map<String, byte[]> collect() { 610 // Ensure classes are produced 611 if (!built) produce(); 612 HashMap<String, byte[]> rv = new HashMap<>(); 613 // Not copying byte[] for efficiency, perhaps not a safe idea though 614 if (owner.pkgName.isEmpty()) { 615 types.forEach((clsName, bytecodes) -> { 616 rv.put(clsName, bytecodes); 617 }); 618 } else { 619 types.forEach((clsName, bytecodes) -> { 620 rv.put(owner.pkgName + "." + clsName, bytecodes); 621 }); 622 } 623 return Collections.unmodifiableMap(rv); 624 } 625 } |