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