1 /* 2 * Copyright (c) 2020, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.jextract.impl; 26 27 import jdk.incubator.foreign.*; 28 import jdk.incubator.jextract.Declaration; 29 import jdk.incubator.jextract.JextractTool; 30 import jdk.incubator.jextract.Type; 31 32 import jdk.internal.jextract.impl.JavaSourceBuilder.VarInfo; 33 import jdk.internal.jextract.impl.JavaSourceBuilder.FunctionInfo; 34 35 import javax.tools.JavaFileObject; 36 import java.io.File; 37 import java.io.IOException; 38 import java.io.UncheckedIOException; 39 import java.lang.constant.ClassDesc; 40 import java.lang.invoke.MethodType; 41 import java.net.URI; 42 import java.net.URL; 43 import java.net.URISyntaxException; 44 import java.nio.file.Files; 45 import java.nio.file.Paths; 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.List; 50 import java.util.Map; 51 import java.util.Optional; 52 import java.util.Set; 53 import java.util.function.BiFunction; 54 import java.util.stream.Collectors; 55 56 /* 57 * Scan a header file and generate Java source items for entities defined in that header 58 * file. Tree visitor visit methods return true/false depending on whether a 59 * particular Tree is processed or skipped. 60 */ 61 public class OutputFactory implements Declaration.Visitor<Void, Declaration> { 62 // internal symbol used by clang for "va_list" 63 private static final String VA_LIST_TAG = "__va_list_tag"; 64 private final Set<String> constants = new HashSet<>(); 65 // To detect duplicate Variable and Function declarations. 66 private final Set<String> variables = new HashSet<>(); 67 private final Set<Declaration.Function> functions = new HashSet<>(); 68 69 protected final ToplevelBuilder toplevelBuilder; 70 protected JavaSourceBuilder currentBuilder; 71 protected final TypeTranslator typeTranslator = new TypeTranslator(); 72 private final String pkgName; 73 private final Map<Declaration, String> structClassNames = new HashMap<>(); 74 private final Set<Declaration.Typedef> unresolvedStructTypedefs = new HashSet<>(); 75 private final Map<Type, String> functionTypeDefNames = new HashMap<>(); 76 private final IncludeHelper includeHelper; 77 78 private void addStructDefinition(Declaration decl, String name) { 79 structClassNames.put(decl, name); 80 } 81 82 private boolean structDefinitionSeen(Declaration decl) { 83 return structClassNames.containsKey(decl); 84 } 85 86 private String structDefinitionName(Declaration decl) { 87 return structClassNames.get(decl); 88 } 89 90 private void addFunctionTypedef(Type.Delegated typedef, String name) { 91 functionTypeDefNames.put(typedef, name); 92 } 93 94 private boolean functionTypedefSeen(Type.Delegated typedef) { 95 return functionTypeDefNames.containsKey(typedef); 96 } 97 98 private String functionTypedefName(Type.Delegated decl) { 99 return functionTypeDefNames.get(decl); 100 } 101 102 // have we seen this Variable earlier? 103 protected boolean variableSeen(Declaration.Variable tree) { 104 return !variables.add(tree.name()); 105 } 106 107 // have we seen this Function earlier? 108 protected boolean functionSeen(Declaration.Function tree) { 109 return !functions.add(tree); 110 } 111 112 public static JavaFileObject[] generateWrapped(Declaration.Scoped decl, String headerName, 113 String pkgName, IncludeHelper includeHelper, List<String> libraryNames) { 114 String clsName = Utils.javaSafeIdentifier(headerName.replace(".h", "_h"), true); 115 ToplevelBuilder toplevelBuilder = new ToplevelBuilder(pkgName, clsName); 116 return new OutputFactory(pkgName, toplevelBuilder, includeHelper).generate(decl, libraryNames.toArray(new String[0])); 117 } 118 119 private OutputFactory(String pkgName, ToplevelBuilder toplevelBuilder, IncludeHelper includeHelper) { 120 this.pkgName = pkgName; 121 this.toplevelBuilder = toplevelBuilder; 122 this.currentBuilder = toplevelBuilder; 123 this.includeHelper = includeHelper; 124 } 125 126 JavaFileObject[] generate(Declaration.Scoped decl, String[] libs) { 127 //generate all decls 128 decl.members().forEach(this::generateDecl); 129 // check if unresolved typedefs can be resolved now! 130 for (Declaration.Typedef td : unresolvedStructTypedefs) { 131 Declaration.Scoped structDef = ((Type.Declared) td.type()).tree(); 132 toplevelBuilder.addTypedef(td.name(), 133 structDefinitionSeen(structDef) ? structDefinitionName(structDef) : null, td.type()); 134 } 135 try { 136 List<JavaFileObject> files = new ArrayList<>(toplevelBuilder.toFiles()); 137 files.add(jfoFromString(pkgName,"RuntimeHelper", getRuntimeHelperSource(libs))); 138 return files.toArray(new JavaFileObject[0]); 139 } catch (IOException ex) { 140 throw new UncheckedIOException(ex); 141 } catch (URISyntaxException ex2) { 142 throw new RuntimeException(ex2); 143 } 144 } 145 146 private String getRuntimeHelperSource(String[] libraries) throws URISyntaxException, IOException { 147 URL runtimeHelper = OutputFactory.class.getResource("resources/RuntimeHelper.java.template"); 148 String template = (pkgName.isEmpty()? "" : "package " + pkgName + ";\n") + 149 String.join("\n", Files.readAllLines(Paths.get(runtimeHelper.toURI()))); 150 List<String> loadLibrariesStr = new ArrayList<>(); 151 for (String lib : libraries) { 152 String quotedLibName = quoteLibraryName(lib); 153 if (quotedLibName.indexOf(File.separatorChar) != -1) { 154 loadLibrariesStr.add("System.load(\"" + quotedLibName + "\");"); 155 } else { 156 loadLibrariesStr.add("System.loadLibrary(\"" + quotedLibName + "\");"); 157 } 158 } 159 return template.replace("#LOAD_LIBRARIES#", loadLibrariesStr.stream().collect(Collectors.joining(" "))); 160 } 161 162 private String quoteLibraryName(String lib) { 163 return lib.replace("\\", "\\\\"); // double up slashes 164 } 165 166 private void generateDecl(Declaration tree) { 167 try { 168 tree.accept(this, null); 169 } catch (Exception ex) { 170 ex.printStackTrace(); 171 } 172 } 173 174 private JavaFileObject jfoFromString(String pkgName, String clsName, String contents) { 175 String pkgPrefix = pkgName.isEmpty() ? "" : pkgName.replaceAll("\\.", "/") + "/"; 176 return InMemoryJavaCompiler.jfoFromString(URI.create(pkgPrefix + clsName + ".java"), contents); 177 } 178 179 @Override 180 public Void visitConstant(Declaration.Constant constant, Declaration parent) { 181 if (!constants.add(constant.name()) || !includeHelper.isIncluded(constant)) { 182 //skip 183 return null; 184 } 185 186 Class<?> clazz = getJavaType(constant.type()); 187 if (clazz == null) { 188 warn("skipping " + constant.name() + " because of unsupported type usage"); 189 return null; 190 } 191 toplevelBuilder.addConstant(Utils.javaSafeIdentifier(constant.name()), 192 constant.value() instanceof String ? MemorySegment.class : 193 getJavaType(constant.type()), constant.value()); 194 return null; 195 } 196 197 @Override 198 public Void visitScoped(Declaration.Scoped d, Declaration parent) { 199 if (d.layout().isEmpty() || structDefinitionSeen(d)) { 200 //skip decl 201 return null; 202 } 203 boolean isStructKind = switch (d.kind()) { 204 case STRUCT, UNION -> true; 205 default -> false; 206 }; 207 StructBuilder structBuilder = null; 208 if (isStructKind) { 209 String className = d.name(); 210 if (!className.isEmpty() && !includeHelper.isIncluded(d)) { 211 return null; 212 } 213 GroupLayout layout = (GroupLayout) layoutFor(d); 214 currentBuilder = structBuilder = currentBuilder.addStruct(className, parent, layout, Type.declared(d)); 215 structBuilder.classBegin(); 216 if (!className.isEmpty()) { 217 addStructDefinition(d, structBuilder.fullName()); 218 } 219 } 220 try { 221 d.members().forEach(fieldTree -> fieldTree.accept(this, d)); 222 } finally { 223 if (isStructKind) { 224 currentBuilder = structBuilder.classEnd(); 225 } 226 } 227 return null; 228 } 229 230 private String generateFunctionalInterface(Type.Function func, String name) { 231 return functionInfo(func, name, false, 232 (mtype, desc) -> FunctionInfo.ofFunctionPointer(mtype, getMethodType(func, true), desc, func.parameterNames())) 233 .map(fInfo -> currentBuilder.addFunctionalInterface(Utils.javaSafeIdentifier(name), fInfo)) 234 .orElse(null); 235 } 236 237 @Override 238 public Void visitFunction(Declaration.Function funcTree, Declaration parent) { 239 if (functionSeen(funcTree) || 240 !includeHelper.isIncluded(funcTree)) { 241 return null; 242 } 243 244 String mhName = Utils.javaSafeIdentifier(funcTree.name()); 245 //generate static wrapper for function 246 List<String> paramNames = funcTree.parameters() 247 .stream() 248 .map(Declaration.Variable::name) 249 .map(p -> !p.isEmpty() ? Utils.javaSafeIdentifier(p) : p) 250 .collect(Collectors.toList()); 251 252 Optional<FunctionInfo> functionInfo = functionInfo(funcTree.type(), funcTree.name(), true, 253 (mtype, desc) -> FunctionInfo.ofFunction(mtype, desc, funcTree.type().varargs(), paramNames)); 254 255 if (functionInfo.isPresent()) { 256 int i = 0; 257 for (Declaration.Variable param : funcTree.parameters()) { 258 Type.Function f = getAsFunctionPointer(param.type()); 259 if (f != null) { 260 String name = funcTree.name() + "$" + (param.name().isEmpty() ? "x" + i : param.name()); 261 if (generateFunctionalInterface(f, name) == null) { 262 return null; 263 } 264 i++; 265 } 266 } 267 268 toplevelBuilder.addFunction(mhName, funcTree.name(), functionInfo.get()); 269 } 270 271 return null; 272 } 273 274 Optional<String> getAsFunctionPointerTypedef(Type type) { 275 if (type instanceof Type.Delegated delegated && 276 delegated.kind() == Type.Delegated.Kind.TYPEDEF && 277 functionTypedefSeen(delegated)) { 278 return Optional.of(functionTypedefName(delegated)); 279 } else { 280 return Optional.empty(); 281 } 282 } 283 284 Type.Function getAsFunctionPointer(Type type) { 285 if (type instanceof Type.Function) { 286 /* 287 * // pointer to function declared as function like this 288 * 289 * typedef void CB(int); 290 * void func(CB cb); 291 */ 292 return (Type.Function) type; 293 } else if (Utils.isPointerType(type)) { 294 return getAsFunctionPointer(((Type.Delegated)type).type()); 295 } else { 296 return null; 297 } 298 } 299 300 @Override 301 public Void visitTypedef(Declaration.Typedef tree, Declaration parent) { 302 if (!includeHelper.isIncluded(tree)) { 303 return null; 304 } 305 Type type = tree.type(); 306 if (type instanceof Type.Declared) { 307 Declaration.Scoped s = ((Type.Declared) type).tree(); 308 if (!s.name().equals(tree.name())) { 309 switch (s.kind()) { 310 case STRUCT, UNION -> { 311 if (s.name().isEmpty()) { 312 visitScoped(s, tree); 313 } else { 314 /* 315 * If typedef is seen after the struct/union definition, we can generate subclass 316 * right away. If not, we've to save it and revisit after all the declarations are 317 * seen. This is to support forward declaration of typedefs. 318 * 319 * typedef struct Foo Bar; 320 * 321 * struct Foo { 322 * int x, y; 323 * }; 324 */ 325 if (structDefinitionSeen(s)) { 326 toplevelBuilder.addTypedef(tree.name(), structDefinitionName(s), tree.type()); 327 } else { 328 /* 329 * Definition of typedef'ed struct/union not seen yet. May be the definition comes later. 330 * Save it to visit at the end of all declarations. 331 */ 332 unresolvedStructTypedefs.add(tree); 333 } 334 } 335 } 336 default -> visitScoped(s, tree); 337 } 338 } 339 } else if (type instanceof Type.Primitive) { 340 toplevelBuilder.addTypedef(tree.name(), null, type); 341 } else { 342 Type.Function func = getAsFunctionPointer(type); 343 if (func != null) { 344 String funcIntfName = generateFunctionalInterface(func, tree.name()); 345 if (funcIntfName != null) { 346 addFunctionTypedef(Type.typedef(tree.name(), tree.type()), funcIntfName); 347 } 348 } else if (((TypeImpl)type).isPointer()) { 349 toplevelBuilder.addTypedef(tree.name(), null, type); 350 } 351 } 352 return null; 353 } 354 355 @Override 356 public Void visitVariable(Declaration.Variable tree, Declaration parent) { 357 if (parent == null && (variableSeen(tree) || !includeHelper.isIncluded(tree))) { 358 return null; 359 } 360 361 String fieldName = tree.name(); 362 String symbol = tree.name(); 363 assert !symbol.isEmpty(); 364 assert !fieldName.isEmpty(); 365 fieldName = Utils.javaSafeIdentifier(fieldName); 366 367 Type type = tree.type(); 368 369 if (type instanceof Type.Declared) { 370 // declared type - visit declaration recursively 371 ((Type.Declared) type).tree().accept(this, tree); 372 } 373 MemoryLayout layout = tree.layout().orElse(Type.layoutFor(type).orElse(null)); 374 if (layout == null) { 375 //no layout - abort 376 return null; 377 } 378 379 String unsupportedType = UnsupportedLayouts.firstUnsupportedType(type); 380 if (unsupportedType != null) { 381 String name = parent != null? parent.name() + "." : ""; 382 name += fieldName; 383 warn("skipping " + name + " because of unsupported type usage: " + 384 unsupportedType); 385 return null; 386 } 387 388 Class<?> clazz = getJavaType(type); 389 if (clazz == null) { 390 String name = parent != null? parent.name() + "." : ""; 391 name += fieldName; 392 warn("skipping " + name + " because of unsupported type usage"); 393 return null; 394 } 395 396 397 VarInfo varInfo = VarInfo.ofVar(clazz, layout); 398 Type.Function func = getAsFunctionPointer(type); 399 String fiName; 400 if (func != null) { 401 fiName = generateFunctionalInterface(func, fieldName); 402 if (fiName != null) { 403 varInfo = VarInfo.ofFunctionalPointerVar(clazz, layout, fiName); 404 } 405 } else { 406 Optional<String> funcTypedef = getAsFunctionPointerTypedef(type); 407 if (funcTypedef.isPresent()) { 408 varInfo = VarInfo.ofFunctionalPointerVar(clazz, layout, Utils.javaSafeIdentifier(funcTypedef.get())); 409 } 410 } 411 412 if (tree.kind() == Declaration.Variable.Kind.BITFIELD || 413 (layout instanceof ValueLayout && layout.byteSize() > 8)) { 414 //skip 415 return null; 416 } 417 418 boolean sizeAvailable; 419 try { 420 layout.byteSize(); 421 sizeAvailable = true; 422 } catch (Exception ignored) { 423 sizeAvailable = false; 424 } 425 if (sizeAvailable) { 426 currentBuilder.addVar(fieldName, tree.name(), varInfo); 427 } else { 428 warn("Layout size not available for " + fieldName); 429 } 430 431 return null; 432 } 433 434 private Optional<FunctionInfo> functionInfo(Type.Function funcPtr, String nativeName, boolean downcall, 435 BiFunction<MethodType, FunctionDescriptor, FunctionInfo> functionInfoFactory) { 436 FunctionDescriptor descriptor = Type.descriptorFor(funcPtr).orElse(null); 437 if (descriptor == null) { 438 //abort 439 return Optional.empty(); 440 } 441 442 //generate functional interface 443 if (!downcall && funcPtr.varargs() && !funcPtr.argumentTypes().isEmpty()) { 444 warn("varargs in callbacks is not supported: " + funcPtr); 445 return Optional.empty(); 446 } 447 448 String unsupportedType = UnsupportedLayouts.firstUnsupportedType(funcPtr); 449 if (unsupportedType != null) { 450 warn("skipping " + nativeName + " because of unsupported type usage: " + 451 unsupportedType); 452 return Optional.empty(); 453 } 454 455 MethodType mtype = getMethodType(funcPtr, downcall); 456 return mtype != null ? 457 Optional.of(functionInfoFactory.apply(mtype, descriptor)) : 458 Optional.empty(); 459 } 460 461 protected static MemoryLayout layoutFor(Declaration decl) { 462 if (decl instanceof Declaration.Typedef) { 463 Declaration.Typedef alias = (Declaration.Typedef) decl; 464 return Type.layoutFor(alias.type()).orElseThrow(); 465 } else if (decl instanceof Declaration.Scoped) { 466 return ((Declaration.Scoped) decl).layout().orElseThrow(); 467 } else { 468 throw new IllegalArgumentException("Unexpected parent declaration"); 469 } 470 // case like `typedef struct { ... } Foo` 471 } 472 473 static void warn(String msg) { 474 System.err.println("WARNING: " + msg); 475 } 476 477 private Class<?> getJavaType(Type type) { 478 try { 479 return typeTranslator.getJavaType(type, false); 480 } catch (UnsupportedOperationException uoe) { 481 warn(uoe.toString()); 482 if (JextractTool.DEBUG) { 483 uoe.printStackTrace(); 484 } 485 return null; 486 } 487 } 488 489 private MethodType getMethodType(Type.Function type, boolean downcall) { 490 try { 491 return typeTranslator.getMethodType(type, downcall); 492 } catch (UnsupportedOperationException uoe) { 493 warn(uoe.toString()); 494 if (JextractTool.DEBUG) { 495 uoe.printStackTrace(); 496 } 497 return null; 498 } 499 } 500 }