/* * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.tools.jaotc; import static org.graalvm.compiler.core.common.GraalOptions.GeneratePIC; import static org.graalvm.compiler.core.common.GraalOptions.ImmutableCode; import static org.graalvm.compiler.hotspot.meta.HotSpotAOTProfilingPlugin.Options.TieredAOT; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.lang.management.ManagementFactory; import java.lang.management.MemoryUsage; import java.nio.file.Path; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; import java.util.stream.Stream; import jdk.tools.jaotc.binformat.BinaryContainer; import jdk.tools.jaotc.binformat.ByteContainer; import jdk.tools.jaotc.collect.*; import jdk.tools.jaotc.collect.classname.ClassNameSourceProvider; import jdk.tools.jaotc.collect.directory.DirectorySourceProvider; import jdk.tools.jaotc.collect.jar.JarSourceProvider; import jdk.tools.jaotc.collect.module.ModuleSourceProvider; import jdk.tools.jaotc.utils.Timer; import org.graalvm.compiler.api.runtime.GraalJVMCICompiler; import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig; import org.graalvm.compiler.hotspot.HotSpotGraalRuntimeProvider; import org.graalvm.compiler.hotspot.HotSpotHostBackend; import org.graalvm.compiler.java.GraphBuilderPhase; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderConfiguration; import org.graalvm.compiler.phases.BasePhase; import org.graalvm.compiler.phases.PhaseSuite; import org.graalvm.compiler.phases.tiers.HighTierContext; import org.graalvm.compiler.runtime.RuntimeProvider; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.runtime.JVMCI; public class Main implements LogPrinter { static { GeneratePIC.setValue(true); ImmutableCode.setValue(true); } static class BadArgs extends Exception { private static final long serialVersionUID = 1L; final String key; final Object[] args; boolean showUsage; BadArgs(String key, Object... args) { super(MessageFormat.format(key, args)); this.key = key; this.args = args; } BadArgs showUsage(boolean b) { showUsage = b; return this; } } abstract static class Option { final String help; final boolean hasArg; final String[] aliases; Option(String help, boolean hasArg, String... aliases) { this.help = help; this.hasArg = hasArg; this.aliases = aliases; } boolean isHidden() { return false; } boolean matches(String opt) { for (String a : aliases) { if (a.equals(opt)) { return true; } else if (opt.startsWith("--") && hasArg && opt.startsWith(a + "=")) { return true; } } return false; } boolean ignoreRest() { return false; } abstract void process(Main task, String opt, String arg) throws BadArgs; } static Option[] recognizedOptions = { new Option(" --output Output file name", true, "--output") { @Override void process(Main task, String opt, String arg) { String name = arg; if (name.endsWith(".so")) { name = name.substring(0, name.length() - ".so".length()); } task.options.outputName = name; } }, new Option(" --class-name List of classes to compile", true, "--class-name", "--classname") { @Override void process(Main task, String opt, String arg) { task.options.files.addAll(ClassSearch.makeList(ClassNameSourceProvider.TYPE, arg)); } }, new Option(" --jar List of jar files to compile", true, "--jar") { @Override void process(Main task, String opt, String arg) { task.options.files.addAll(ClassSearch.makeList(JarSourceProvider.TYPE, arg)); } }, new Option(" --module List of modules to compile", true, "--module") { @Override void process(Main task, String opt, String arg) { task.options.files.addAll(ClassSearch.makeList(ModuleSourceProvider.TYPE, arg)); } }, new Option(" --directory List of directories where to search for files to compile", true, "--directory") { @Override void process(Main task, String opt, String arg) { task.options.files.addAll(ClassSearch.makeList(DirectorySourceProvider.TYPE, arg)); } }, new Option(" --search-path List of directories where to search for specified files", true, "--search-path") { @Override void process(Main task, String opt, String arg) { String[] elements = arg.split(":"); task.options.searchPath.add(elements); } }, new Option(" --compile-commands Name of file with compile commands", true, "--compile-commands") { @Override void process(Main task, String opt, String arg) { task.options.methodList = arg; } }, new Option(" --compile-for-tiered Generate profiling code for tiered compilation", false, "--compile-for-tiered") { @Override void process(Main task, String opt, String arg) { TieredAOT.setValue(true); } }, new Option(" --compile-with-assertions Compile with java assertions", false, "--compile-with-assertions") { @Override void process(Main task, String opt, String arg) { task.options.compileWithAssertions = true; } }, new Option(" --compile-threads Number of compilation threads to be used", true, "--compile-threads", "--threads") { @Override void process(Main task, String opt, String arg) { int threads = Integer.parseInt(arg); final int available = Runtime.getRuntime().availableProcessors(); if (threads <= 0) { task.warning("invalid number of threads specified: {0}, using: {1}", threads, available); threads = available; } if (threads > available) { task.warning("too many threads specified: {0}, limiting to: {1}", threads, available); } task.options.threads = Integer.min(threads, available); } }, new Option(" --ignore-errors Ignores all exceptions thrown during class loading", false, "--ignore-errors") { @Override void process(Main task, String opt, String arg) { task.options.ignoreClassLoadingErrors = true; } }, new Option(" --exit-on-error Exit on compilation errors", false, "--exit-on-error") { @Override void process(Main task, String opt, String arg) { task.options.exitOnError = true; } }, new Option(" --info Print information during compilation", false, "--info") { @Override void process(Main task, String opt, String arg) throws BadArgs { task.options.info = true; } }, new Option(" --verbose Print verbose information", false, "--verbose") { @Override void process(Main task, String opt, String arg) throws BadArgs { task.options.info = true; task.options.verbose = true; } }, new Option(" --debug Print debug information", false, "--debug") { @Override void process(Main task, String opt, String arg) throws BadArgs { task.options.info = true; task.options.verbose = true; task.options.debug = true; } }, new Option(" --help Print this usage message", false, "--help") { @Override void process(Main task, String opt, String arg) { task.options.help = true; } }, new Option(" --version Version information", false, "--version") { @Override void process(Main task, String opt, String arg) { task.options.version = true; } }, new Option(" -J Pass directly to the runtime system", false, "-J") { @Override void process(Main task, String opt, String arg) { } }}; public static class Options { public List files = new LinkedList<>(); public String outputName = "unnamed"; public String methodList; public List sources = new ArrayList<>(); public SearchPath searchPath = new SearchPath(); /** * We don't see scaling beyond 16 threads. */ private static final int COMPILER_THREADS = 16; public int threads = Integer.min(COMPILER_THREADS, Runtime.getRuntime().availableProcessors()); public boolean ignoreClassLoadingErrors; public boolean exitOnError; public boolean info; public boolean verbose; public boolean debug; public boolean help; public boolean version; public boolean compileWithAssertions; } /* package */final Options options = new Options(); /** * Logfile. */ private static FileWriter logFile = null; private static final int EXIT_OK = 0; // No errors. private static final int EXIT_CMDERR = 2; // Bad command-line arguments and/or switches. private static final int EXIT_ABNORMAL = 4; // Terminated abnormally. private static final String PROGNAME = "jaotc"; private static final String JVM_VERSION = System.getProperty("java.runtime.version"); public static void main(String[] args) throws Exception { Main t = new Main(); final int exitCode = t.run(args); System.exit(exitCode); } private int run(String[] args) { if (log == null) { log = new PrintWriter(System.out); } try { handleOptions(args); if (options.help) { showHelp(); return EXIT_OK; } if (options.version) { showVersion(); return EXIT_OK; } printlnInfo("Compiling " + options.outputName + "..."); final long start = System.currentTimeMillis(); if (!run()) { return EXIT_ABNORMAL; } final long end = System.currentTimeMillis(); printlnInfo("Total time: " + (end - start) + " ms"); return EXIT_OK; } catch (BadArgs e) { reportError(e.key, e.args); if (e.showUsage) { showUsage(); } return EXIT_CMDERR; } catch (Exception e) { e.printStackTrace(); return EXIT_ABNORMAL; } finally { log.flush(); } } private static String humanReadableByteCount(long bytes) { int unit = 1024; if (bytes < unit) { return bytes + " B"; } int exp = (int) (Math.log(bytes) / Math.log(unit)); char pre = "KMGTPE".charAt(exp - 1); return String.format("%.1f %cB", bytes / Math.pow(unit, exp), pre); } void printMemoryUsage() { if (options.verbose) { MemoryUsage memusage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage(); float freeratio = 1f - (float) memusage.getUsed() / memusage.getCommitted(); log.format(" [used: %-7s, comm: %-7s, freeRatio ~= %.1f%%]", humanReadableByteCount(memusage.getUsed()), humanReadableByteCount(memusage.getCommitted()), freeratio * 100); } } @SuppressWarnings("try") private boolean run() throws Exception { openLog(); try { CompilationSpec compilationRestrictions = collectSpecifiedMethods(); Set> classesToCompile = new HashSet<>(); try (Timer t = new Timer(this, "")) { FileSupport fileSupport = new FileSupport(); ClassSearch lookup = new ClassSearch(); lookup.addProvider(new ModuleSourceProvider()); lookup.addProvider(new ClassNameSourceProvider(fileSupport)); lookup.addProvider(new JarSourceProvider()); lookup.addProvider(new DirectorySourceProvider(fileSupport)); List found = null; try { found = lookup.search(options.files, options.searchPath); } catch (InternalError e) { reportError(e); return false; } for (LoadedClass loadedClass : found) { classesToCompile.add(loadedClass.getLoadedClass()); } printInfo(classesToCompile.size() + " classes found"); } GraalJVMCICompiler graalCompiler = (GraalJVMCICompiler) JVMCI.getRuntime().getCompiler(); HotSpotGraalRuntimeProvider runtime = (HotSpotGraalRuntimeProvider) graalCompiler.getGraalRuntime(); HotSpotHostBackend backend = (HotSpotHostBackend) runtime.getCapability(RuntimeProvider.class).getHostBackend(); MetaAccessProvider metaAccess = backend.getProviders().getMetaAccess(); GraalFilters filters = new GraalFilters(metaAccess); List classes; try (Timer t = new Timer(this, "")) { classes = collectMethodsToCompile(classesToCompile, compilationRestrictions, filters, metaAccess); } // Free memory! try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) { printMemoryUsage(); compilationRestrictions = null; classesToCompile = null; System.gc(); } AOTBackend aotBackend = new AOTBackend(this, backend, filters); AOTCompiler compiler = new AOTCompiler(this, aotBackend, options.threads); classes = compiler.compileClasses(classes); GraalHotSpotVMConfig graalHotSpotVMConfig = runtime.getVMConfig(); PhaseSuite graphBuilderSuite = aotBackend.getGraphBuilderSuite(); ListIterator> iterator = graphBuilderSuite.findPhase(GraphBuilderPhase.class); GraphBuilderConfiguration graphBuilderConfig = ((GraphBuilderPhase) iterator.previous()).getGraphBuilderConfig(); // Free memory! try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) { printMemoryUsage(); aotBackend = null; compiler = null; System.gc(); } BinaryContainer binaryContainer = new BinaryContainer(graalHotSpotVMConfig, graphBuilderConfig, JVM_VERSION); DataBuilder dataBuilder = new DataBuilder(this, backend, classes, binaryContainer); dataBuilder.prepareData(); // Print information about section sizes printContainerInfo(binaryContainer.getHeaderContainer().getContainer()); printContainerInfo(binaryContainer.getConfigContainer()); printContainerInfo(binaryContainer.getKlassesOffsetsContainer()); printContainerInfo(binaryContainer.getMethodsOffsetsContainer()); printContainerInfo(binaryContainer.getKlassesDependenciesContainer()); printContainerInfo(binaryContainer.getStubsOffsetsContainer()); printContainerInfo(binaryContainer.getMethodMetadataContainer()); printContainerInfo(binaryContainer.getCodeContainer()); printContainerInfo(binaryContainer.getCodeSegmentsContainer()); printContainerInfo(binaryContainer.getConstantDataContainer()); printContainerInfo(binaryContainer.getMetaspaceGotContainer()); printContainerInfo(binaryContainer.getMetadataGotContainer()); printContainerInfo(binaryContainer.getMethodStateContainer()); printContainerInfo(binaryContainer.getOopGotContainer()); printContainerInfo(binaryContainer.getMetaspaceNamesContainer()); // Free memory! try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) { printMemoryUsage(); backend = null; for (AOTCompiledClass aotCompClass : classes) { aotCompClass.clear(); } classes.clear(); classes = null; dataBuilder = null; binaryContainer.freeMemory(); System.gc(); } String objectFileName = options.outputName + ".o"; String libraryFileName = options.outputName + ".so"; try (Timer t = new Timer(this, "Creating binary: " + objectFileName)) { binaryContainer.createBinary(objectFileName, JVM_VERSION); } // Free memory! try (Timer t = options.verbose ? new Timer(this, "Freeing memory") : null) { printMemoryUsage(); binaryContainer = null; System.gc(); } try (Timer t = new Timer(this, "Creating shared library: " + libraryFileName)) { Process p = Runtime.getRuntime().exec("ld -shared -z noexecstack -o " + libraryFileName + " " + objectFileName); final int exitCode = p.waitFor(); if (exitCode != 0) { InputStream stderr = p.getErrorStream(); BufferedReader br = new BufferedReader(new InputStreamReader(stderr)); Stream lines = br.lines(); StringBuilder sb = new StringBuilder(); lines.iterator().forEachRemaining(e -> sb.append(e)); throw new InternalError(sb.toString()); } File objFile = new File(objectFileName); if (objFile.exists()) { if (!objFile.delete()) { throw new InternalError("Failed to delete " + objectFileName + " file"); } } // Make non-executable for all. File libFile = new File(libraryFileName); if (libFile.exists()) { if (!libFile.setExecutable(false, false)) { throw new InternalError("Failed to change attribute for " + libraryFileName + " file"); } } } printVerbose("Final memory "); printMemoryUsage(); printlnVerbose(""); } finally { closeLog(); } return true; } private void addMethods(AOTCompiledClass aotClass, ResolvedJavaMethod[] methods, CompilationSpec compilationRestrictions, GraalFilters filters) { for (ResolvedJavaMethod m : methods) { addMethod(aotClass, m, compilationRestrictions, filters); } } private void addMethod(AOTCompiledClass aotClass, ResolvedJavaMethod method, CompilationSpec compilationRestrictions, GraalFilters filters) { // Don't compile native or abstract methods. if (!method.hasBytecodes()) { return; } if (!compilationRestrictions.shouldCompileMethod(method)) { return; } if (!filters.shouldCompileMethod(method)) { return; } aotClass.addMethod(method); printlnVerbose(" added " + method.getName() + method.getSignature().toMethodDescriptor()); } private void printContainerInfo(ByteContainer container) { printlnVerbose(container.getContainerName() + ": " + container.getByteStreamSize() + " bytes"); } PrintWriter log; private void handleOptions(String[] args) throws BadArgs { if (args.length == 0) { options.help = true; return; } // Make checkstyle happy. int i = 0; for (; i < args.length; i++) { String arg = args[i]; if (arg.charAt(0) == '-') { Option option = getOption(arg); String param = null; if (option.hasArg) { if (arg.startsWith("--") && arg.indexOf('=') > 0) { param = arg.substring(arg.indexOf('=') + 1, arg.length()); } else if (i + 1 < args.length) { param = args[++i]; } if (param == null || param.isEmpty() || param.charAt(0) == '-') { throw new BadArgs("missing argument for option: {0}", arg).showUsage(true); } } option.process(this, arg, param); if (option.ignoreRest()) { break; } } else { options.files.add(new SearchFor(arg)); } } } private static Option getOption(String name) throws BadArgs { for (Option o : recognizedOptions) { if (o.matches(name)) { return o; } } throw new BadArgs("unknown option: {0}", name).showUsage(true); } public void printInfo(String message) { if (options.info) { log.print(message); log.flush(); } } public void printlnInfo(String message) { if (options.info) { log.println(message); log.flush(); } } public void printVerbose(String message) { if (options.verbose) { log.print(message); log.flush(); } } public void printlnVerbose(String message) { if (options.verbose) { log.println(message); log.flush(); } } public void printDebug(String message) { if (options.debug) { log.print(message); log.flush(); } } public void printlnDebug(String message) { if (options.debug) { log.println(message); log.flush(); } } public void printError(String message) { log.println("Error: " + message); log.flush(); } private void reportError(Throwable e) { log.println("Error: " + e.getMessage()); if (options.info) { e.printStackTrace(log); } log.flush(); } private void reportError(String key, Object... args) { printError(MessageFormat.format(key, args)); } private void warning(String key, Object... args) { log.println("Warning: " + MessageFormat.format(key, args)); log.flush(); } private void showUsage() { log.println("Usage: " + PROGNAME + " list"); log.println("use --help for a list of possible options"); } private void showHelp() { log.println("Usage: " + PROGNAME + " list"); log.println(); log.println(" list A : separated list of class names, modules, jar files"); log.println(" or directories which contain class files."); log.println(); log.println("where options include:"); for (Option o : recognizedOptions) { String name = o.aliases[0].substring(1); // there must always be at least one name name = name.charAt(0) == '-' ? name.substring(1) : name; if (o.isHidden() || name.equals("h")) { continue; } log.println(o.help); } } private void showVersion() { log.println(PROGNAME + " " + JVM_VERSION); } /** * Collect all method we should compile. * * @return array list of AOT classes which have compiled methods. */ private List collectMethodsToCompile(Set> classesToCompile, CompilationSpec compilationRestrictions, GraalFilters filters, MetaAccessProvider metaAccess) { int total = 0; int count = 0; List classes = new ArrayList<>(); for (Class c : classesToCompile) { ResolvedJavaType resolvedJavaType = metaAccess.lookupJavaType(c); if (filters.shouldCompileAnyMethodInClass(resolvedJavaType)) { AOTCompiledClass aotClass = new AOTCompiledClass(resolvedJavaType); printlnVerbose(" Scanning " + c.getName()); // Constructors try { ResolvedJavaMethod[] ctors = resolvedJavaType.getDeclaredConstructors(); addMethods(aotClass, ctors, compilationRestrictions, filters); total += ctors.length; } catch (Throwable e) { // If we are running in JCK mode we ignore all exceptions. if (options.ignoreClassLoadingErrors) { printError(c.getName() + ": " + e); } else { throw new InternalError(e); } } // Methods try { ResolvedJavaMethod[] methods = resolvedJavaType.getDeclaredMethods(); addMethods(aotClass, methods, compilationRestrictions, filters); total += methods.length; } catch (Throwable e) { // If we are running in JCK mode we ignore all exceptions. if (options.ignoreClassLoadingErrors) { printError(c.getName() + ": " + e); } else { throw new InternalError(e); } } // Class initializer try { ResolvedJavaMethod clinit = resolvedJavaType.getClassInitializer(); if (clinit != null) { addMethod(aotClass, clinit, compilationRestrictions, filters); total++; } } catch (Throwable e) { // If we are running in JCK mode we ignore all exceptions. if (options.ignoreClassLoadingErrors) { printError(c.getName() + ": " + e); } else { throw new InternalError(e); } } // Found any methods to compile? Add the class. if (aotClass.hasMethods()) { classes.add(aotClass); count += aotClass.getMethodCount(); } } } printInfo(total + " methods total, " + count + " methods to compile"); return classes; } /** * If a file with compilation limitations is specified using the java property * jdk.tools.jaotc.compile.method.list, read the file's contents and collect the restrictions. */ private CompilationSpec collectSpecifiedMethods() { CompilationSpec compilationRestrictions = new CompilationSpec(); String methodListFileName = options.methodList; if (methodListFileName != null && !methodListFileName.equals("")) { try { FileReader methListFile = new FileReader(methodListFileName); BufferedReader readBuf = new BufferedReader(methListFile); String line = null; while ((line = readBuf.readLine()) != null) { String trimmedLine = line.trim(); if (!trimmedLine.startsWith("#")) { String[] components = trimmedLine.split(" "); if (components.length == 2) { String directive = components[0]; String pattern = components[1]; switch (directive) { case "compileOnly": compilationRestrictions.addCompileOnlyPattern(pattern); break; case "exclude": compilationRestrictions.addExcludePattern(pattern); break; default: System.out.println("Unrecognized command " + directive + ". Ignoring\n\t" + line + "\n encountered in " + methodListFileName); } } else { if (!trimmedLine.equals("")) { System.out.println("Ignoring malformed line:\n\t " + line + "\n"); } } } } readBuf.close(); } catch (FileNotFoundException e) { throw new InternalError("Unable to open method list file: " + methodListFileName, e); } catch (IOException e) { throw new InternalError("Unable to read method list file: " + methodListFileName, e); } } return compilationRestrictions; } private static void openLog() { int v = Integer.getInteger("jdk.tools.jaotc.logCompilation", 0); if (v == 0) { logFile = null; return; } // Create log file in current directory String fileName = "aot_compilation" + new Date().getTime() + ".log"; Path logFilePath = Paths.get("./", fileName); String logFileName = logFilePath.toString(); try { // Create file to which we do not append logFile = new FileWriter(logFileName, false); } catch (IOException e) { System.out.println("Unable to open logfile :" + logFileName + "\nNo logs will be created"); logFile = null; } } public static void writeLog(String str) { if (logFile != null) { try { logFile.write(str + "\n"); logFile.flush(); } catch (IOException e) { // Print to console System.out.println(str + "\n"); } } } public static void closeLog() { if (logFile != null) { try { logFile.close(); } catch (IOException e) { // Do nothing } } } }