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 }