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 }