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.GraalCompilerOptions.ExitVMOnException;
  27 import static org.graalvm.compiler.core.GraalCompilerOptions.PrintBailout;
  28 import static org.graalvm.compiler.core.GraalCompilerOptions.PrintStackTraceOnException;
  29 import static org.graalvm.compiler.core.test.ReflectionOptionDescriptors.extractEntries;
  30 import static org.graalvm.compiler.hotspot.test.CompileTheWorld.Options.DESCRIPTORS;
  31 import static org.graalvm.compiler.serviceprovider.JDK9Method.Java8OrEarlier;
  32 
  33 import java.io.Closeable;
  34 import java.io.File;
  35 import java.io.IOException;
  36 import java.lang.annotation.Annotation;
  37 import java.lang.reflect.Constructor;
  38 import java.lang.reflect.Method;
  39 import java.lang.reflect.Modifier;
  40 import java.net.URI;
  41 import java.net.URL;
  42 import java.net.URLClassLoader;
  43 import java.nio.file.FileSystem;
  44 import java.nio.file.FileSystems;
  45 import java.nio.file.FileVisitResult;
  46 import java.nio.file.Files;
  47 import java.nio.file.Path;
  48 import java.nio.file.SimpleFileVisitor;
  49 import java.nio.file.attribute.BasicFileAttributes;
  50 import java.util.ArrayList;
  51 import java.util.Arrays;
  52 import java.util.Collections;
  53 import java.util.Enumeration;
  54 import java.util.HashSet;
  55 import java.util.List;
  56 import java.util.ServiceLoader;
  57 import java.util.Set;
  58 import java.util.concurrent.ExecutionException;
  59 import java.util.concurrent.Future;
  60 import java.util.concurrent.LinkedBlockingQueue;
  61 import java.util.concurrent.ThreadPoolExecutor;
  62 import java.util.concurrent.TimeUnit;
  63 import java.util.concurrent.atomic.AtomicLong;
  64 import java.util.jar.JarEntry;
  65 import java.util.jar.JarFile;
  66 import java.util.stream.Collectors;
  67 
  68 import org.graalvm.compiler.api.replacements.Snippet;
  69 import org.graalvm.compiler.bytecode.Bytecodes;
  70 import org.graalvm.compiler.core.CompilerThreadFactory;
  71 import org.graalvm.compiler.core.CompilerThreadFactory.DebugConfigAccess;
  72 import org.graalvm.compiler.core.test.ReflectionOptionDescriptors;
  73 import org.graalvm.compiler.debug.DebugEnvironment;
  74 import org.graalvm.compiler.debug.GraalDebugConfig;
  75 import org.graalvm.compiler.debug.GraalError;
  76 import org.graalvm.compiler.debug.MethodFilter;
  77 import org.graalvm.compiler.debug.TTY;
  78 import org.graalvm.compiler.debug.internal.MemUseTrackerImpl;
  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 don't want the VM to exit when a method fails to compile...
 215         ExitVMOnException.putIfAbsent(compilationOptionsCopy, false);
 216 
 217         // ...but we want to see exceptions.
 218         PrintBailout.putIfAbsent(compilationOptionsCopy, true);
 219         PrintStackTraceOnException.putIfAbsent(compilationOptionsCopy, true);
 220 
 221         // By default only report statistics for the CTW threads themselves
 222         GraalDebugConfig.Options.DebugValueThreadFilter.putIfAbsent(compilationOptionsCopy, "^CompileTheWorld");
 223         this.compilationOptions = compilationOptionsCopy;
 224     }
 225 
 226     public CompileTheWorld(HotSpotJVMCIRuntimeProvider jvmciRuntime, HotSpotGraalCompiler compiler, OptionValues options) {
 227         this(jvmciRuntime, compiler, Options.Classpath.getValue(options),
 228                         Options.StartAt.getValue(options),
 229                         Options.StopAt.getValue(options),
 230                         Options.MethodFilter.getValue(options),
 231                         Options.ExcludeMethodFilter.getValue(options),
 232                         Options.Verbose.getValue(options),
 233                         options,
 234                         parseOptions(Options.Config.getValue(options)));
 235     }
 236 
 237     /**
 238      * Compiles all methods in all classes in {@link #inputClassPath}. If {@link #inputClassPath}
 239      * equals {@link #SUN_BOOT_CLASS_PATH} the boot classes are used.
 240      */
 241     public void compile() throws Throwable {
 242         if (SUN_BOOT_CLASS_PATH.equals(inputClassPath)) {
 243             String bcpEntry = null;
 244             if (Java8OrEarlier) {
 245                 final String[] entries = System.getProperty(SUN_BOOT_CLASS_PATH).split(File.pathSeparator);
 246                 for (int i = 0; i < entries.length && bcpEntry == null; i++) {
 247                     String entry = entries[i];
 248                     File entryFile = new File(entry);
 249                     if (entryFile.getName().endsWith("rt.jar") && entryFile.isFile()) {
 250                         bcpEntry = entry;
 251                     }
 252                 }
 253                 if (bcpEntry == null) {
 254                     throw new GraalError("Could not find rt.jar on boot class path %s", System.getProperty(SUN_BOOT_CLASS_PATH));
 255                 }
 256             } else {
 257                 bcpEntry = JRT_CLASS_PATH_ENTRY;
 258             }
 259             compile(bcpEntry);
 260         } else {
 261             compile(inputClassPath);
 262         }
 263     }
 264 
 265     public void println() {
 266         println("");
 267     }
 268 
 269     public void println(String format, Object... args) {
 270         println(String.format(format, args));
 271     }
 272 
 273     public void println(String s) {
 274         println(verbose, s);
 275     }
 276 
 277     public static void println(boolean cond, String s) {
 278         if (cond) {
 279             TTY.println(s);
 280         }
 281     }
 282 
 283     public void printStackTrace(Throwable t) {
 284         if (verbose) {
 285             t.printStackTrace(TTY.out);
 286         }
 287     }
 288 
 289     @SuppressWarnings("unused")
 290     private static void dummy() {
 291     }
 292 
 293     /**
 294      * Abstraction over different types of class path entries.
 295      */
 296     abstract static class ClassPathEntry implements Closeable {
 297         final String name;
 298 
 299         ClassPathEntry(String name) {
 300             this.name = name;
 301         }
 302 
 303         /**
 304          * Creates a {@link ClassLoader} for loading classes from this entry.
 305          */
 306         public abstract ClassLoader createClassLoader() throws IOException;
 307 
 308         /**
 309          * Gets the list of classes available under this entry.
 310          */
 311         public abstract List<String> getClassNames() throws IOException;
 312 
 313         @Override
 314         public String toString() {
 315             return name;
 316         }
 317 
 318         @Override
 319         public void close() throws IOException {
 320         }
 321     }
 322 
 323     /**
 324      * A class path entry that is a normal file system directory.
 325      */
 326     static class DirClassPathEntry extends ClassPathEntry {
 327 
 328         private final File dir;
 329 
 330         DirClassPathEntry(String name) {
 331             super(name);
 332             dir = new File(name);
 333             assert dir.isDirectory();
 334         }
 335 
 336         @Override
 337         public ClassLoader createClassLoader() throws IOException {
 338             URL url = dir.toURI().toURL();
 339             return new URLClassLoader(new URL[]{url});
 340         }
 341 
 342         @Override
 343         public List<String> getClassNames() throws IOException {
 344             List<String> classNames = new ArrayList<>();
 345             String root = dir.getPath();
 346             SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
 347                 @Override
 348                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 349                     if (attrs.isRegularFile()) {
 350                         File path = file.toFile();
 351                         if (path.getName().endsWith(".class")) {
 352                             String pathString = path.getPath();
 353                             assert pathString.startsWith(root);
 354                             String classFile = pathString.substring(root.length() + 1);
 355                             String className = classFile.replace(File.separatorChar, '.');
 356                             classNames.add(className.replace('/', '.').substring(0, className.length() - ".class".length()));
 357                         }
 358                     }
 359                     return super.visitFile(file, attrs);
 360                 }
 361             };
 362             Files.walkFileTree(dir.toPath(), visitor);
 363             return classNames;
 364         }
 365     }
 366 
 367     /**
 368      * A class path entry that is a jar or zip file.
 369      */
 370     static class JarClassPathEntry extends ClassPathEntry {
 371 
 372         private final JarFile jarFile;
 373 
 374         JarClassPathEntry(String name) throws IOException {
 375             super(name);
 376             jarFile = new JarFile(name);
 377         }
 378 
 379         @Override
 380         public ClassLoader createClassLoader() throws IOException {
 381             URL url = new URL("jar", "", "file:" + name + "!/");
 382             return new URLClassLoader(new URL[]{url});
 383         }
 384 
 385         @Override
 386         public List<String> getClassNames() throws IOException {
 387             Enumeration<JarEntry> e = jarFile.entries();
 388             List<String> classNames = new ArrayList<>(jarFile.size());
 389             while (e.hasMoreElements()) {
 390                 JarEntry je = e.nextElement();
 391                 if (je.isDirectory() || !je.getName().endsWith(".class")) {
 392                     continue;
 393                 }
 394                 String className = je.getName().substring(0, je.getName().length() - ".class".length());
 395                 classNames.add(className.replace('/', '.'));
 396             }
 397             return classNames;
 398         }
 399 
 400         @Override
 401         public void close() throws IOException {
 402             jarFile.close();
 403         }
 404     }
 405 
 406     /**
 407      * A class path entry representing the {@code jrt:/} file system.
 408      */
 409     static class JRTClassPathEntry extends ClassPathEntry {
 410 
 411         private final String limitModules;
 412 
 413         JRTClassPathEntry(String name, String limitModules) {
 414             super(name);
 415             this.limitModules = limitModules;
 416         }
 417 
 418         @Override
 419         public ClassLoader createClassLoader() throws IOException {
 420             URL url = URI.create("jrt:/").toURL();
 421             return new URLClassLoader(new URL[]{url});
 422         }
 423 
 424         @Override
 425         public List<String> getClassNames() throws IOException {
 426             Set<String> negative = new HashSet<>();
 427             Set<String> positive = new HashSet<>();
 428             if (limitModules != null && !limitModules.isEmpty()) {
 429                 for (String s : limitModules.split(",")) {
 430                     if (s.startsWith("~")) {
 431                         negative.add(s.substring(1));
 432                     } else {
 433                         positive.add(s);
 434                     }
 435                 }
 436             }
 437             List<String> classNames = new ArrayList<>();
 438             FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap());
 439             Path top = fs.getPath("/modules/");
 440             Files.find(top, Integer.MAX_VALUE,
 441                             (path, attrs) -> attrs.isRegularFile()).forEach(p -> {
 442                                 int nameCount = p.getNameCount();
 443                                 if (nameCount > 2) {
 444                                     String base = p.getName(nameCount - 1).toString();
 445                                     if (base.endsWith(".class") && !base.equals("module-info.class")) {
 446                                         String module = p.getName(1).toString();
 447                                         if (positive.isEmpty() || positive.contains(module)) {
 448                                             if (negative.isEmpty() || !negative.contains(module)) {
 449                                                 // Strip module prefix and convert to dotted form
 450                                                 String className = p.subpath(2, nameCount).toString().replace('/', '.');
 451                                                 // Strip ".class" suffix
 452                                                 className = className.replace('/', '.').substring(0, className.length() - ".class".length());
 453                                                 classNames.add(className);
 454                                             }
 455                                         }
 456                                     }
 457                                 }
 458                             });
 459             return classNames;
 460         }
 461     }
 462 
 463     private boolean isClassIncluded(String className) {
 464         if (methodFilters != null && !MethodFilter.matchesClassName(methodFilters, className)) {
 465             return false;
 466         }
 467         if (excludeMethodFilters != null && MethodFilter.matchesClassName(excludeMethodFilters, className)) {
 468             return false;
 469         }
 470         return true;
 471     }
 472 
 473     /**
 474      * Compiles all methods in all classes in a given class path.
 475      *
 476      * @param classPath class path denoting classes to compile
 477      * @throws IOException
 478      */
 479     @SuppressWarnings("try")
 480     private void compile(String classPath) throws IOException {
 481         final String[] entries = classPath.split(File.pathSeparator);
 482         long start = System.currentTimeMillis();
 483 
 484         try {
 485             // compile dummy method to get compiler initialized outside of the
 486             // config debug override.
 487             HotSpotResolvedJavaMethod dummyMethod = (HotSpotResolvedJavaMethod) JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaMethod(
 488                             CompileTheWorld.class.getDeclaredMethod("dummy"));
 489             int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI;
 490             boolean useProfilingInfo = false;
 491             boolean installAsDefault = false;
 492             CompilationTask task = new CompilationTask(jvmciRuntime, compiler, new HotSpotCompilationRequest(dummyMethod, entryBCI, 0L), useProfilingInfo, installAsDefault, currentOptions);
 493             task.runCompilation();
 494         } catch (NoSuchMethodException | SecurityException e1) {
 495             printStackTrace(e1);
 496         }
 497 
 498         /*
 499          * Always use a thread pool, even for single threaded mode since it simplifies the use of
 500          * DebugValueThreadFilter to filter on the thread names.
 501          */
 502         int threadCount = 1;
 503         if (Options.MultiThreaded.getValue(currentOptions)) {
 504             threadCount = Options.Threads.getValue(currentOptions);
 505             if (threadCount == 0) {
 506                 threadCount = Runtime.getRuntime().availableProcessors();
 507             }
 508         } else {
 509             running = true;
 510         }
 511 
 512         OptionValues savedOptions = currentOptions;
 513         currentOptions = new OptionValues(compilationOptions);
 514         threadPool = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(),
 515                         new CompilerThreadFactory("CompileTheWorld", new DebugConfigAccess() {
 516                             @Override
 517                             public GraalDebugConfig getDebugConfig() {
 518                                 return DebugEnvironment.ensureInitialized(currentOptions, compiler.getGraalRuntime().getHostProviders().getSnippetReflection());
 519                             }
 520                         }));
 521 
 522         try {
 523             for (int i = 0; i < entries.length; i++) {
 524                 final String entry = entries[i];
 525 
 526                 ClassPathEntry cpe;
 527                 if (entry.endsWith(".zip") || entry.endsWith(".jar")) {
 528                     cpe = new JarClassPathEntry(entry);
 529                 } else if (entry.equals(JRT_CLASS_PATH_ENTRY)) {
 530                     cpe = new JRTClassPathEntry(entry, Options.LimitModules.getValue(currentOptions));
 531                 } else {
 532                     if (!new File(entry).isDirectory()) {
 533                         println("CompileTheWorld : Skipped classes in " + entry);
 534                         println();
 535                         continue;
 536                     }
 537                     cpe = new DirClassPathEntry(entry);
 538                 }
 539 
 540                 if (methodFilters == null || methodFilters.length == 0) {
 541                     println("CompileTheWorld : Compiling all classes in " + entry);
 542                 } else {
 543                     String include = Arrays.asList(methodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", "));
 544                     println("CompileTheWorld : Compiling all methods in " + entry + " matching one of the following filters: " + include);
 545                 }
 546                 if (excludeMethodFilters != null && excludeMethodFilters.length > 0) {
 547                     String exclude = Arrays.asList(excludeMethodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", "));
 548                     println("CompileTheWorld : Excluding all methods matching one of the following filters: " + exclude);
 549                 }
 550                 println();
 551 
 552                 ClassLoader loader = cpe.createClassLoader();
 553 
 554                 for (String className : cpe.getClassNames()) {
 555 
 556                     // Are we done?
 557                     if (classFileCounter >= stopAt) {
 558                         break;
 559                     }
 560 
 561                     classFileCounter++;
 562 
 563                     if (className.startsWith("jdk.management.") || className.startsWith("jdk.internal.cmm.*")) {
 564                         continue;
 565                     }
 566 
 567                     try {
 568                         // Load and initialize class
 569                         Class<?> javaClass = Class.forName(className, true, loader);
 570 
 571                         // Pre-load all classes in the constant pool.
 572                         try {
 573                             HotSpotResolvedObjectType objectType = HotSpotResolvedObjectType.fromObjectClass(javaClass);
 574                             ConstantPool constantPool = objectType.getConstantPool();
 575                             for (int cpi = 1; cpi < constantPool.length(); cpi++) {
 576                                 constantPool.loadReferencedType(cpi, Bytecodes.LDC);
 577                             }
 578                         } catch (Throwable t) {
 579                             // If something went wrong during pre-loading we just ignore it.
 580                             if (isClassIncluded(className)) {
 581                                 println("Preloading failed for (%d) %s: %s", classFileCounter, className, t);
 582                             }
 583                             continue;
 584                         }
 585 
 586                         /*
 587                          * Only check filters after class loading and resolution to mitigate impact
 588                          * on reproducibility.
 589                          */
 590                         if (!isClassIncluded(className)) {
 591                             continue;
 592                         }
 593 
 594                         // Are we compiling this class?
 595                         MetaAccessProvider metaAccess = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess();
 596                         if (classFileCounter >= startAt) {
 597                             println("CompileTheWorld (%d) : %s", classFileCounter, className);
 598 
 599                             // Compile each constructor/method in the class.
 600                             for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
 601                                 HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(constructor);
 602                                 if (canBeCompiled(javaMethod, constructor.getModifiers())) {
 603                                     compileMethod(javaMethod);
 604                                 }
 605                             }
 606                             for (Method method : javaClass.getDeclaredMethods()) {
 607                                 HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(method);
 608                                 if (canBeCompiled(javaMethod, method.getModifiers())) {
 609                                     compileMethod(javaMethod);
 610                                 }
 611                             }
 612 
 613                             // Also compile the class initializer if it exists
 614                             HotSpotResolvedJavaMethod clinit = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaType(javaClass).getClassInitializer();
 615                             if (clinit != null && canBeCompiled(clinit, clinit.getModifiers())) {
 616                                 compileMethod(clinit);
 617                             }
 618                         }
 619                     } catch (Throwable t) {
 620                         if (isClassIncluded(className)) {
 621                             println("CompileTheWorld (%d) : Skipping %s %s", classFileCounter, className, t.toString());
 622                             printStackTrace(t);
 623                         }
 624                     }
 625                 }
 626                 cpe.close();
 627             }
 628         } finally {
 629             currentOptions = savedOptions;
 630         }
 631 
 632         if (!running) {
 633             startThreads();
 634         }
 635         int wakeups = 0;
 636         while (threadPool.getCompletedTaskCount() != threadPool.getTaskCount()) {
 637             if (wakeups % 15 == 0) {
 638                 TTY.println("CompileTheWorld : Waiting for " + (threadPool.getTaskCount() - threadPool.getCompletedTaskCount()) + " compiles");
 639             }
 640             try {
 641                 threadPool.awaitTermination(1, TimeUnit.SECONDS);
 642                 wakeups++;
 643             } catch (InterruptedException e) {
 644             }
 645         }
 646         threadPool = null;
 647 
 648         long elapsedTime = System.currentTimeMillis() - start;
 649 
 650         println();
 651         if (Options.MultiThreaded.getValue(currentOptions)) {
 652             TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms elapsed, %d ms compile time, %d bytes of memory used)", classFileCounter, compiledMethodsCounter.get(), elapsedTime,
 653                             compileTime.get(), memoryUsed.get());
 654         } else {
 655             TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms, %d bytes of memory used)", classFileCounter, compiledMethodsCounter.get(), compileTime.get(), memoryUsed.get());
 656         }
 657     }
 658 
 659     private synchronized void startThreads() {
 660         running = true;
 661         // Wake up any waiting threads
 662         notifyAll();
 663     }
 664 
 665     private synchronized void waitToRun() {
 666         while (!running) {
 667             try {
 668                 wait();
 669             } catch (InterruptedException e) {
 670             }
 671         }
 672     }
 673 
 674     @SuppressWarnings("try")
 675     private void compileMethod(HotSpotResolvedJavaMethod method) throws InterruptedException, ExecutionException {
 676         if (methodFilters != null && !MethodFilter.matches(methodFilters, method)) {
 677             return;
 678         }
 679         if (excludeMethodFilters != null && MethodFilter.matches(excludeMethodFilters, method)) {
 680             return;
 681         }
 682         Future<?> task = threadPool.submit(new Runnable() {
 683             @Override
 684             public void run() {
 685                 waitToRun();
 686                 OptionValues savedOptions = currentOptions;
 687                 currentOptions = new OptionValues(compilationOptions);
 688                 try {
 689                     compileMethod(method, classFileCounter);
 690                 } finally {
 691                     currentOptions = savedOptions;
 692                 }
 693             }
 694         });
 695         if (threadPool.getCorePoolSize() == 1) {
 696             task.get();
 697         }
 698     }
 699 
 700     /**
 701      * Compiles a method and gathers some statistics.
 702      */
 703     private void compileMethod(HotSpotResolvedJavaMethod method, int counter) {
 704         try {
 705             long start = System.currentTimeMillis();
 706             long allocatedAtStart = MemUseTrackerImpl.getCurrentThreadAllocatedBytes();
 707             int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI;
 708             HotSpotCompilationRequest request = new HotSpotCompilationRequest(method, entryBCI, 0L);
 709             // For more stable CTW execution, disable use of profiling information
 710             boolean useProfilingInfo = false;
 711             boolean installAsDefault = false;
 712             CompilationTask task = new CompilationTask(jvmciRuntime, compiler, request, useProfilingInfo, installAsDefault, currentOptions);
 713             task.runCompilation();
 714 
 715             // Invalidate the generated code so the code cache doesn't fill up
 716             HotSpotInstalledCode installedCode = task.getInstalledCode();
 717             if (installedCode != null) {
 718                 installedCode.invalidate();
 719             }
 720 
 721             memoryUsed.getAndAdd(MemUseTrackerImpl.getCurrentThreadAllocatedBytes() - allocatedAtStart);
 722             compileTime.getAndAdd(System.currentTimeMillis() - start);
 723             compiledMethodsCounter.incrementAndGet();
 724         } catch (Throwable t) {
 725             // Catch everything and print a message
 726             println("CompileTheWorld (%d) : Error compiling method: %s", counter, method.format("%H.%n(%p):%r"));
 727             printStackTrace(t);
 728         }
 729     }
 730 
 731     /**
 732      * Determines if a method should be compiled (Cf. CompilationPolicy::can_be_compiled).
 733      *
 734      * @return true if it can be compiled, false otherwise
 735      */
 736     private boolean canBeCompiled(HotSpotResolvedJavaMethod javaMethod, int modifiers) {
 737         if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) {
 738             return false;
 739         }
 740         GraalHotSpotVMConfig c = compiler.getGraalRuntime().getVMConfig();
 741         if (c.dontCompileHugeMethods && javaMethod.getCodeSize() > c.hugeMethodLimit) {
 742             println(verbose || methodFilters != null,
 743                             String.format("CompileTheWorld (%d) : Skipping huge method %s (use -XX:-DontCompileHugeMethods or -XX:HugeMethodLimit=%d to include it)", classFileCounter,
 744                                             javaMethod.format("%H.%n(%p):%r"),
 745                                             javaMethod.getCodeSize()));
 746             return false;
 747         }
 748         // Allow use of -XX:CompileCommand=dontinline to exclude problematic methods
 749         if (!javaMethod.canBeInlined()) {
 750             return false;
 751         }
 752         // Skip @Snippets for now
 753         for (Annotation annotation : javaMethod.getAnnotations()) {
 754             if (annotation.annotationType().equals(Snippet.class)) {
 755                 return false;
 756             }
 757         }
 758         return true;
 759     }
 760 
 761     static class Options {
 762         // @formatter:off
 763         public static final OptionKey<Boolean> Help = new OptionKey<>(false);
 764         public static final OptionKey<String> Classpath = new OptionKey<>(CompileTheWorld.SUN_BOOT_CLASS_PATH);
 765         public static final OptionKey<Boolean> Verbose = new OptionKey<>(true);
 766         /**
 767          * Ignore Graal classes by default to avoid problems associated with compiling
 768          * snippets and method substitutions.
 769          */
 770         public static final OptionKey<String> LimitModules = new OptionKey<>("~jdk.internal.vm.compiler");
 771         public static final OptionKey<Integer> Iterations = new OptionKey<>(1);
 772         public static final OptionKey<String> MethodFilter = new OptionKey<>(null);
 773         public static final OptionKey<String> ExcludeMethodFilter = new OptionKey<>(null);
 774         public static final OptionKey<Integer> StartAt = new OptionKey<>(1);
 775         public static final OptionKey<Integer> StopAt = new OptionKey<>(Integer.MAX_VALUE);
 776         public static final OptionKey<String> Config = new OptionKey<>(null);
 777         public static final OptionKey<Boolean> MultiThreaded = new OptionKey<>(false);
 778         public static final OptionKey<Integer> Threads = new OptionKey<>(0);
 779 
 780         static final ReflectionOptionDescriptors DESCRIPTORS = new ReflectionOptionDescriptors(Options.class,
 781                            "Help", "List options and their help messages and then exit.",
 782                       "Classpath", "Class path denoting methods to compile. Default is to compile boot classes.",
 783                         "Verbose", "Verbose operation.",
 784                    "LimitModules", "Comma separated list of module names to which compilation should be limited. " +
 785                                    "Module names can be prefixed with \"~\" to exclude the named module.",
 786                      "Iterations", "The number of iterations to perform.",
 787                    "MethodFilter", "Only compile methods matching this filter.",
 788             "ExcludeMethodFilter", "Exclude methods matching this filter from compilation.",
 789                         "StartAt", "First class to consider for compilation.",
 790                          "StopAt", "Last class to consider for compilation.",
 791                          "Config", "Option value overrides to use during compile the world. For example, " +
 792                                    "to disable inlining and partial escape analysis specify 'PartialEscapeAnalysis=false Inline=false'. " +
 793                                    "The format for each option is the same as on the command line just without the '-Dgraal.' prefix.",
 794                   "MultiThreaded", "Run using multiple threads for compilation.",
 795                         "Threads", "Number of threads to use for multithreaded execution. Defaults to Runtime.getRuntime().availableProcessors().");
 796         // @formatter:on
 797     }
 798 
 799     public static OptionValues loadOptions(OptionValues initialValues) {
 800         EconomicMap<OptionKey<?>, Object> values = OptionValues.newOptionMap();
 801         List<OptionDescriptors> loader = singletonList(DESCRIPTORS);
 802         OptionsParser.parseOptions(extractEntries(System.getProperties(), "CompileTheWorld.", true), values, loader);
 803         OptionValues options = new OptionValues(initialValues, values);
 804         if (Options.Help.getValue(options)) {
 805             options.printHelp(loader, System.out, "CompileTheWorld.");
 806             System.exit(0);
 807         }
 808         return options;
 809     }
 810 
 811     public static void main(String[] args) throws Throwable {
 812         HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime();
 813         HotSpotGraalCompiler compiler = (HotSpotGraalCompiler) jvmciRuntime.getCompiler();
 814         HotSpotGraalRuntimeProvider graalRuntime = compiler.getGraalRuntime();
 815         HotSpotCodeCacheProvider codeCache = graalRuntime.getHostProviders().getCodeCache();
 816         OptionValues options = loadOptions(graalRuntime.getOptions());
 817 
 818         int iterations = Options.Iterations.getValue(options);
 819         for (int i = 0; i < iterations; i++) {
 820             codeCache.resetCompilationStatistics();
 821             TTY.println("CompileTheWorld : iteration " + i);
 822 
 823             CompileTheWorld ctw = new CompileTheWorld(jvmciRuntime, compiler, options);
 824             ctw.compile();
 825         }
 826         // This is required as non-daemon threads can be started by class initializers
 827         System.exit(0);
 828     }
 829 }