1 /*
   2  * Copyright (c) 2013, 2015, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.hotspot.test;
  24 
  25 import static java.util.Collections.singletonList;
  26 import static org.graalvm.compiler.core.CompilationWrapper.ExceptionAction.Print;
  27 import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationBailoutAction;
  28 import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationFailureAction;
  29 import static org.graalvm.compiler.core.test.ReflectionOptionDescriptors.extractEntries;
  30 import static org.graalvm.compiler.debug.MemUseTrackerKey.getCurrentThreadAllocatedBytes;
  31 import static org.graalvm.compiler.hotspot.test.CompileTheWorld.Options.DESCRIPTORS;
  32 import static org.graalvm.compiler.serviceprovider.JDK9Method.Java8OrEarlier;
  33 
  34 import java.io.Closeable;
  35 import java.io.File;
  36 import java.io.IOException;
  37 import java.lang.annotation.Annotation;
  38 import java.lang.reflect.Constructor;
  39 import java.lang.reflect.Method;
  40 import java.lang.reflect.Modifier;
  41 import java.net.URI;
  42 import java.net.URL;
  43 import java.net.URLClassLoader;
  44 import java.nio.file.FileSystem;
  45 import java.nio.file.FileSystems;
  46 import java.nio.file.FileVisitResult;
  47 import java.nio.file.Files;
  48 import java.nio.file.Path;
  49 import java.nio.file.SimpleFileVisitor;
  50 import java.nio.file.attribute.BasicFileAttributes;
  51 import java.util.ArrayList;
  52 import java.util.Arrays;
  53 import java.util.Collections;
  54 import java.util.Enumeration;
  55 import java.util.HashMap;
  56 import java.util.HashSet;
  57 import java.util.List;
  58 import java.util.Map;
  59 import java.util.ServiceLoader;
  60 import java.util.Set;
  61 import java.util.concurrent.ExecutionException;
  62 import java.util.concurrent.Future;
  63 import java.util.concurrent.LinkedBlockingQueue;
  64 import java.util.concurrent.ThreadPoolExecutor;
  65 import java.util.concurrent.TimeUnit;
  66 import java.util.concurrent.atomic.AtomicLong;
  67 import java.util.jar.JarEntry;
  68 import java.util.jar.JarFile;
  69 import java.util.stream.Collectors;
  70 
  71 import org.graalvm.compiler.api.replacements.Snippet;
  72 import org.graalvm.compiler.bytecode.Bytecodes;
  73 import org.graalvm.compiler.core.CompilerThreadFactory;
  74 import org.graalvm.compiler.core.test.ReflectionOptionDescriptors;
  75 import org.graalvm.compiler.debug.DebugOptions;
  76 import org.graalvm.compiler.debug.GraalError;
  77 import org.graalvm.compiler.debug.MethodFilter;
  78 import org.graalvm.compiler.debug.TTY;
  79 import org.graalvm.compiler.hotspot.CompilationTask;
  80 import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig;
  81 import org.graalvm.compiler.hotspot.HotSpotGraalCompiler;
  82 import org.graalvm.compiler.hotspot.HotSpotGraalRuntimeProvider;
  83 import org.graalvm.compiler.options.OptionDescriptors;
  84 import org.graalvm.compiler.options.OptionKey;
  85 import org.graalvm.compiler.options.OptionValues;
  86 import org.graalvm.compiler.options.OptionsParser;
  87 import org.graalvm.compiler.serviceprovider.JDK9Method;
  88 import org.graalvm.util.EconomicMap;
  89 import org.graalvm.util.UnmodifiableEconomicMap;
  90 
  91 import jdk.vm.ci.hotspot.HotSpotCodeCacheProvider;
  92 import jdk.vm.ci.hotspot.HotSpotCompilationRequest;
  93 import jdk.vm.ci.hotspot.HotSpotInstalledCode;
  94 import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
  95 import jdk.vm.ci.hotspot.HotSpotJVMCIRuntimeProvider;
  96 import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod;
  97 import jdk.vm.ci.hotspot.HotSpotResolvedObjectType;
  98 import jdk.vm.ci.meta.ConstantPool;
  99 import jdk.vm.ci.meta.MetaAccessProvider;
 100 import jdk.vm.ci.runtime.JVMCI;
 101 import jdk.vm.ci.runtime.JVMCICompiler;
 102 
 103 /**
 104  * This class implements compile-the-world functionality with JVMCI.
 105  */
 106 public final class CompileTheWorld {
 107 
 108     /**
 109      * Magic token to denote that JDK classes are to be compiled. If
 110      * {@link JDK9Method#Java8OrEarlier}, then the classes in {@code rt.jar} are compiled. Otherwise
 111      * the classes in the Java runtime image are compiled.
 112      */
 113     public static final String SUN_BOOT_CLASS_PATH = "sun.boot.class.path";
 114 
 115     /**
 116      * Magic token to denote the classes in the Java runtime image (i.e. in the {@code jrt:/} file
 117      * system).
 118      */
 119     public static final String JRT_CLASS_PATH_ENTRY = "<jrt>";
 120 
 121     /**
 122      * @param options a space separated set of option value settings with each option setting in a
 123      *            {@code -Dgraal.<name>=<value>} format but without the leading {@code -Dgraal.}.
 124      *            Ignored if null.
 125      */
 126     public static EconomicMap<OptionKey<?>, Object> parseOptions(String options) {
 127         if (options != null) {
 128             EconomicMap<String, String> optionSettings = EconomicMap.create();
 129             for (String optionSetting : options.split("\\s+|#")) {
 130                 OptionsParser.parseOptionSettingTo(optionSetting, optionSettings);
 131             }
 132             EconomicMap<OptionKey<?>, Object> values = OptionValues.newOptionMap();
 133             ServiceLoader<OptionDescriptors> loader = ServiceLoader.load(OptionDescriptors.class, OptionDescriptors.class.getClassLoader());
 134             OptionsParser.parseOptions(optionSettings, values, loader);
 135             return values;
 136         }
 137         return EconomicMap.create();
 138     }
 139 
 140     private final HotSpotJVMCIRuntimeProvider jvmciRuntime;
 141 
 142     private final HotSpotGraalCompiler compiler;
 143 
 144     /**
 145      * Class path denoting classes to compile.
 146      *
 147      * @see Options#Classpath
 148      */
 149     private final String inputClassPath;
 150 
 151     /**
 152      * Class index to start compilation at.
 153      *
 154      * @see Options#StartAt
 155      */
 156     private final int startAt;
 157 
 158     /**
 159      * Class index to stop compilation at.
 160      *
 161      * @see Options#StopAt
 162      */
 163     private final int stopAt;
 164 
 165     /** Only compile methods matching one of the filters in this array if the array is non-null. */
 166     private final MethodFilter[] methodFilters;
 167 
 168     /** Exclude methods matching one of the filters in this array if the array is non-null. */
 169     private final MethodFilter[] excludeMethodFilters;
 170 
 171     // Counters
 172     private int classFileCounter = 0;
 173     private AtomicLong compiledMethodsCounter = new AtomicLong();
 174     private AtomicLong compileTime = new AtomicLong();
 175     private AtomicLong memoryUsed = new AtomicLong();
 176 
 177     private boolean verbose;
 178 
 179     /**
 180      * Signal that the threads should start compiling in multithreaded mode.
 181      */
 182     private boolean running;
 183 
 184     private ThreadPoolExecutor threadPool;
 185 
 186     private OptionValues currentOptions;
 187     private final UnmodifiableEconomicMap<OptionKey<?>, Object> compilationOptions;
 188 
 189     /**
 190      * Creates a compile-the-world instance.
 191      *
 192      * @param files {@link File#pathSeparator} separated list of Zip/Jar files to compile
 193      * @param startAt index of the class file to start compilation at
 194      * @param stopAt index of the class file to stop compilation at
 195      * @param methodFilters
 196      * @param excludeMethodFilters
 197      */
 198     public CompileTheWorld(HotSpotJVMCIRuntimeProvider jvmciRuntime, HotSpotGraalCompiler compiler, String files, int startAt, int stopAt, String methodFilters, String excludeMethodFilters,
 199                     boolean verbose, OptionValues initialOptions, EconomicMap<OptionKey<?>, Object> compilationOptions) {
 200         this.jvmciRuntime = jvmciRuntime;
 201         this.compiler = compiler;
 202         this.inputClassPath = files;
 203         this.startAt = startAt;
 204         this.stopAt = stopAt;
 205         this.methodFilters = methodFilters == null || methodFilters.isEmpty() ? null : MethodFilter.parse(methodFilters);
 206         this.excludeMethodFilters = excludeMethodFilters == null || excludeMethodFilters.isEmpty() ? null : MethodFilter.parse(excludeMethodFilters);
 207         this.verbose = verbose;
 208         this.currentOptions = initialOptions;
 209 
 210         // Copy the initial options and add in any extra options
 211         EconomicMap<OptionKey<?>, Object> compilationOptionsCopy = EconomicMap.create(initialOptions.getMap());
 212         compilationOptionsCopy.putAll(compilationOptions);
 213 
 214         // We want to see stack traces when a method fails to compile
 215         CompilationBailoutAction.putIfAbsent(compilationOptionsCopy, Print);
 216         CompilationFailureAction.putIfAbsent(compilationOptionsCopy, Print);
 217 
 218         // By default only report statistics for the CTW threads themselves
 219         DebugOptions.MetricsThreadFilter.putIfAbsent(compilationOptionsCopy, "^CompileTheWorld");
 220         this.compilationOptions = compilationOptionsCopy;
 221     }
 222 
 223     public CompileTheWorld(HotSpotJVMCIRuntimeProvider jvmciRuntime, HotSpotGraalCompiler compiler, OptionValues options) {
 224         this(jvmciRuntime, compiler, Options.Classpath.getValue(options),
 225                         Options.StartAt.getValue(options),
 226                         Options.StopAt.getValue(options),
 227                         Options.MethodFilter.getValue(options),
 228                         Options.ExcludeMethodFilter.getValue(options),
 229                         Options.Verbose.getValue(options),
 230                         options,
 231                         parseOptions(Options.Config.getValue(options)));
 232     }
 233 
 234     /**
 235      * Compiles all methods in all classes in {@link #inputClassPath}. If {@link #inputClassPath}
 236      * equals {@link #SUN_BOOT_CLASS_PATH} the boot classes are used.
 237      */
 238     public void compile() throws Throwable {
 239         if (SUN_BOOT_CLASS_PATH.equals(inputClassPath)) {
 240             String bcpEntry = null;
 241             if (Java8OrEarlier) {
 242                 final String[] entries = System.getProperty(SUN_BOOT_CLASS_PATH).split(File.pathSeparator);
 243                 for (int i = 0; i < entries.length && bcpEntry == null; i++) {
 244                     String entry = entries[i];
 245                     File entryFile = new File(entry);
 246                     if (entryFile.getName().endsWith("rt.jar") && entryFile.isFile()) {
 247                         bcpEntry = entry;
 248                     }
 249                 }
 250                 if (bcpEntry == null) {
 251                     throw new GraalError("Could not find rt.jar on boot class path %s", System.getProperty(SUN_BOOT_CLASS_PATH));
 252                 }
 253             } else {
 254                 bcpEntry = JRT_CLASS_PATH_ENTRY;
 255             }
 256             compile(bcpEntry);
 257         } else {
 258             compile(inputClassPath);
 259         }
 260     }
 261 
 262     public void println() {
 263         println("");
 264     }
 265 
 266     public void println(String format, Object... args) {
 267         println(String.format(format, args));
 268     }
 269 
 270     public void println(String s) {
 271         println(verbose, s);
 272     }
 273 
 274     public static void println(boolean cond, String s) {
 275         if (cond) {
 276             TTY.println(s);
 277         }
 278     }
 279 
 280     public void printStackTrace(Throwable t) {
 281         if (verbose) {
 282             t.printStackTrace(TTY.out);
 283         }
 284     }
 285 
 286     @SuppressWarnings("unused")
 287     private static void dummy() {
 288     }
 289 
 290     /**
 291      * Abstraction over different types of class path entries.
 292      */
 293     abstract static class ClassPathEntry implements Closeable {
 294         final String name;
 295 
 296         ClassPathEntry(String name) {
 297             this.name = name;
 298         }
 299 
 300         /**
 301          * Creates a {@link ClassLoader} for loading classes from this entry.
 302          */
 303         public abstract ClassLoader createClassLoader() throws IOException;
 304 
 305         /**
 306          * Gets the list of classes available under this entry.
 307          */
 308         public abstract List<String> getClassNames() throws IOException;
 309 
 310         @Override
 311         public String toString() {
 312             return name;
 313         }
 314 
 315         @Override
 316         public void close() throws IOException {
 317         }
 318     }
 319 
 320     /**
 321      * A class path entry that is a normal file system directory.
 322      */
 323     static class DirClassPathEntry extends ClassPathEntry {
 324 
 325         private final File dir;
 326 
 327         DirClassPathEntry(String name) {
 328             super(name);
 329             dir = new File(name);
 330             assert dir.isDirectory();
 331         }
 332 
 333         @Override
 334         public ClassLoader createClassLoader() throws IOException {
 335             URL url = dir.toURI().toURL();
 336             return new URLClassLoader(new URL[]{url});
 337         }
 338 
 339         @Override
 340         public List<String> getClassNames() throws IOException {
 341             List<String> classNames = new ArrayList<>();
 342             String root = dir.getPath();
 343             SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
 344                 @Override
 345                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 346                     if (attrs.isRegularFile()) {
 347                         File path = file.toFile();
 348                         if (path.getName().endsWith(".class")) {
 349                             String pathString = path.getPath();
 350                             assert pathString.startsWith(root);
 351                             String classFile = pathString.substring(root.length() + 1);
 352                             String className = classFile.replace(File.separatorChar, '.');
 353                             classNames.add(className.replace('/', '.').substring(0, className.length() - ".class".length()));
 354                         }
 355                     }
 356                     return super.visitFile(file, attrs);
 357                 }
 358             };
 359             Files.walkFileTree(dir.toPath(), visitor);
 360             return classNames;
 361         }
 362     }
 363 
 364     /**
 365      * A class path entry that is a jar or zip file.
 366      */
 367     static class JarClassPathEntry extends ClassPathEntry {
 368 
 369         private final JarFile jarFile;
 370 
 371         JarClassPathEntry(String name) throws IOException {
 372             super(name);
 373             jarFile = new JarFile(name);
 374         }
 375 
 376         @Override
 377         public ClassLoader createClassLoader() throws IOException {
 378             URL url = new URL("jar", "", "file:" + name + "!/");
 379             return new URLClassLoader(new URL[]{url});
 380         }
 381 
 382         @Override
 383         public List<String> getClassNames() throws IOException {
 384             Enumeration<JarEntry> e = jarFile.entries();
 385             List<String> classNames = new ArrayList<>(jarFile.size());
 386             while (e.hasMoreElements()) {
 387                 JarEntry je = e.nextElement();
 388                 if (je.isDirectory() || !je.getName().endsWith(".class")) {
 389                     continue;
 390                 }
 391                 String className = je.getName().substring(0, je.getName().length() - ".class".length());
 392                 classNames.add(className.replace('/', '.'));
 393             }
 394             return classNames;
 395         }
 396 
 397         @Override
 398         public void close() throws IOException {
 399             jarFile.close();
 400         }
 401     }
 402 
 403     /**
 404      * A class path entry representing the {@code jrt:/} file system.
 405      */
 406     static class JRTClassPathEntry extends ClassPathEntry {
 407 
 408         private final String limitModules;
 409 
 410         JRTClassPathEntry(String name, String limitModules) {
 411             super(name);
 412             this.limitModules = limitModules;
 413         }
 414 
 415         @Override
 416         public ClassLoader createClassLoader() throws IOException {
 417             URL url = URI.create("jrt:/").toURL();
 418             return new URLClassLoader(new URL[]{url});
 419         }
 420 
 421         @Override
 422         public List<String> getClassNames() throws IOException {
 423             Set<String> negative = new HashSet<>();
 424             Set<String> positive = new HashSet<>();
 425             if (limitModules != null && !limitModules.isEmpty()) {
 426                 for (String s : limitModules.split(",")) {
 427                     if (s.startsWith("~")) {
 428                         negative.add(s.substring(1));
 429                     } else {
 430                         positive.add(s);
 431                     }
 432                 }
 433             }
 434             List<String> classNames = new ArrayList<>();
 435             FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap());
 436             Path top = fs.getPath("/modules/");
 437             Files.find(top, Integer.MAX_VALUE,
 438                             (path, attrs) -> attrs.isRegularFile()).forEach(p -> {
 439                                 int nameCount = p.getNameCount();
 440                                 if (nameCount > 2) {
 441                                     String base = p.getName(nameCount - 1).toString();
 442                                     if (base.endsWith(".class") && !base.equals("module-info.class")) {
 443                                         String module = p.getName(1).toString();
 444                                         if (positive.isEmpty() || positive.contains(module)) {
 445                                             if (negative.isEmpty() || !negative.contains(module)) {
 446                                                 // Strip module prefix and convert to dotted form
 447                                                 String className = p.subpath(2, nameCount).toString().replace('/', '.');
 448                                                 // Strip ".class" suffix
 449                                                 className = className.replace('/', '.').substring(0, className.length() - ".class".length());
 450                                                 classNames.add(className);
 451                                             }
 452                                         }
 453                                     }
 454                                 }
 455                             });
 456             return classNames;
 457         }
 458     }
 459 
 460     private boolean isClassIncluded(String className) {
 461         if (methodFilters != null && !MethodFilter.matchesClassName(methodFilters, className)) {
 462             return false;
 463         }
 464         if (excludeMethodFilters != null && MethodFilter.matchesClassName(excludeMethodFilters, className)) {
 465             return false;
 466         }
 467         return true;
 468     }
 469 
 470     /**
 471      * Compiles all methods in all classes in a given class path.
 472      *
 473      * @param classPath class path denoting classes to compile
 474      * @throws IOException
 475      */
 476     @SuppressWarnings("try")
 477     private void compile(String classPath) throws IOException {
 478         final String[] entries = classPath.split(File.pathSeparator);
 479         long start = System.currentTimeMillis();
 480         Map<Thread, StackTraceElement[]> initialThreads = Thread.getAllStackTraces();
 481 
 482         try {
 483             // compile dummy method to get compiler initialized outside of the
 484             // config debug override.
 485             HotSpotResolvedJavaMethod dummyMethod = (HotSpotResolvedJavaMethod) JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaMethod(
 486                             CompileTheWorld.class.getDeclaredMethod("dummy"));
 487             int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI;
 488             boolean useProfilingInfo = false;
 489             boolean installAsDefault = false;
 490             CompilationTask task = new CompilationTask(jvmciRuntime, compiler, new HotSpotCompilationRequest(dummyMethod, entryBCI, 0L), useProfilingInfo, installAsDefault, currentOptions);
 491             task.runCompilation();
 492         } catch (NoSuchMethodException | SecurityException e1) {
 493             printStackTrace(e1);
 494         }
 495 
 496         /*
 497          * Always use a thread pool, even for single threaded mode since it simplifies the use of
 498          * DebugValueThreadFilter to filter on the thread names.
 499          */
 500         int threadCount = 1;
 501         if (Options.MultiThreaded.getValue(currentOptions)) {
 502             threadCount = Options.Threads.getValue(currentOptions);
 503             if (threadCount == 0) {
 504                 threadCount = Runtime.getRuntime().availableProcessors();
 505             }
 506         } else {
 507             running = true;
 508         }
 509 
 510         OptionValues savedOptions = currentOptions;
 511         currentOptions = new OptionValues(compilationOptions);
 512         threadPool = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new CompilerThreadFactory("CompileTheWorld"));
 513 
 514         try {
 515             for (int i = 0; i < entries.length; i++) {
 516                 final String entry = entries[i];
 517 
 518                 ClassPathEntry cpe;
 519                 if (entry.endsWith(".zip") || entry.endsWith(".jar")) {
 520                     cpe = new JarClassPathEntry(entry);
 521                 } else if (entry.equals(JRT_CLASS_PATH_ENTRY)) {
 522                     cpe = new JRTClassPathEntry(entry, Options.LimitModules.getValue(currentOptions));
 523                 } else {
 524                     if (!new File(entry).isDirectory()) {
 525                         println("CompileTheWorld : Skipped classes in " + entry);
 526                         println();
 527                         continue;
 528                     }
 529                     cpe = new DirClassPathEntry(entry);
 530                 }
 531 
 532                 if (methodFilters == null || methodFilters.length == 0) {
 533                     println("CompileTheWorld : Compiling all classes in " + entry);
 534                 } else {
 535                     String include = Arrays.asList(methodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", "));
 536                     println("CompileTheWorld : Compiling all methods in " + entry + " matching one of the following filters: " + include);
 537                 }
 538                 if (excludeMethodFilters != null && excludeMethodFilters.length > 0) {
 539                     String exclude = Arrays.asList(excludeMethodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", "));
 540                     println("CompileTheWorld : Excluding all methods matching one of the following filters: " + exclude);
 541                 }
 542                 println();
 543 
 544                 ClassLoader loader = cpe.createClassLoader();
 545 
 546                 for (String className : cpe.getClassNames()) {
 547 
 548                     // Are we done?
 549                     if (classFileCounter >= stopAt) {
 550                         break;
 551                     }
 552 
 553                     classFileCounter++;
 554 
 555                     if (className.startsWith("jdk.management.") ||
 556                                     className.startsWith("jdk.internal.cmm.*") ||
 557                                     // GR-5881: The class initializer for
 558                                     // sun.tools.jconsole.OutputViewer
 559                                     // spawns non-daemon threads for redirecting sysout and syserr.
 560                                     // These threads tend to cause deadlock at VM exit
 561                                     className.startsWith("sun.tools.jconsole.")) {
 562                         continue;
 563                     }
 564 
 565                     try {
 566                         // Load and initialize class
 567                         Class<?> javaClass = Class.forName(className, true, loader);
 568 
 569                         // Pre-load all classes in the constant pool.
 570                         try {
 571                             HotSpotResolvedObjectType objectType = HotSpotResolvedObjectType.fromObjectClass(javaClass);
 572                             ConstantPool constantPool = objectType.getConstantPool();
 573                             for (int cpi = 1; cpi < constantPool.length(); cpi++) {
 574                                 constantPool.loadReferencedType(cpi, Bytecodes.LDC);
 575                             }
 576                         } catch (Throwable t) {
 577                             // If something went wrong during pre-loading we just ignore it.
 578                             if (isClassIncluded(className)) {
 579                                 println("Preloading failed for (%d) %s: %s", classFileCounter, className, t);
 580                             }
 581                             continue;
 582                         }
 583 
 584                         /*
 585                          * Only check filters after class loading and resolution to mitigate impact
 586                          * on reproducibility.
 587                          */
 588                         if (!isClassIncluded(className)) {
 589                             continue;
 590                         }
 591 
 592                         // Are we compiling this class?
 593                         MetaAccessProvider metaAccess = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess();
 594                         if (classFileCounter >= startAt) {
 595                             println("CompileTheWorld (%d) : %s", classFileCounter, className);
 596 
 597                             // Compile each constructor/method in the class.
 598                             for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
 599                                 HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(constructor);
 600                                 if (canBeCompiled(javaMethod, constructor.getModifiers())) {
 601                                     compileMethod(javaMethod);
 602                                 }
 603                             }
 604                             for (Method method : javaClass.getDeclaredMethods()) {
 605                                 HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(method);
 606                                 if (canBeCompiled(javaMethod, method.getModifiers())) {
 607                                     compileMethod(javaMethod);
 608                                 }
 609                             }
 610 
 611                             // Also compile the class initializer if it exists
 612                             HotSpotResolvedJavaMethod clinit = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaType(javaClass).getClassInitializer();
 613                             if (clinit != null && canBeCompiled(clinit, clinit.getModifiers())) {
 614                                 compileMethod(clinit);
 615                             }
 616                         }
 617                     } catch (Throwable t) {
 618                         if (isClassIncluded(className)) {
 619                             println("CompileTheWorld (%d) : Skipping %s %s", classFileCounter, className, t.toString());
 620                             printStackTrace(t);
 621                         }
 622                     }
 623                 }
 624                 cpe.close();
 625             }
 626         } finally {
 627             currentOptions = savedOptions;
 628         }
 629 
 630         if (!running) {
 631             startThreads();
 632         }
 633         int wakeups = 0;
 634         while (threadPool.getCompletedTaskCount() != threadPool.getTaskCount()) {
 635             if (wakeups % 15 == 0) {
 636                 TTY.println("CompileTheWorld : Waiting for " + (threadPool.getTaskCount() - threadPool.getCompletedTaskCount()) + " compiles");
 637             }
 638             try {
 639                 threadPool.awaitTermination(1, TimeUnit.SECONDS);
 640                 wakeups++;
 641             } catch (InterruptedException e) {
 642             }
 643         }
 644         threadPool = null;
 645 
 646         long elapsedTime = System.currentTimeMillis() - start;
 647 
 648         println();
 649         if (Options.MultiThreaded.getValue(currentOptions)) {
 650             TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms elapsed, %d ms compile time, %d bytes of memory used)", classFileCounter, compiledMethodsCounter.get(), elapsedTime,
 651                             compileTime.get(), memoryUsed.get());
 652         } else {
 653             TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms, %d bytes of memory used)", classFileCounter, compiledMethodsCounter.get(), compileTime.get(), memoryUsed.get());
 654         }
 655 
 656         // Apart from the main thread, there should be only be daemon threads
 657         // alive now. If not, then a class initializer has probably started
 658         // a thread that could cause a deadlock while trying to exit the VM.
 659         // One known example of this is sun.tools.jconsole.OutputViewer which
 660         // spawns threads to redirect sysout and syserr. To help debug such
 661         // scenarios, the stacks of potentially problematic threads are dumped.
 662         Map<Thread, StackTraceElement[]> suspiciousThreads = new HashMap<>();
 663         for (Map.Entry<Thread, StackTraceElement[]> e : Thread.getAllStackTraces().entrySet()) {
 664             Thread thread = e.getKey();
 665             if (thread != Thread.currentThread() && !initialThreads.containsKey(thread) && !thread.isDaemon() && thread.isAlive()) {
 666                 suspiciousThreads.put(thread, e.getValue());
 667             }
 668         }
 669         if (!suspiciousThreads.isEmpty()) {
 670             TTY.println("--- Non-daemon threads started during CTW ---");
 671             for (Map.Entry<Thread, StackTraceElement[]> e : suspiciousThreads.entrySet()) {
 672                 Thread thread = e.getKey();
 673                 if (thread.isAlive()) {
 674                     TTY.println(thread.toString() + " " + thread.getState());
 675                     for (StackTraceElement ste : e.getValue()) {
 676                         TTY.println("\tat " + ste);
 677                     }
 678                 }
 679             }
 680             TTY.println("---------------------------------------------");
 681         }
 682     }
 683 
 684     private synchronized void startThreads() {
 685         running = true;
 686         // Wake up any waiting threads
 687         notifyAll();
 688     }
 689 
 690     private synchronized void waitToRun() {
 691         while (!running) {
 692             try {
 693                 wait();
 694             } catch (InterruptedException e) {
 695             }
 696         }
 697     }
 698 
 699     @SuppressWarnings("try")
 700     private void compileMethod(HotSpotResolvedJavaMethod method) throws InterruptedException, ExecutionException {
 701         if (methodFilters != null && !MethodFilter.matches(methodFilters, method)) {
 702             return;
 703         }
 704         if (excludeMethodFilters != null && MethodFilter.matches(excludeMethodFilters, method)) {
 705             return;
 706         }
 707         Future<?> task = threadPool.submit(new Runnable() {
 708             @Override
 709             public void run() {
 710                 waitToRun();
 711                 OptionValues savedOptions = currentOptions;
 712                 currentOptions = new OptionValues(compilationOptions);
 713                 try {
 714                     compileMethod(method, classFileCounter);
 715                 } finally {
 716                     currentOptions = savedOptions;
 717                 }
 718             }
 719         });
 720         if (threadPool.getCorePoolSize() == 1) {
 721             task.get();
 722         }
 723     }
 724 
 725     /**
 726      * Compiles a method and gathers some statistics.
 727      */
 728     private void compileMethod(HotSpotResolvedJavaMethod method, int counter) {
 729         try {
 730             long start = System.currentTimeMillis();
 731             long allocatedAtStart = getCurrentThreadAllocatedBytes();
 732             int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI;
 733             HotSpotCompilationRequest request = new HotSpotCompilationRequest(method, entryBCI, 0L);
 734             // For more stable CTW execution, disable use of profiling information
 735             boolean useProfilingInfo = false;
 736             boolean installAsDefault = false;
 737             CompilationTask task = new CompilationTask(jvmciRuntime, compiler, request, useProfilingInfo, installAsDefault, currentOptions);
 738             task.runCompilation();
 739 
 740             // Invalidate the generated code so the code cache doesn't fill up
 741             HotSpotInstalledCode installedCode = task.getInstalledCode();
 742             if (installedCode != null) {
 743                 installedCode.invalidate();
 744             }
 745 
 746             memoryUsed.getAndAdd(getCurrentThreadAllocatedBytes() - allocatedAtStart);
 747             compileTime.getAndAdd(System.currentTimeMillis() - start);
 748             compiledMethodsCounter.incrementAndGet();
 749         } catch (Throwable t) {
 750             // Catch everything and print a message
 751             println("CompileTheWorld (%d) : Error compiling method: %s", counter, method.format("%H.%n(%p):%r"));
 752             printStackTrace(t);
 753         }
 754     }
 755 
 756     /**
 757      * Determines if a method should be compiled (Cf. CompilationPolicy::can_be_compiled).
 758      *
 759      * @return true if it can be compiled, false otherwise
 760      */
 761     private boolean canBeCompiled(HotSpotResolvedJavaMethod javaMethod, int modifiers) {
 762         if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) {
 763             return false;
 764         }
 765         GraalHotSpotVMConfig c = compiler.getGraalRuntime().getVMConfig();
 766         if (c.dontCompileHugeMethods && javaMethod.getCodeSize() > c.hugeMethodLimit) {
 767             println(verbose || methodFilters != null,
 768                             String.format("CompileTheWorld (%d) : Skipping huge method %s (use -XX:-DontCompileHugeMethods or -XX:HugeMethodLimit=%d to include it)", classFileCounter,
 769                                             javaMethod.format("%H.%n(%p):%r"),
 770                                             javaMethod.getCodeSize()));
 771             return false;
 772         }
 773         // Allow use of -XX:CompileCommand=dontinline to exclude problematic methods
 774         if (!javaMethod.canBeInlined()) {
 775             return false;
 776         }
 777         // Skip @Snippets for now
 778         for (Annotation annotation : javaMethod.getAnnotations()) {
 779             if (annotation.annotationType().equals(Snippet.class)) {
 780                 return false;
 781             }
 782         }
 783         return true;
 784     }
 785 
 786     static class Options {
 787         // @formatter:off
 788         public static final OptionKey<Boolean> Help = new OptionKey<>(false);
 789         public static final OptionKey<String> Classpath = new OptionKey<>(CompileTheWorld.SUN_BOOT_CLASS_PATH);
 790         public static final OptionKey<Boolean> Verbose = new OptionKey<>(true);
 791         /**
 792          * Ignore Graal classes by default to avoid problems associated with compiling
 793          * snippets and method substitutions.
 794          */
 795         public static final OptionKey<String> LimitModules = new OptionKey<>("~jdk.internal.vm.compiler");
 796         public static final OptionKey<Integer> Iterations = new OptionKey<>(1);
 797         public static final OptionKey<String> MethodFilter = new OptionKey<>(null);
 798         public static final OptionKey<String> ExcludeMethodFilter = new OptionKey<>(null);
 799         public static final OptionKey<Integer> StartAt = new OptionKey<>(1);
 800         public static final OptionKey<Integer> StopAt = new OptionKey<>(Integer.MAX_VALUE);
 801         public static final OptionKey<String> Config = new OptionKey<>(null);
 802         public static final OptionKey<Boolean> MultiThreaded = new OptionKey<>(false);
 803         public static final OptionKey<Integer> Threads = new OptionKey<>(0);
 804 
 805         static final ReflectionOptionDescriptors DESCRIPTORS = new ReflectionOptionDescriptors(Options.class,
 806                            "Help", "List options and their help messages and then exit.",
 807                       "Classpath", "Class path denoting methods to compile. Default is to compile boot classes.",
 808                         "Verbose", "Verbose operation.",
 809                    "LimitModules", "Comma separated list of module names to which compilation should be limited. " +
 810                                    "Module names can be prefixed with \"~\" to exclude the named module.",
 811                      "Iterations", "The number of iterations to perform.",
 812                    "MethodFilter", "Only compile methods matching this filter.",
 813             "ExcludeMethodFilter", "Exclude methods matching this filter from compilation.",
 814                         "StartAt", "First class to consider for compilation.",
 815                          "StopAt", "Last class to consider for compilation.",
 816                          "Config", "Option value overrides to use during compile the world. For example, " +
 817                                    "to disable inlining and partial escape analysis specify 'PartialEscapeAnalysis=false Inline=false'. " +
 818                                    "The format for each option is the same as on the command line just without the '-Dgraal.' prefix.",
 819                   "MultiThreaded", "Run using multiple threads for compilation.",
 820                         "Threads", "Number of threads to use for multithreaded execution. Defaults to Runtime.getRuntime().availableProcessors().");
 821         // @formatter:on
 822     }
 823 
 824     public static OptionValues loadOptions(OptionValues initialValues) {
 825         EconomicMap<OptionKey<?>, Object> values = OptionValues.newOptionMap();
 826         List<OptionDescriptors> loader = singletonList(DESCRIPTORS);
 827         OptionsParser.parseOptions(extractEntries(System.getProperties(), "CompileTheWorld.", true), values, loader);
 828         OptionValues options = new OptionValues(initialValues, values);
 829         if (Options.Help.getValue(options)) {
 830             options.printHelp(loader, System.out, "CompileTheWorld.");
 831             System.exit(0);
 832         }
 833         return options;
 834     }
 835 
 836     public static void main(String[] args) throws Throwable {
 837         HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime();
 838         HotSpotGraalCompiler compiler = (HotSpotGraalCompiler) jvmciRuntime.getCompiler();
 839         HotSpotGraalRuntimeProvider graalRuntime = compiler.getGraalRuntime();
 840         HotSpotCodeCacheProvider codeCache = graalRuntime.getHostProviders().getCodeCache();
 841         OptionValues options = loadOptions(graalRuntime.getOptions());
 842 
 843         int iterations = Options.Iterations.getValue(options);
 844         for (int i = 0; i < iterations; i++) {
 845             codeCache.resetCompilationStatistics();
 846             TTY.println("CompileTheWorld : iteration " + i);
 847 
 848             CompileTheWorld ctw = new CompileTheWorld(jvmciRuntime, compiler, options);
 849             ctw.compile();
 850         }
 851         // This is required as non-daemon threads can be started by class initializers
 852         System.exit(0);
 853     }
 854 }