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 }