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.incubator.jextract.tool; 26 27 import jdk.incubator.jextract.Declaration; 28 import jdk.incubator.jextract.Type; 29 import jdk.incubator.foreign.FunctionDescriptor; 30 import jdk.incubator.foreign.MemoryAddress; 31 import jdk.incubator.foreign.MemoryLayout; 32 import jdk.incubator.foreign.MemorySegment; 33 34 import javax.tools.JavaFileObject; 35 import javax.tools.SimpleJavaFileObject; 36 import java.io.File; 37 import java.io.IOException; 38 import java.io.UncheckedIOException; 39 import java.lang.invoke.MethodType; 40 import java.net.URI; 41 import java.net.URL; 42 import java.net.URISyntaxException; 43 import java.nio.file.Files; 44 import java.nio.file.Paths; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Optional; 48 import java.util.Set; 49 import java.util.stream.Collectors; 50 51 /* 52 * Scan a header file and generate Java source items for entities defined in that header 53 * file. Tree visitor visit methods return true/false depending on whether a 54 * particular Tree is processed or skipped. 55 */ 56 public class HandleSourceFactory implements Declaration.Visitor<Void, Declaration> { 57 58 private final Set<String> constants = new HashSet<>(); 59 protected final JavaSourceBuilder builder = new JavaSourceBuilder(); 60 protected final TypeTranslator typeTranslator = new TypeTranslator(); 61 private final List<String> libraryNames; 62 private final List<String> libraryPaths; 63 private final String clsName; 64 private final String pkgName; 65 66 static JavaFileObject[] generateRaw(Declaration.Scoped decl, String clsName, String pkgName, List<String> libraryNames, List<String> libraryPaths) { 67 return new HandleSourceFactory(clsName, pkgName, libraryNames, libraryPaths).generate(decl); 68 } 69 70 static JavaFileObject[] generateWrapped(Declaration.Scoped decl, String clsName, String pkgName, List<String> libraryNames, List<String> libraryPaths) { 71 return new StaticWrapperSourceFactory(clsName, pkgName, libraryNames, libraryPaths).generate(decl); 72 } 73 74 public HandleSourceFactory(String clsName, String pkgName, List<String> libraryNames, List<String> libraryPaths) { 75 this.libraryNames = libraryNames; 76 this.libraryPaths = libraryPaths; 77 this.clsName = clsName; 78 this.pkgName = pkgName; 79 } 80 81 private static String getCLangConstantsHolder() { 82 String prefix = "jdk.incubator.foreign.MemoryLayouts."; 83 String arch = System.getProperty("os.arch"); 84 String os = System.getProperty("os.name"); 85 if (arch.equals("amd64") || arch.equals("x86_64")) { 86 if (os.startsWith("Windows")) { 87 return prefix + "WinABI"; 88 } else { 89 return prefix + "SysV"; 90 } 91 } else if (arch.equals("aarch64")) { 92 return prefix + "AArch64ABI"; 93 } 94 throw new UnsupportedOperationException("Unsupported os or arch: " + os + ", " + arch); 95 } 96 97 private static final String C_LANG_CONSTANTS_HOLDER = getCLangConstantsHolder(); 98 99 public JavaFileObject[] generate(Declaration.Scoped decl) { 100 builder.addPackagePrefix(pkgName); 101 builder.classBegin(clsName); 102 builder.addLibraries(libraryNames.toArray(new String[0]), 103 libraryPaths != null ? libraryPaths.toArray(new String[0]) : null); 104 //generate all decls 105 decl.members().forEach(this::generateDecl); 106 107 //generate functional interfaces 108 generateFunctionalInterfaces(decl); 109 110 builder.classEnd(); 111 String src = builder.build(); 112 113 URL runtimeHelper = HandleSourceFactory.class.getResource("resources/RuntimeHelper.template"); 114 115 try { 116 return new JavaFileObject[] { 117 fileFromString(pkgName, clsName, src), 118 fileFromString(pkgName,"RuntimeHelper", (pkgName.isEmpty()? "" : "package " + pkgName + ";\n") + 119 Files.readAllLines(Paths.get(runtimeHelper.toURI())) 120 .stream().collect(Collectors.joining("\n")).replace("${C_LANG}", C_LANG_CONSTANTS_HOLDER)) 121 }; 122 } catch (IOException ex) { 123 throw new UncheckedIOException(ex); 124 } catch (URISyntaxException ex2) { 125 throw new RuntimeException(ex2); 126 } 127 } 128 129 protected void generateFunctionalInterfaces(Declaration.Scoped decl) { 130 //generate functional interfaces 131 Set<FunctionDescriptor> functionalInterfaces = new HashSet<>(); 132 new FunctionalInterfaceScanner(functionalInterfaces).scan(decl); 133 functionalInterfaces.forEach(builder::addUpcallFactory); 134 } 135 136 private void generateDecl(Declaration tree) { 137 try { 138 tree.accept(this, null); 139 } catch (Exception ex) { 140 ex.printStackTrace(); 141 } 142 } 143 144 private JavaFileObject fileFromString(String pkgName, String clsName, String contents) { 145 String pkgPrefix = pkgName.isEmpty() ? "" : pkgName.replaceAll("\\.", "/") + "/"; 146 return new SimpleJavaFileObject(URI.create(pkgPrefix + clsName + ".java"), JavaFileObject.Kind.SOURCE) { 147 @Override 148 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 149 return contents; 150 } 151 }; 152 } 153 154 @Override 155 public Void visitVariable(Declaration.Variable tree, Declaration parent) { 156 String fieldName = tree.name(); 157 String symbol = tree.name(); 158 assert !symbol.isEmpty(); 159 assert !fieldName.isEmpty(); 160 Type type = tree.type(); 161 MemoryLayout layout = tree.layout().orElse(Type.layoutFor(type).orElse(null)); 162 if (layout == null) { 163 //no layout - abort 164 return null; 165 } 166 Class<?> clazz = typeTranslator.getJavaType(type); 167 if (clazz == MemoryAddress.class || clazz == MemorySegment.class || layout.byteSize() > 8) { 168 //skip 169 return null; 170 } 171 172 if (parent != null) { 173 //struct field 174 builder.addVarHandle(fieldName, clazz, parent.name()); 175 } else { 176 builder.addLayout(fieldName, layout); 177 builder.addVarHandle(fieldName, clazz, null); 178 builder.addAddress(fieldName); 179 } 180 181 return null; 182 } 183 184 @Override 185 public Void visitFunction(Declaration.Function funcTree, Declaration parent) { 186 FunctionDescriptor descriptor = Type.descriptorFor(funcTree.type()).orElse(null); 187 if (descriptor == null) { 188 //abort 189 } 190 MethodType mtype = typeTranslator.getMethodType(funcTree.type()); 191 builder.addMethodHandle(funcTree, mtype, descriptor); 192 return null; 193 } 194 195 @Override 196 public Void visitConstant(Declaration.Constant constant, Declaration parent) { 197 if (!constants.add(constant.name())) { 198 //skip 199 return null; 200 } 201 202 builder.addConstant(constant.name(), typeTranslator.getJavaType(constant.type()), constant.value()); 203 return null; 204 } 205 206 @Override 207 public Void visitScoped(Declaration.Scoped d, Declaration parent) { 208 if (d.kind() == Declaration.Scoped.Kind.TYPEDEF) { 209 return d.members().get(0).accept(this, d); 210 } 211 if (d.layout().isEmpty()) { 212 //skip decl-only 213 return null; 214 } 215 String name = d.name(); 216 if (d.name().isEmpty() && parent != null) { 217 name = parent.name(); 218 } 219 220 if (!d.name().isEmpty() || !isRecord(parent)) { 221 //only add explicit struct layout if the struct is not to be flattened inside another struct 222 switch (d.kind()) { 223 case STRUCT: 224 case UNION: 225 builder.addLayout(name, d.layout().get()); 226 break; 227 } 228 } 229 d.members().forEach(fieldTree -> fieldTree.accept(this, d.name().isEmpty() ? parent : d)); 230 return null; 231 } 232 233 private boolean isRecord(Declaration declaration) { 234 if (declaration == null) { 235 return false; 236 } else if (!(declaration instanceof Declaration.Scoped)) { 237 return false; 238 } else { 239 Declaration.Scoped scope = (Declaration.Scoped)declaration; 240 return scope.kind() == Declaration.Scoped.Kind.CLASS || 241 scope.kind() == Declaration.Scoped.Kind.STRUCT || 242 scope.kind() == Declaration.Scoped.Kind.UNION; 243 } 244 } 245 }