1 /* 2 * Copyright (c) 2013, 2019, 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.CompilationTask.CompilationTime; 34 import static org.graalvm.compiler.hotspot.CompilationTask.CompiledAndInstalledBytecodes; 35 import static org.graalvm.compiler.hotspot.test.CompileTheWorld.Options.DESCRIPTORS; 36 import static org.graalvm.compiler.hotspot.test.CompileTheWorld.Options.InvalidateInstalledCode; 37 import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET; 38 39 import java.io.ByteArrayOutputStream; 40 import java.io.Closeable; 41 import java.io.File; 42 import java.io.IOException; 43 import java.lang.annotation.Annotation; 44 import java.lang.reflect.Constructor; 45 import java.lang.reflect.Method; 46 import java.lang.reflect.Modifier; 47 import java.net.URI; 48 import java.net.URL; 49 import java.net.URLClassLoader; 50 import java.nio.file.FileSystem; 51 import java.nio.file.FileSystems; 52 import java.nio.file.FileVisitResult; 53 import java.nio.file.Files; 54 import java.nio.file.Path; 55 import java.nio.file.SimpleFileVisitor; 56 import java.nio.file.attribute.BasicFileAttributes; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collections; 60 import java.util.Enumeration; 61 import java.util.HashMap; 62 import java.util.HashSet; 63 import java.util.List; 64 import java.util.Map; 65 import java.util.ServiceLoader; 66 import java.util.Set; 67 import java.util.concurrent.ExecutionException; 68 import java.util.concurrent.Future; 69 import java.util.concurrent.LinkedBlockingQueue; 70 import java.util.concurrent.ThreadPoolExecutor; 71 import java.util.concurrent.TimeUnit; 72 import java.util.concurrent.atomic.AtomicLong; 73 import java.util.jar.JarEntry; 74 import java.util.jar.JarFile; 75 import java.util.regex.Matcher; 76 import java.util.regex.Pattern; 77 import java.util.stream.Collectors; 78 79 import jdk.internal.vm.compiler.collections.EconomicMap; 80 import jdk.internal.vm.compiler.collections.UnmodifiableMapCursor; 81 import org.graalvm.compiler.api.replacements.Snippet; 82 import org.graalvm.compiler.bytecode.Bytecodes; 83 import org.graalvm.compiler.core.CompilerThreadFactory; 84 import org.graalvm.compiler.core.phases.HighTier; 85 import org.graalvm.compiler.core.test.ReflectionOptionDescriptors; 86 import org.graalvm.compiler.debug.DebugOptions; 87 import org.graalvm.compiler.debug.GlobalMetrics; 88 import org.graalvm.compiler.debug.GraalError; 89 import org.graalvm.compiler.debug.MethodFilter; 90 import org.graalvm.compiler.debug.MetricKey; 91 import org.graalvm.compiler.debug.TTY; 92 import org.graalvm.compiler.hotspot.CompilationTask; 93 import org.graalvm.compiler.hotspot.GraalHotSpotVMConfig; 94 import org.graalvm.compiler.hotspot.HotSpotGraalCompiler; 95 import org.graalvm.compiler.hotspot.HotSpotGraalRuntime; 96 import org.graalvm.compiler.hotspot.HotSpotGraalRuntimeProvider; 97 import org.graalvm.compiler.hotspot.test.CompileTheWorld.LibGraalParams.StackTraceBuffer; 98 import org.graalvm.compiler.options.OptionDescriptors; 99 import org.graalvm.compiler.options.OptionKey; 100 import org.graalvm.compiler.options.OptionValues; 101 import org.graalvm.compiler.options.OptionsParser; 102 import org.graalvm.compiler.serviceprovider.GraalUnsafeAccess; 103 import org.graalvm.compiler.serviceprovider.JavaVersionUtil; 104 import org.graalvm.compiler.test.ModuleSupport; 105 import jdk.internal.vm.compiler.libgraal.LibGraal; 106 import jdk.internal.vm.compiler.libgraal.LibGraalScope; 107 import jdk.internal.vm.compiler.libgraal.OptionsEncoder; 108 109 import jdk.vm.ci.hotspot.HotSpotCodeCacheProvider; 110 import jdk.vm.ci.hotspot.HotSpotCompilationRequest; 111 import jdk.vm.ci.hotspot.HotSpotInstalledCode; 112 import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; 113 import jdk.vm.ci.hotspot.HotSpotResolvedJavaMethod; 114 import jdk.vm.ci.hotspot.HotSpotResolvedObjectType; 115 import jdk.vm.ci.meta.ConstantPool; 116 import jdk.vm.ci.meta.MetaAccessProvider; 117 import jdk.vm.ci.runtime.JVMCI; 118 import jdk.vm.ci.runtime.JVMCICompiler; 119 import sun.misc.Unsafe; 120 121 /** 122 * This class implements compile-the-world functionality with JVMCI. 123 */ 124 public final class CompileTheWorld { 125 126 static { 127 ModuleSupport.exportAndOpenAllPackagesToUnnamed("jdk.internal.vm.compiler"); 128 } 129 130 /** 131 * Magic token to denote that JDK classes are to be compiled. For JDK 8, the classes in 132 * {@code rt.jar} are compiled. Otherwise the classes in the Java runtime image are compiled. 133 */ 134 public static final String SUN_BOOT_CLASS_PATH = "sun.boot.class.path"; 135 136 /** 137 * Magic token to denote the classes in the Java runtime image (i.e. in the {@code jrt:/} file 138 * system). 139 */ 140 public static final String JRT_CLASS_PATH_ENTRY = "<jrt>"; 141 142 /** 143 * @param options a space separated set of option value settings with each option setting in a 144 * {@code -Dgraal.<name>=<value>} format but without the leading {@code -Dgraal.}. 145 * Ignored if null. 146 */ 147 public static EconomicMap<OptionKey<?>, Object> parseOptions(String options) { 148 EconomicMap<OptionKey<?>, Object> values = OptionValues.newOptionMap(); 149 if (options != null) { 150 EconomicMap<String, String> optionSettings = EconomicMap.create(); 151 for (String optionSetting : options.split("\\s+|#")) { 152 OptionsParser.parseOptionSettingTo(optionSetting, optionSettings); 153 } 154 ServiceLoader<OptionDescriptors> loader = ServiceLoader.load(OptionDescriptors.class, OptionDescriptors.class.getClassLoader()); 155 OptionsParser.parseOptions(optionSettings, values, loader); 156 } 157 if (!values.containsKey(HighTier.Options.Inline)) { 158 values.put(HighTier.Options.Inline, false); 159 } 160 return values; 161 } 162 163 private final HotSpotJVMCIRuntime jvmciRuntime; 164 165 private final HotSpotGraalCompiler compiler; 166 167 /** 168 * Class path denoting classes to compile. 169 * 170 * @see Options#Classpath 171 */ 172 private final String inputClassPath; 173 174 /** 175 * Class index to start compilation at. 176 * 177 * @see Options#StartAt 178 */ 179 private final int startAt; 180 181 /** 182 * Class index to stop compilation at. 183 * 184 * @see Options#StopAt 185 */ 186 private final int stopAt; 187 188 /** 189 * Max classes to compile. 190 * 191 * @see Options#MaxClasses 192 */ 193 private final int maxClasses; 194 195 /** Only compile methods matching one of the filters in this array if the array is non-null. */ 196 private final MethodFilter[] methodFilters; 197 198 /** Exclude methods matching one of the filters in this array if the array is non-null. */ 199 private final MethodFilter[] excludeMethodFilters; 200 201 // Counters 202 private int classFileCounter = 0; 203 private AtomicLong compiledMethodsCounter = new AtomicLong(); 204 private AtomicLong compileTime = new AtomicLong(); 205 private AtomicLong memoryUsed = new AtomicLong(); 206 207 private boolean verbose; 208 209 /** 210 * Signal that the threads should start compiling in multithreaded mode. 211 */ 212 private boolean running; 213 214 private ThreadPoolExecutor threadPool; 215 216 /** 217 * Values for {@link CompileTheWorld.Options}. 218 */ 219 private final OptionValues harnessOptions; 220 221 /** 222 * Option values used during compilation. 223 */ 224 private final OptionValues compilerOptions; 225 226 /** 227 * Manages native memory buffers for passing arguments into libgraal and receiving return 228 * values. The native memory buffers are freed when this object is {@linkplain #close() closed}. 229 */ 230 static class LibGraalParams implements AutoCloseable { 231 232 static { 233 LibGraal.registerNativeMethods(HotSpotJVMCIRuntime.runtime(), CompileTheWorld.class); 234 } 235 236 /** 237 * Native memory containing {@linkplain OptionsEncoder encoded} {@link OptionValues}. 238 */ 239 static class OptionsBuffer { 240 private long address; 241 final int size; 242 final int hash; 243 244 OptionsBuffer(OptionValues options) { 245 Map<String, Object> map = new HashMap<>(); 246 UnmodifiableMapCursor<OptionKey<?>, Object> cursor = options.getMap().getEntries(); 247 while (cursor.advance()) { 248 final OptionKey<?> key = cursor.getKey(); 249 Object value = cursor.getValue(); 250 map.put(key.getName(), value); 251 } 252 253 byte[] encoded = OptionsEncoder.encode(map); 254 size = encoded.length; 255 hash = Arrays.hashCode(encoded); 256 address = UNSAFE.allocateMemory(encoded.length); 257 UNSAFE.copyMemory(encoded, ARRAY_BYTE_BASE_OFFSET, null, address, size); 258 } 259 260 long getAddress() { 261 if (address == 0) { 262 throw new IllegalStateException(); 263 } 264 return address; 265 } 266 267 void free() { 268 if (address != 0) { 269 UNSAFE.freeMemory(address); 270 address = 0; 271 } 272 } 273 } 274 275 /** 276 * Manages native memory for receiving a {@linkplain Throwable#printStackTrace() stack 277 * trace} from libgraal serialized via {@link ByteArrayOutputStream} to a byte array. 278 */ 279 static class StackTraceBuffer { 280 final int size; 281 private long address; 282 283 StackTraceBuffer(int size) { 284 this.size = size; 285 address = UNSAFE.allocateMemory(size); 286 } 287 288 void free() { 289 if (address != 0L) { 290 UNSAFE.freeMemory(address); 291 address = 0L; 292 } 293 } 294 295 long getAddress() { 296 if (address == 0) { 297 throw new IllegalStateException(); 298 } 299 return address; 300 } 301 } 302 303 final OptionsBuffer options; 304 305 private final List<StackTraceBuffer> stackTraceBuffers = new ArrayList<>(); 306 307 /** 308 * Gets a stack trace buffer for the current thread. 309 */ 310 StackTraceBuffer getStackTraceBuffer() { 311 return stackTraceBuffer.get(); 312 } 313 314 private final ThreadLocal<StackTraceBuffer> stackTraceBuffer = new ThreadLocal<StackTraceBuffer>() { 315 @Override 316 protected StackTraceBuffer initialValue() { 317 StackTraceBuffer buffer = new StackTraceBuffer(10_000); 318 synchronized (stackTraceBuffers) { 319 stackTraceBuffers.add(buffer); 320 } 321 return buffer; 322 } 323 }; 324 325 LibGraalParams(OptionValues options) { 326 this.options = new OptionsBuffer(options); 327 } 328 329 @Override 330 public void close() { 331 options.free(); 332 synchronized (stackTraceBuffers) { 333 for (StackTraceBuffer buffer : stackTraceBuffers) { 334 buffer.free(); 335 } 336 stackTraceBuffers.clear(); 337 } 338 } 339 } 340 341 /** 342 * Creates a compile-the-world instance. 343 * 344 * @param files {@link File#pathSeparator} separated list of Zip/Jar files to compile 345 * @param startAt index of the class file to start compilation at 346 * @param stopAt index of the class file to stop compilation at 347 * @param maxClasses maximum number of classes to process 348 * @param methodFilters 349 * @param excludeMethodFilters 350 * @param harnessOptions values for {@link CompileTheWorld.Options} 351 * @param compilerOptions option values used by the compiler 352 */ 353 public CompileTheWorld(HotSpotJVMCIRuntime jvmciRuntime, 354 HotSpotGraalCompiler compiler, 355 String files, 356 int startAt, 357 int stopAt, 358 int maxClasses, 359 String methodFilters, 360 String excludeMethodFilters, 361 boolean verbose, 362 OptionValues harnessOptions, 363 OptionValues compilerOptions) { 364 this.jvmciRuntime = jvmciRuntime; 365 this.compiler = compiler; 366 this.inputClassPath = files; 367 this.startAt = Math.max(startAt, 1); 368 this.stopAt = Math.max(stopAt, 1); 369 this.maxClasses = Math.max(maxClasses, 1); 370 this.methodFilters = methodFilters == null || methodFilters.isEmpty() ? null : MethodFilter.parse(methodFilters); 371 this.excludeMethodFilters = excludeMethodFilters == null || excludeMethodFilters.isEmpty() ? null : MethodFilter.parse(excludeMethodFilters); 372 this.verbose = verbose; 373 this.harnessOptions = harnessOptions; 374 375 // Copy the initial options and add in any extra options 376 EconomicMap<OptionKey<?>, Object> compilerOptionsMap = EconomicMap.create(compilerOptions.getMap()); 377 378 // We want to see stack traces when a method fails to compile 379 CompilationBailoutAsFailure.putIfAbsent(compilerOptionsMap, true); 380 CompilationFailureAction.putIfAbsent(compilerOptionsMap, Print); 381 382 // By default only report statistics for the CTW threads themselves 383 DebugOptions.MetricsThreadFilter.putIfAbsent(compilerOptionsMap, "^CompileTheWorld"); 384 this.compilerOptions = new OptionValues(compilerOptionsMap); 385 } 386 387 public CompileTheWorld(HotSpotJVMCIRuntime jvmciRuntime, 388 HotSpotGraalCompiler compiler, 389 OptionValues harnessOptions, 390 OptionValues compilerOptions) { 391 this(jvmciRuntime, compiler, Options.Classpath.getValue(harnessOptions), 392 Options.StartAt.getValue(harnessOptions), 393 Options.StopAt.getValue(harnessOptions), 394 Options.MaxClasses.getValue(harnessOptions), 395 Options.MethodFilter.getValue(harnessOptions), 396 Options.ExcludeMethodFilter.getValue(harnessOptions), 397 Options.Verbose.hasBeenSet(harnessOptions) ? Options.Verbose.getValue(harnessOptions) : !Options.MultiThreaded.getValue(harnessOptions), 398 harnessOptions, 399 new OptionValues(compilerOptions, parseOptions(Options.Config.getValue(harnessOptions)))); 400 } 401 402 /** 403 * Compiles all methods in all classes in {@link #inputClassPath}. If {@link #inputClassPath} 404 * equals {@link #SUN_BOOT_CLASS_PATH} the boot classes are used. 405 */ 406 @SuppressWarnings("try") 407 public void compile() throws Throwable { 408 try (LibGraalParams libgraal = LibGraal.isAvailable() ? new LibGraalParams(compilerOptions) : null) { 409 if (SUN_BOOT_CLASS_PATH.equals(inputClassPath)) { 410 String bcpEntry = null; 411 if (JavaVersionUtil.JAVA_SPEC <= 8) { 412 final String[] entries = System.getProperty(SUN_BOOT_CLASS_PATH).split(File.pathSeparator); 413 for (int i = 0; i < entries.length && bcpEntry == null; i++) { 414 String entry = entries[i]; 415 File entryFile = new File(entry); 416 if (entryFile.getName().endsWith("rt.jar") && entryFile.isFile()) { 417 bcpEntry = entry; 418 } 419 } 420 if (bcpEntry == null) { 421 throw new GraalError("Could not find rt.jar on boot class path %s", System.getProperty(SUN_BOOT_CLASS_PATH)); 422 } 423 } else { 424 bcpEntry = JRT_CLASS_PATH_ENTRY; 425 } 426 compile(bcpEntry, libgraal); 427 } else { 428 compile(inputClassPath, libgraal); 429 } 430 } 431 } 432 433 public void println() { 434 println(""); 435 } 436 437 public void println(String format, Object... args) { 438 println(String.format(format, args)); 439 } 440 441 public void println(String s) { 442 println(verbose, s); 443 } 444 445 public static void println(boolean cond, String s) { 446 if (cond) { 447 TTY.println(s); 448 } 449 } 450 451 public void printStackTrace(Throwable t) { 452 if (verbose) { 453 t.printStackTrace(TTY.out); 454 } 455 } 456 457 @SuppressWarnings("unused") 458 private static void dummy() { 459 } 460 461 /** 462 * Abstraction over different types of class path entries. 463 */ 464 abstract static class ClassPathEntry implements Closeable { 465 final String name; 466 467 ClassPathEntry(String name) { 468 this.name = name; 469 } 470 471 /** 472 * Creates a {@link ClassLoader} for loading classes from this entry. 473 */ 474 public abstract ClassLoader createClassLoader() throws IOException; 475 476 /** 477 * Gets the list of classes available under this entry. 478 */ 479 public abstract List<String> getClassNames() throws IOException; 480 481 @Override 482 public String toString() { 483 return name; 484 } 485 486 @Override 487 public void close() throws IOException { 488 } 489 } 490 491 /** 492 * A class path entry that is a normal file system directory. 493 */ 494 static class DirClassPathEntry extends ClassPathEntry { 495 496 private final File dir; 497 498 DirClassPathEntry(String name) { 499 super(name); 500 dir = new File(name); 501 assert dir.isDirectory(); 502 } 503 504 @Override 505 public ClassLoader createClassLoader() throws IOException { 506 URL url = dir.toURI().toURL(); 507 return new URLClassLoader(new URL[]{url}); 508 } 509 510 @Override 511 public List<String> getClassNames() throws IOException { 512 List<String> classNames = new ArrayList<>(); 513 String root = dir.getPath(); 514 SimpleFileVisitor<Path> visitor = new SimpleFileVisitor<Path>() { 515 @Override 516 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 517 if (attrs.isRegularFile()) { 518 File path = file.toFile(); 519 if (path.getName().endsWith(".class")) { 520 String pathString = path.getPath(); 521 assert pathString.startsWith(root); 522 String classFile = pathString.substring(root.length() + 1); 523 String className = classFile.replace(File.separatorChar, '.'); 524 classNames.add(className.replace('/', '.').substring(0, className.length() - ".class".length())); 525 } 526 } 527 return super.visitFile(file, attrs); 528 } 529 }; 530 Files.walkFileTree(dir.toPath(), visitor); 531 return classNames; 532 } 533 } 534 535 /** 536 * A class path entry that is a jar or zip file. 537 */ 538 static class JarClassPathEntry extends ClassPathEntry { 539 540 private final JarFile jarFile; 541 542 JarClassPathEntry(String name) throws IOException { 543 super(name); 544 jarFile = new JarFile(name); 545 } 546 547 @Override 548 public ClassLoader createClassLoader() throws IOException { 549 URL url = new URL("jar", "", "file:" + name + "!/"); 550 return new URLClassLoader(new URL[]{url}); 551 } 552 553 /** 554 * @see "https://docs.oracle.com/javase/9/docs/specs/jar/jar.html#Multi-release" 555 */ 556 static Pattern MultiReleaseJarVersionedClassRE = Pattern.compile("META-INF/versions/[1-9][0-9]*/(.+)"); 557 558 @Override 559 public List<String> getClassNames() throws IOException { 560 Enumeration<JarEntry> e = jarFile.entries(); 561 List<String> classNames = new ArrayList<>(jarFile.size()); 562 while (e.hasMoreElements()) { 563 JarEntry je = e.nextElement(); 564 if (je.isDirectory() || !je.getName().endsWith(".class")) { 565 continue; 566 } 567 String className = je.getName().substring(0, je.getName().length() - ".class".length()); 568 if (className.equals("module-info")) { 569 continue; 570 } 571 if (className.startsWith("META-INF/versions/")) { 572 Matcher m = MultiReleaseJarVersionedClassRE.matcher(className); 573 if (m.matches()) { 574 className = m.group(1); 575 } else { 576 continue; 577 } 578 } 579 classNames.add(className.replace('/', '.')); 580 } 581 return classNames; 582 } 583 584 @Override 585 public void close() throws IOException { 586 jarFile.close(); 587 } 588 } 589 590 /** 591 * A class path entry representing the {@code jrt:/} file system. 592 */ 593 static class JRTClassPathEntry extends ClassPathEntry { 594 595 private final String limitModules; 596 597 JRTClassPathEntry(String name, String limitModules) { 598 super(name); 599 this.limitModules = limitModules; 600 } 601 602 @Override 603 public ClassLoader createClassLoader() throws IOException { 604 URL url = URI.create("jrt:/").toURL(); 605 return new URLClassLoader(new URL[]{url}); 606 } 607 608 @Override 609 public List<String> getClassNames() throws IOException { 610 Set<String> negative = new HashSet<>(); 611 Set<String> positive = new HashSet<>(); 612 if (limitModules != null && !limitModules.isEmpty()) { 613 for (String s : limitModules.split(",")) { 614 if (s.startsWith("~")) { 615 negative.add(s.substring(1)); 616 } else { 617 positive.add(s); 618 } 619 } 620 } 621 List<String> classNames = new ArrayList<>(); 622 FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"), Collections.emptyMap()); 623 Path top = fs.getPath("/modules/"); 624 Files.find(top, Integer.MAX_VALUE, 625 (path, attrs) -> attrs.isRegularFile()).forEach(p -> { 626 int nameCount = p.getNameCount(); 627 if (nameCount > 2) { 628 String base = p.getName(nameCount - 1).toString(); 629 if (base.endsWith(".class") && !base.equals("module-info.class")) { 630 String module = p.getName(1).toString(); 631 if (positive.isEmpty() || positive.contains(module)) { 632 if (negative.isEmpty() || !negative.contains(module)) { 633 // Strip module prefix and convert to dotted form 634 String className = p.subpath(2, nameCount).toString().replace('/', '.'); 635 // Strip ".class" suffix 636 className = className.replace('/', '.').substring(0, className.length() - ".class".length()); 637 classNames.add(className); 638 } 639 } 640 } 641 } 642 }); 643 return classNames; 644 } 645 } 646 647 private boolean isClassIncluded(String className) { 648 if (methodFilters != null && !MethodFilter.matchesClassName(methodFilters, className)) { 649 return false; 650 } 651 if (excludeMethodFilters != null && MethodFilter.matchesClassName(excludeMethodFilters, className)) { 652 return false; 653 } 654 return true; 655 } 656 657 private ClassPathEntry openClassPathEntry(String entry) throws IOException { 658 if (entry.endsWith(".zip") || entry.endsWith(".jar")) { 659 return new JarClassPathEntry(entry); 660 } else if (entry.equals(JRT_CLASS_PATH_ENTRY)) { 661 return new JRTClassPathEntry(entry, Options.LimitModules.getValue(harnessOptions)); 662 } else { 663 if (!new File(entry).isDirectory()) { 664 return null; 665 } 666 return new DirClassPathEntry(entry); 667 } 668 } 669 670 /** 671 * Compiles all methods in all classes in a given class path. 672 * 673 * @param classPath class path denoting classes to compile 674 * @throws IOException 675 */ 676 @SuppressWarnings("try") 677 private void compile(String classPath, LibGraalParams libgraal) throws IOException { 678 final String[] entries = classPath.split(File.pathSeparator); 679 long start = System.nanoTime(); 680 Map<Thread, StackTraceElement[]> initialThreads = Thread.getAllStackTraces(); 681 682 if (libgraal == null) { 683 try { 684 // compile dummy method to get compiler initialized outside of the 685 // config debug override. 686 HotSpotResolvedJavaMethod dummyMethod = (HotSpotResolvedJavaMethod) JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaMethod( 687 CompileTheWorld.class.getDeclaredMethod("dummy")); 688 int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI; 689 boolean useProfilingInfo = false; 690 boolean installAsDefault = false; 691 CompilationTask task = new CompilationTask(jvmciRuntime, compiler, new HotSpotCompilationRequest(dummyMethod, entryBCI, 0L), useProfilingInfo, installAsDefault); 692 task.runCompilation(compilerOptions); 693 } catch (NoSuchMethodException | SecurityException e1) { 694 printStackTrace(e1); 695 } 696 } 697 698 /* 699 * Always use a thread pool, even for single threaded mode since it simplifies the use of 700 * DebugValueThreadFilter to filter on the thread names. 701 */ 702 int threadCount = 1; 703 if (Options.MultiThreaded.getValue(harnessOptions)) { 704 threadCount = Options.Threads.getValue(harnessOptions); 705 if (threadCount == 0) { 706 threadCount = Runtime.getRuntime().availableProcessors(); 707 } 708 } else { 709 running = true; 710 } 711 712 threadPool = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new CompilerThreadFactory("CompileTheWorld")); 713 714 int compileStartAt = startAt; 715 int compileStopAt = stopAt; 716 int compileStep = 1; 717 if (maxClasses != Integer.MAX_VALUE) { 718 int totalClassFileCount = 0; 719 for (String entry : entries) { 720 try (ClassPathEntry cpe = openClassPathEntry(entry)) { 721 if (cpe != null) { 722 totalClassFileCount += cpe.getClassNames().size(); 723 } 724 } 725 } 726 727 int lastClassFile = totalClassFileCount - 1; 728 compileStartAt = Math.min(startAt, lastClassFile); 729 compileStopAt = Math.min(stopAt, lastClassFile); 730 int range = compileStopAt - compileStartAt + 1; 731 if (maxClasses < range) { 732 compileStep = range / maxClasses; 733 } 734 } 735 736 for (int i = 0; i < entries.length; i++) { 737 final String entry = entries[i]; 738 try (ClassPathEntry cpe = openClassPathEntry(entry)) { 739 if (cpe == null) { 740 println("CompileTheWorld : Skipped classes in " + entry); 741 println(); 742 continue; 743 } 744 745 if (methodFilters == null || methodFilters.length == 0) { 746 println("CompileTheWorld : Compiling all classes in " + entry); 747 } else { 748 String include = Arrays.asList(methodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", ")); 749 println("CompileTheWorld : Compiling all methods in " + entry + " matching one of the following filters: " + include); 750 } 751 if (excludeMethodFilters != null && excludeMethodFilters.length > 0) { 752 String exclude = Arrays.asList(excludeMethodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", ")); 753 println("CompileTheWorld : Excluding all methods matching one of the following filters: " + exclude); 754 } 755 println(); 756 757 ClassLoader loader = cpe.createClassLoader(); 758 759 for (String className : cpe.getClassNames()) { 760 761 // Are we done? 762 if (classFileCounter >= compileStopAt) { 763 break; 764 } 765 766 classFileCounter++; 767 768 if (compileStep > 1 && ((classFileCounter - compileStartAt) % compileStep) != 0) { 769 continue; 770 } 771 772 if (className.startsWith("jdk.management.") || 773 className.startsWith("jdk.internal.cmm.*") || 774 // GR-5881: The class initializer for 775 // sun.tools.jconsole.OutputViewer 776 // spawns non-daemon threads for redirecting sysout and syserr. 777 // These threads tend to cause deadlock at VM exit 778 className.startsWith("sun.tools.jconsole.")) { 779 continue; 780 } 781 782 if (!isClassIncluded(className)) { 783 continue; 784 } 785 786 try { 787 // Load and initialize class 788 Class<?> javaClass = Class.forName(className, true, loader); 789 MetaAccessProvider metaAccess = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess(); 790 791 // Pre-load all classes in the constant pool. 792 try { 793 HotSpotResolvedObjectType objectType = (HotSpotResolvedObjectType) metaAccess.lookupJavaType(javaClass); 794 ConstantPool constantPool = objectType.getConstantPool(); 795 for (int cpi = 1; cpi < constantPool.length(); cpi++) { 796 constantPool.loadReferencedType(cpi, Bytecodes.LDC); 797 } 798 } catch (Throwable t) { 799 // If something went wrong during pre-loading we just ignore it. 800 if (isClassIncluded(className)) { 801 println("Preloading failed for (%d) %s: %s", classFileCounter, className, t); 802 } 803 continue; 804 } 805 806 // Are we compiling this class? 807 if (classFileCounter >= compileStartAt) { 808 809 long start0 = System.nanoTime(); 810 // Compile each constructor/method in the class. 811 for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) { 812 HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(constructor); 813 if (canBeCompiled(javaMethod, constructor.getModifiers())) { 814 compileMethod(javaMethod, libgraal); 815 } 816 } 817 for (Method method : javaClass.getDeclaredMethods()) { 818 HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(method); 819 if (canBeCompiled(javaMethod, method.getModifiers())) { 820 compileMethod(javaMethod, libgraal); 821 } 822 } 823 824 // Also compile the class initializer if it exists 825 HotSpotResolvedJavaMethod clinit = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaType(javaClass).getClassInitializer(); 826 if (clinit != null && canBeCompiled(clinit, clinit.getModifiers())) { 827 compileMethod(clinit, libgraal); 828 } 829 println("CompileTheWorld (%d) : %s (%d us)", classFileCounter, className, (System.nanoTime() - start0) / 1000); 830 } 831 } catch (Throwable t) { 832 if (isClassIncluded(className)) { 833 println("CompileTheWorld (%d) : Skipping %s %s", classFileCounter, className, t.toString()); 834 printStackTrace(t); 835 } 836 } 837 } 838 } 839 } 840 841 if (!running) { 842 startThreads(); 843 } 844 int wakeups = 0; 845 long lastCompletedTaskCount = 0; 846 for (long completedTaskCount = threadPool.getCompletedTaskCount(); completedTaskCount != threadPool.getTaskCount(); completedTaskCount = threadPool.getCompletedTaskCount()) { 847 if (wakeups % 15 == 0) { 848 TTY.printf("CompileTheWorld : Waiting for %d compiles, just completed %d compiles%n", threadPool.getTaskCount() - completedTaskCount, completedTaskCount - lastCompletedTaskCount); 849 lastCompletedTaskCount = completedTaskCount; 850 } 851 try { 852 threadPool.awaitTermination(1, TimeUnit.SECONDS); 853 wakeups++; 854 } catch (InterruptedException e) { 855 } 856 } 857 threadPool.shutdown(); 858 threadPool = null; 859 860 long elapsedTime = System.nanoTime() - start; 861 862 println(); 863 int compiledClasses = classFileCounter > compileStartAt ? classFileCounter - compileStartAt : 0; 864 if (Options.MultiThreaded.getValue(harnessOptions)) { 865 TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms elapsed, %d ms compile time, %d bytes of memory used)", compiledClasses, compiledMethodsCounter.get(), elapsedTime, 866 compileTime.get() / 1000000, memoryUsed.get()); 867 } else { 868 TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms, %d bytes of memory used)", compiledClasses, compiledMethodsCounter.get(), compileTime.get(), memoryUsed.get()); 869 } 870 871 GlobalMetrics metricValues = ((HotSpotGraalRuntime) compiler.getGraalRuntime()).getMetricValues(); 872 EconomicMap<MetricKey, Long> map = metricValues.asKeyValueMap(); 873 Long compiledAndInstalledBytecodes = map.get(CompiledAndInstalledBytecodes); 874 Long compilationTime = map.get(CompilationTime); 875 if (compiledAndInstalledBytecodes != null && compilationTime != null) { 876 TTY.println("CompileTheWorld : Aggregate compile speed %d bytecodes per second (%d / %d)", (int) (compiledAndInstalledBytecodes / (compilationTime / 1000000000.0)), 877 compiledAndInstalledBytecodes, compilationTime); 878 } 879 880 metricValues.print(compilerOptions); 881 metricValues.clear(); 882 883 // Apart from the main thread, there should be only be daemon threads 884 // alive now. If not, then a class initializer has probably started 885 // a thread that could cause a deadlock while trying to exit the VM. 886 // One known example of this is sun.tools.jconsole.OutputViewer which 887 // spawns threads to redirect sysout and syserr. To help debug such 888 // scenarios, the stacks of potentially problematic threads are dumped. 889 Map<Thread, StackTraceElement[]> suspiciousThreads = new HashMap<>(); 890 for (Map.Entry<Thread, StackTraceElement[]> e : Thread.getAllStackTraces().entrySet()) { 891 Thread thread = e.getKey(); 892 if (thread != Thread.currentThread() && !initialThreads.containsKey(thread) && !thread.isDaemon() && thread.isAlive()) { 893 suspiciousThreads.put(thread, e.getValue()); 894 } 895 } 896 if (!suspiciousThreads.isEmpty()) { 897 TTY.println("--- Non-daemon threads started during CTW ---"); 898 for (Map.Entry<Thread, StackTraceElement[]> e : suspiciousThreads.entrySet()) { 899 Thread thread = e.getKey(); 900 if (thread.isAlive()) { 901 TTY.println(thread.toString() + " " + thread.getState()); 902 for (StackTraceElement ste : e.getValue()) { 903 TTY.println("\tat " + ste); 904 } 905 } 906 } 907 TTY.println("---------------------------------------------"); 908 } 909 } 910 911 private synchronized void startThreads() { 912 running = true; 913 // Wake up any waiting threads 914 notifyAll(); 915 } 916 917 private synchronized void waitToRun() { 918 while (!running) { 919 try { 920 wait(); 921 } catch (InterruptedException e) { 922 } 923 } 924 } 925 926 @SuppressWarnings("try") 927 private void compileMethod(HotSpotResolvedJavaMethod method, LibGraalParams libgraal) throws InterruptedException, ExecutionException { 928 if (methodFilters != null && !MethodFilter.matches(methodFilters, method)) { 929 return; 930 } 931 if (excludeMethodFilters != null && MethodFilter.matches(excludeMethodFilters, method)) { 932 return; 933 } 934 Future<?> task = threadPool.submit(new Runnable() { 935 @Override 936 public void run() { 937 waitToRun(); 938 compileMethod(method, classFileCounter, libgraal); 939 } 940 }); 941 if (threadPool.getCorePoolSize() == 1) { 942 task.get(); 943 } 944 } 945 946 private static final Unsafe UNSAFE = GraalUnsafeAccess.getUnsafe(); 947 948 /** 949 * Implemented by 950 * {@code com.oracle.svm.graal.hotspot.libgraal.LibGraalEntryPoints.compileMethod}. 951 */ 952 static native long compileMethodInLibgraal(long isolateThread, 953 long methodHandle, 954 boolean useProfilingInfo, 955 boolean installAsDefault, 956 long optionsAddress, 957 int optionsSize, 958 int optionsHash, 959 long encodedThrowableBufferAddress, 960 int encodedThrowableBufferSize); 961 962 /** 963 * Compiles a method and gathers some statistics. 964 */ 965 @SuppressWarnings("try") 966 private void compileMethod(HotSpotResolvedJavaMethod method, int counter, LibGraalParams libgraal) { 967 try { 968 long start = System.nanoTime(); 969 long allocatedAtStart = getCurrentThreadAllocatedBytes(); 970 // For more stable CTW execution, disable use of profiling information 971 boolean useProfilingInfo = false; 972 boolean installAsDefault = false; 973 HotSpotInstalledCode installedCode; 974 if (libgraal != null) { 975 HotSpotJVMCIRuntime runtime = HotSpotJVMCIRuntime.runtime(); 976 try (LibGraalScope scope = new LibGraalScope(runtime)) { 977 long methodHandle = LibGraal.translate(runtime, method); 978 long isolateThread = LibGraalScope.getIsolateThread(); 979 980 StackTraceBuffer stackTraceBuffer = libgraal.getStackTraceBuffer(); 981 982 long stackTraceBufferAddress = stackTraceBuffer.getAddress(); 983 long installedCodeHandle = compileMethodInLibgraal(isolateThread, 984 methodHandle, 985 useProfilingInfo, 986 installAsDefault, 987 libgraal.options.getAddress(), 988 libgraal.options.size, 989 libgraal.options.hash, 990 stackTraceBufferAddress, 991 stackTraceBuffer.size); 992 993 installedCode = LibGraal.unhand(runtime, HotSpotInstalledCode.class, installedCodeHandle); 994 if (installedCode == null) { 995 int length = UNSAFE.getInt(stackTraceBufferAddress); 996 byte[] data = new byte[length]; 997 UNSAFE.copyMemory(null, stackTraceBufferAddress + Integer.BYTES, data, ARRAY_BYTE_BASE_OFFSET, length); 998 String stackTrace = new String(data).trim(); 999 println(true, String.format("CompileTheWorld (%d) : Error compiling method: %s", counter, method.format("%H.%n(%p):%r"))); 1000 println(true, stackTrace); 1001 } 1002 } 1003 } else { 1004 int entryBCI = JVMCICompiler.INVOCATION_ENTRY_BCI; 1005 HotSpotCompilationRequest request = new HotSpotCompilationRequest(method, entryBCI, 0L); 1006 CompilationTask task = new CompilationTask(jvmciRuntime, compiler, request, useProfilingInfo, installAsDefault); 1007 task.runCompilation(compilerOptions); 1008 installedCode = task.getInstalledCode(); 1009 } 1010 1011 // Invalidate the generated code so the code cache doesn't fill up 1012 if (installedCode != null && InvalidateInstalledCode.getValue(compilerOptions)) { 1013 installedCode.invalidate(); 1014 } 1015 1016 memoryUsed.getAndAdd(getCurrentThreadAllocatedBytes() - allocatedAtStart); 1017 compileTime.getAndAdd(System.nanoTime() - start); 1018 compiledMethodsCounter.incrementAndGet(); 1019 } catch (Throwable t) { 1020 // Catch everything and print a message 1021 println("CompileTheWorld (%d) : Error compiling method: %s", counter, method.format("%H.%n(%p):%r")); 1022 printStackTrace(t); 1023 } 1024 } 1025 1026 /** 1027 * Determines if a method should be compiled (Cf. CompilationPolicy::can_be_compiled). 1028 * 1029 * @return true if it can be compiled, false otherwise 1030 */ 1031 private boolean canBeCompiled(HotSpotResolvedJavaMethod javaMethod, int modifiers) { 1032 if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) { 1033 return false; 1034 } 1035 GraalHotSpotVMConfig c = compiler.getGraalRuntime().getVMConfig(); 1036 if (c.dontCompileHugeMethods && javaMethod.getCodeSize() > c.hugeMethodLimit) { 1037 println(verbose || methodFilters != null, 1038 String.format("CompileTheWorld (%d) : Skipping huge method %s (use -XX:-DontCompileHugeMethods or -XX:HugeMethodLimit=%d to include it)", classFileCounter, 1039 javaMethod.format("%H.%n(%p):%r"), 1040 javaMethod.getCodeSize())); 1041 return false; 1042 } 1043 // Allow use of -XX:CompileCommand=dontinline to exclude problematic methods 1044 if (!javaMethod.canBeInlined()) { 1045 return false; 1046 } 1047 // Skip @Snippets for now 1048 for (Annotation annotation : javaMethod.getAnnotations()) { 1049 if (annotation.annotationType().equals(Snippet.class)) { 1050 return false; 1051 } 1052 } 1053 return true; 1054 } 1055 1056 static class Options { 1057 public static final OptionKey<Boolean> Help = new OptionKey<>(false); 1058 public static final OptionKey<String> Classpath = new OptionKey<>(CompileTheWorld.SUN_BOOT_CLASS_PATH); 1059 public static final OptionKey<Boolean> Verbose = new OptionKey<>(true); 1060 /** 1061 * Ignore Graal classes by default to avoid problems associated with compiling snippets and 1062 * method substitutions. 1063 */ 1064 public static final OptionKey<String> LimitModules = new OptionKey<>("~jdk.internal.vm.compiler"); 1065 public static final OptionKey<Integer> Iterations = new OptionKey<>(1); 1066 public static final OptionKey<String> MethodFilter = new OptionKey<>(null); 1067 public static final OptionKey<String> ExcludeMethodFilter = new OptionKey<>(null); 1068 public static final OptionKey<Integer> StartAt = new OptionKey<>(1); 1069 public static final OptionKey<Integer> StopAt = new OptionKey<>(Integer.MAX_VALUE); 1070 public static final OptionKey<Integer> MaxClasses = new OptionKey<>(Integer.MAX_VALUE); 1071 public static final OptionKey<String> Config = new OptionKey<>(null); 1072 public static final OptionKey<Boolean> MultiThreaded = new OptionKey<>(false); 1073 public static final OptionKey<Integer> Threads = new OptionKey<>(0); 1074 public static final OptionKey<Boolean> InvalidateInstalledCode = new OptionKey<>(false); 1075 1076 // @formatter:off 1077 static final ReflectionOptionDescriptors DESCRIPTORS = new ReflectionOptionDescriptors(Options.class, 1078 "Help", "List options and their help messages and then exit.", 1079 "Classpath", "Class path denoting methods to compile. Default is to compile boot classes.", 1080 "Verbose", "Verbose operation. Default is !MultiThreaded.", 1081 "LimitModules", "Comma separated list of module names to which compilation should be limited. " + 1082 "Module names can be prefixed with \"~\" to exclude the named module.", 1083 "Iterations", "The number of iterations to perform.", 1084 "MethodFilter", "Only compile methods matching this filter.", 1085 "ExcludeMethodFilter", "Exclude methods matching this filter from compilation.", 1086 "StartAt", "First class to consider for compilation (default = 1).", 1087 "StopAt", "Last class to consider for compilation (default = <number of classes>).", 1088 "MaxClasses", "Maximum number of classes to process (default = <number of classes>). " + 1089 "Ignored if less than (StopAt - StartAt + 1).", 1090 "Config", "Option values to use during compile the world compilations. For example, " + 1091 "to disable partial escape analysis and print compilations specify " + 1092 "'PartialEscapeAnalysis=false PrintCompilation=true'. " + 1093 "Unless explicitly enabled with 'Inline=true' here, inlining is disabled.", 1094 "MultiThreaded", "Run using multiple threads for compilation.", 1095 "Threads", "Number of threads to use for multithreaded execution. Defaults to Runtime.getRuntime().availableProcessors()."); 1096 // @formatter:on 1097 } 1098 1099 public static OptionValues loadHarnessOptions() { 1100 EconomicMap<OptionKey<?>, Object> values = OptionValues.newOptionMap(); 1101 List<OptionDescriptors> loader = singletonList(DESCRIPTORS); 1102 OptionsParser.parseOptions(extractEntries(System.getProperties(), "CompileTheWorld.", true), values, loader); 1103 OptionValues options = new OptionValues(values); 1104 if (Options.Help.getValue(options)) { 1105 options.printHelp(loader, System.out, "CompileTheWorld."); 1106 System.exit(0); 1107 } 1108 return options; 1109 } 1110 1111 public static void main(String[] args) throws Throwable { 1112 HotSpotJVMCIRuntime jvmciRuntime = HotSpotJVMCIRuntime.runtime(); 1113 HotSpotGraalCompiler compiler = (HotSpotGraalCompiler) jvmciRuntime.getCompiler(); 1114 HotSpotGraalRuntimeProvider graalRuntime = compiler.getGraalRuntime(); 1115 HotSpotCodeCacheProvider codeCache = graalRuntime.getHostProviders().getCodeCache(); 1116 OptionValues harnessOptions = loadHarnessOptions(); 1117 1118 int iterations = Options.Iterations.getValue(harnessOptions); 1119 for (int i = 0; i < iterations; i++) { 1120 codeCache.resetCompilationStatistics(); 1121 TTY.println("CompileTheWorld : iteration " + i); 1122 1123 CompileTheWorld ctw = new CompileTheWorld(jvmciRuntime, compiler, harnessOptions, graalRuntime.getOptions()); 1124 ctw.compile(); 1125 if (iterations > 1) { 1126 // Force a GC to encourage reclamation of nmethods when their InstalledCode 1127 // reference has been dropped. 1128 System.gc(); 1129 } 1130 } 1131 // This is required as non-daemon threads can be started by class initializers 1132 System.exit(0); 1133 } 1134 }