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