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;
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();
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 }
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 }
|
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;
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();
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 }
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 }
|