hotspot/src/jdk.aot/share/classes/jdk.tools.jaotc/src/jdk/tools/jaotc/Main.java
author rbackman
Thu, 05 Jan 2017 08:37:10 +0100
changeset 43464 f38fde4a6b52
parent 43405 1fab8e6866d7
child 43479 67507b173e81
permissions -rw-r--r--
8169588: [AOT] jaotc --classpath option is confusing Reviewed-by: kvn, dlong

/*
 * 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 <file>            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("  --compile-commands <file>  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 assertions", false, "--compile-with-assertions") {
        @Override
        void process(Main task, String opt, String arg) {
            task.options.compileWithAssertions = true;
        }
    }, new Option("  --classname <class names>> Class names to AOT compile (: separated list)", true, "--classname") {
        @Override
        void process(Main task, String opt, String arg) {
            task.options.files.addAll(ClassSearch.makeList(ClassNameSourceProvider.TYPE, arg));
        }
    }, new Option("  --directory <directories>  Directories to search for class files. (: separated list)", true, "--directory") {
        @Override
        void process(Main task, String opt, String arg) {
            task.options.files.addAll(ClassSearch.makeList(DirectorySourceProvider.TYPE, arg));
        }
    }, new Option("  --jar <jarfiles>           Jar files to search for class files. (: separated list)", true, "--jar") {
        @Override
        void process(Main task, String opt, String arg) {
            task.options.files.addAll(ClassSearch.makeList(JarSourceProvider.TYPE, arg));
        }
    }, new Option("  --module <modules>         module names to AOT compile (: separated list)", true, "--module") {
        @Override
        void process(Main task, String opt, String arg) {
            task.options.files.addAll(ClassSearch.makeList(ModuleSourceProvider.TYPE, arg));
        }
    }, new Option("  --threads <number>         Number of compilation threads to be used", true, "--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("  --search-path <name>       Where to search for jarfiles and modules", true, "--search-path") {
        @Override
        void process(Main task, String opt, String arg) {
            String[] elements = arg.split(":");
            task.options.searchPath.add(elements);
        }
    }, new Option("  -J<flag>                   Pass <flag> directly to the runtime system", false, "-J") {
        @Override
        void process(Main task, String opt, String arg) {
        }
    }};

    public static class Options {
        public List<SearchFor> files = new LinkedList<>();
        public String outputName = "unnamed";
        public String methodList;
        public List<ClassSource> 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<Class<?>> 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<LoadedClass> 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<AOTCompiledClass> 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<HighTierContext> graphBuilderSuite = aotBackend.getGraphBuilderSuite();
            ListIterator<BasePhase<? super HighTierContext>> 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<String> 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());
        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 + " <options> list...");
        log.println("use --help for a list of possible options");
    }

    private void showHelp() {
        log.println("Usage: " + PROGNAME + " <options> | <list...>");
        log.println();
        log.println("  list       A list of class names, jar files or directories which");
        log.println("             contains class files.");
        log.println();
        log.println("where possible 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<AOTCompiledClass> collectMethodsToCompile(Set<Class<?>> classesToCompile, CompilationSpec compilationRestrictions, GraalFilters filters, MetaAccessProvider metaAccess) {
        int total = 0;
        int count = 0;
        List<AOTCompiledClass> 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
            }
        }
    }
}