Mercurial > hg > truffle
diff jvmci/com.oracle.jvmci.hotspot/src/com/oracle/jvmci/hotspot/CompileTheWorld.java @ 21798:395ac43a8578
moved JVMCI sources from graal/ to jvmci/ directory
author | Doug Simon <doug.simon@oracle.com> |
---|---|
date | Tue, 09 Jun 2015 00:22:49 +0200 |
parents | graal/com.oracle.jvmci.hotspot/src/com/oracle/jvmci/hotspot/CompileTheWorld.java@5b9adb645217 |
children | 90e3fecd4143 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jvmci/com.oracle.jvmci.hotspot/src/com/oracle/jvmci/hotspot/CompileTheWorld.java Tue Jun 09 00:22:49 2015 +0200 @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.jvmci.hotspot; + +import static com.oracle.jvmci.compiler.Compiler.*; +import static com.oracle.jvmci.debug.internal.MemUseTrackerImpl.*; + +import java.io.*; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.net.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; +import java.util.jar.*; +import java.util.stream.*; + +import com.oracle.jvmci.compiler.*; +import com.oracle.jvmci.compiler.CompilerThreadFactory.DebugConfigAccess; +import com.oracle.jvmci.compiler.Compiler; +import com.oracle.jvmci.debug.*; +import com.oracle.jvmci.debug.internal.*; +import com.oracle.jvmci.meta.*; +import com.oracle.jvmci.options.*; +import com.oracle.jvmci.options.OptionUtils.OptionConsumer; +import com.oracle.jvmci.options.OptionValue.OverrideScope; +import com.oracle.jvmci.runtime.*; + +/** + * This class implements compile-the-world functionality with JVMCI. + */ +public final class CompileTheWorld { + + /** + * Magic token to trigger reading files from the boot class path. + */ + public static final String SUN_BOOT_CLASS_PATH = "sun.boot.class.path"; + + public static class Options { + // @formatter:off + @Option(help = "Compile all methods in all classes on given class path", type = OptionType.Debug) + public static final OptionValue<String> CompileTheWorldClasspath = new OptionValue<>(SUN_BOOT_CLASS_PATH); + @Option(help = "Verbose CompileTheWorld operation", type = OptionType.Debug) + public static final OptionValue<Boolean> CompileTheWorldVerbose = new OptionValue<>(true); + @Option(help = "The number of CompileTheWorld iterations to perform", type = OptionType.Debug) + public static final OptionValue<Integer> CompileTheWorldIterations = new OptionValue<>(1); + @Option(help = "Only compile methods matching this filter", type = OptionType.Debug) + public static final OptionValue<String> CompileTheWorldMethodFilter = new OptionValue<>(null); + @Option(help = "Exclude methods matching this filter from compilation", type = OptionType.Debug) + public static final OptionValue<String> CompileTheWorldExcludeMethodFilter = new OptionValue<>(null); + @Option(help = "First class to consider when using -XX:+CompileTheWorld", type = OptionType.Debug) + public static final OptionValue<Integer> CompileTheWorldStartAt = new OptionValue<>(1); + @Option(help = "Last class to consider when using -XX:+CompileTheWorld", type = OptionType.Debug) + public static final OptionValue<Integer> CompileTheWorldStopAt = new OptionValue<>(Integer.MAX_VALUE); + @Option(help = "Option value overrides to use during compile the world. For example, " + + "to disable inlining and partial escape analysis specify '-PartialEscapeAnalysis -Inline'. " + + "The format for each option is the same as on the command line just without the '-G:' prefix.", type = OptionType.Debug) + public static final OptionValue<String> CompileTheWorldConfig = new OptionValue<>(null); + + @Option(help = "Run CTW using as many threads as there are processors on the system", type = OptionType.Debug) + public static final OptionValue<Boolean> CompileTheWorldMultiThreaded = new OptionValue<>(false); + @Option(help = "Number of threads to use for multithreaded CTW. Defaults to Runtime.getRuntime().availableProcessors()", type = OptionType.Debug) + public static final OptionValue<Integer> CompileTheWorldThreads = new OptionValue<>(0); + // @formatter:on + + /** + * Overrides {@link #CompileTheWorldStartAt} and {@link #CompileTheWorldStopAt} from + * {@code -XX} HotSpot options of the same name if the latter have non-default values. + */ + public static void overrideWithNativeOptions(HotSpotVMConfig c) { + if (c.compileTheWorldStartAt != 1) { + CompileTheWorldStartAt.setValue(c.compileTheWorldStartAt); + } + if (c.compileTheWorldStopAt != Integer.MAX_VALUE) { + CompileTheWorldStopAt.setValue(c.compileTheWorldStopAt); + } + } + } + + /** + * A mechanism for overriding JVMCI options that affect compilation. A {@link Config} object + * should be used in a try-with-resources statement to ensure overriding of options is scoped + * properly. For example: + * + * <pre> + * Config config = ...; + * try (AutoCloseable s = config == null ? null : config.apply()) { + * // perform a JVMCI compilation + * } + * </pre> + */ + @SuppressWarnings("serial") + public static class Config extends HashMap<OptionValue<?>, Object> implements OptionConsumer { + /** + * Creates a {@link Config} object by parsing a set of space separated override options. + * + * @param options a space separated set of option value settings with each option setting in + * a format compatible with + * {@link OptionUtils#parseOption(String, OptionConsumer)}. Ignored if null. + */ + public Config(String options) { + if (options != null) { + for (String option : options.split("\\s+")) { + OptionUtils.parseOption(option, this); + } + } + } + + /** + * Applies the overrides represented by this object. The overrides are in effect until + * {@link OverrideScope#close()} is called on the returned object. + */ + OverrideScope apply() { + return OptionValue.override(this); + } + + public void set(OptionDescriptor desc, Object value) { + put(desc.getOptionValue(), value); + } + } + + /** List of Zip/Jar files to compile (see {@link Options#CompileTheWorldClasspath}). */ + private final String files; + + /** Class index to start compilation at (see {@link Options#CompileTheWorldStartAt}). */ + private final int startAt; + + /** Class index to stop compilation at (see {@link Options#CompileTheWorldStopAt}). */ + private final int stopAt; + + /** Only compile methods matching one of the filters in this array if the array is non-null. */ + private final MethodFilter[] methodFilters; + + /** Exclude methods matching one of the filters in this array if the array is non-null. */ + private final MethodFilter[] excludeMethodFilters; + + // Counters + private int classFileCounter = 0; + private AtomicLong compiledMethodsCounter = new AtomicLong(); + private AtomicLong compileTime = new AtomicLong(); + private AtomicLong memoryUsed = new AtomicLong(); + + private boolean verbose; + private final Config config; + + /** + * Signal that the threads should start compiling in multithreaded mode. + */ + private boolean running; + + private ThreadPoolExecutor threadPool; + + /** + * Creates a compile-the-world instance. + * + * @param files {@link File#pathSeparator} separated list of Zip/Jar files to compile + * @param startAt index of the class file to start compilation at + * @param stopAt index of the class file to stop compilation at + * @param methodFilters + * @param excludeMethodFilters + */ + public CompileTheWorld(String files, Config config, int startAt, int stopAt, String methodFilters, String excludeMethodFilters, boolean verbose) { + this.files = files; + this.startAt = startAt; + this.stopAt = stopAt; + this.methodFilters = methodFilters == null || methodFilters.isEmpty() ? null : MethodFilter.parse(methodFilters); + this.excludeMethodFilters = excludeMethodFilters == null || excludeMethodFilters.isEmpty() ? null : MethodFilter.parse(excludeMethodFilters); + this.verbose = verbose; + this.config = config; + + // We don't want the VM to exit when a method fails to compile... + config.putIfAbsent(ExitVMOnException, false); + + // ...but we want to see exceptions. + config.putIfAbsent(PrintBailout, true); + config.putIfAbsent(PrintStackTraceOnException, true); + config.putIfAbsent(HotSpotResolvedJavaMethodImpl.Options.UseProfilingInformation, false); + } + + /** + * Compiles all methods in all classes in the Zip/Jar archive files in + * {@link Options#CompileTheWorldClasspath}. If {@link Options#CompileTheWorldClasspath} + * contains the magic token {@link #SUN_BOOT_CLASS_PATH} passed up from HotSpot we take the + * files from the boot class path. + */ + public void compile() throws Throwable { + // By default only report statistics for the CTW threads themselves + if (JVMCIDebugConfig.DebugValueThreadFilter.hasDefaultValue()) { + JVMCIDebugConfig.DebugValueThreadFilter.setValue("^CompileTheWorld"); + } + + if (SUN_BOOT_CLASS_PATH.equals(files)) { + final String[] entries = System.getProperty(SUN_BOOT_CLASS_PATH).split(File.pathSeparator); + String bcpFiles = ""; + for (int i = 0; i < entries.length; i++) { + final String entry = entries[i]; + + // We stop at rt.jar, unless it is the first boot class path entry. + if (entry.endsWith("rt.jar") && (i > 0)) { + break; + } + if (i > 0) { + bcpFiles += File.pathSeparator; + } + bcpFiles += entry; + } + compile(bcpFiles); + } else { + compile(files); + } + } + + public void println() { + println(""); + } + + public void println(String format, Object... args) { + println(String.format(format, args)); + } + + public void println(String s) { + if (verbose) { + TTY.println(s); + } + } + + @SuppressWarnings("unused") + private static void dummy() { + } + + /** + * Compiles all methods in all classes in the Zip/Jar files passed. + * + * @param fileList {@link File#pathSeparator} separated list of Zip/Jar files to compile + * @throws IOException + */ + private void compile(String fileList) throws IOException { + final String[] entries = fileList.split(File.pathSeparator); + long start = System.currentTimeMillis(); + + CompilerThreadFactory factory = new CompilerThreadFactory("CompileTheWorld", new DebugConfigAccess() { + public JVMCIDebugConfig getDebugConfig() { + if (Debug.isEnabled() && DebugScope.getConfig() == null) { + return DebugEnvironment.initialize(System.out); + } + return null; + } + }); + + try { + // compile dummy method to get compiler initilized outside of the config debug override. + HotSpotResolvedJavaMethod dummyMethod = (HotSpotResolvedJavaMethod) JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().lookupJavaMethod( + CompileTheWorld.class.getDeclaredMethod("dummy")); + CompilationTask task = new CompilationTask(dummyMethod, Compiler.INVOCATION_ENTRY_BCI, 0L, dummyMethod.allocateCompileId(Compiler.INVOCATION_ENTRY_BCI), false); + task.runCompilation(); + } catch (NoSuchMethodException | SecurityException e1) { + e1.printStackTrace(); + } + + /* + * Always use a thread pool, even for single threaded mode since it simplifies the use of + * DebugValueThreadFilter to filter on the thread names. + */ + int threadCount = 1; + if (Options.CompileTheWorldMultiThreaded.getValue()) { + threadCount = Options.CompileTheWorldThreads.getValue(); + if (threadCount == 0) { + threadCount = Runtime.getRuntime().availableProcessors(); + } + } else { + running = true; + } + threadPool = new ThreadPoolExecutor(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory); + + try (OverrideScope s = config.apply()) { + for (int i = 0; i < entries.length; i++) { + final String entry = entries[i]; + + // For now we only compile all methods in all classes in zip/jar files. + if (!entry.endsWith(".zip") && !entry.endsWith(".jar")) { + println("CompileTheWorld : Skipped classes in " + entry); + println(); + continue; + } + + if (methodFilters == null || methodFilters.length == 0) { + println("CompileTheWorld : Compiling all classes in " + entry); + } else { + String include = Arrays.asList(methodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", ")); + println("CompileTheWorld : Compiling all methods in " + entry + " matching one of the following filters: " + include); + } + if (excludeMethodFilters != null && excludeMethodFilters.length > 0) { + String exclude = Arrays.asList(excludeMethodFilters).stream().map(MethodFilter::toString).collect(Collectors.joining(", ")); + println("CompileTheWorld : Excluding all methods matching one of the following filters: " + exclude); + } + println(); + + URL url = new URL("jar", "", "file:" + entry + "!/"); + ClassLoader loader = new URLClassLoader(new URL[]{url}); + + JarFile jarFile = new JarFile(entry); + Enumeration<JarEntry> e = jarFile.entries(); + + while (e.hasMoreElements()) { + JarEntry je = e.nextElement(); + if (je.isDirectory() || !je.getName().endsWith(".class")) { + continue; + } + + // Are we done? + if (classFileCounter >= stopAt) { + break; + } + + String className = je.getName().substring(0, je.getName().length() - ".class".length()); + String dottedClassName = className.replace('/', '.'); + classFileCounter++; + + if (methodFilters != null && !MethodFilter.matchesClassName(methodFilters, dottedClassName)) { + continue; + } + if (excludeMethodFilters != null && MethodFilter.matchesClassName(excludeMethodFilters, dottedClassName)) { + continue; + } + + if (dottedClassName.startsWith("jdk.management.") || dottedClassName.startsWith("jdk.internal.cmm.*")) { + continue; + } + + try { + // Load and initialize class + Class<?> javaClass = Class.forName(dottedClassName, true, loader); + + // Pre-load all classes in the constant pool. + try { + HotSpotResolvedObjectType objectType = HotSpotResolvedObjectTypeImpl.fromObjectClass(javaClass); + ConstantPool constantPool = objectType.constantPool(); + for (int cpi = 1; cpi < constantPool.length(); cpi++) { + constantPool.loadReferencedType(cpi, HotSpotConstantPool.Bytecodes.LDC); + } + } catch (Throwable t) { + // If something went wrong during pre-loading we just ignore it. + println("Preloading failed for (%d) %s: %s", classFileCounter, className, t); + } + + // Are we compiling this class? + MetaAccessProvider metaAccess = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess(); + if (classFileCounter >= startAt) { + println("CompileTheWorld (%d) : %s", classFileCounter, className); + + // Compile each constructor/method in the class. + for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) { + HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(constructor); + if (canBeCompiled(javaMethod, constructor.getModifiers())) { + compileMethod(javaMethod); + } + } + for (Method method : javaClass.getDeclaredMethods()) { + HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) metaAccess.lookupJavaMethod(method); + if (canBeCompiled(javaMethod, method.getModifiers())) { + compileMethod(javaMethod); + } + } + } + } catch (Throwable t) { + println("CompileTheWorld (%d) : Skipping %s %s", classFileCounter, className, t.toString()); + t.printStackTrace(); + } + } + jarFile.close(); + } + } + + if (!running) { + startThreads(); + } + int wakeups = 0; + while (threadPool.getCompletedTaskCount() != threadPool.getTaskCount()) { + if (wakeups % 15 == 0) { + TTY.println("CompileTheWorld : Waiting for " + (threadPool.getTaskCount() - threadPool.getCompletedTaskCount()) + " compiles"); + } + try { + threadPool.awaitTermination(1, TimeUnit.SECONDS); + wakeups++; + } catch (InterruptedException e) { + } + } + threadPool = null; + + long elapsedTime = System.currentTimeMillis() - start; + + println(); + if (Options.CompileTheWorldMultiThreaded.getValue()) { + TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms elapsed, %d ms compile time, %d bytes of memory used)", classFileCounter, compiledMethodsCounter.get(), elapsedTime, + compileTime.get(), memoryUsed.get()); + } else { + TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms, %d bytes of memory used)", classFileCounter, compiledMethodsCounter.get(), compileTime.get(), memoryUsed.get()); + } + } + + private synchronized void startThreads() { + running = true; + // Wake up any waiting threads + notifyAll(); + } + + private synchronized void waitToRun() { + while (!running) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + + private void compileMethod(HotSpotResolvedJavaMethod method) throws InterruptedException, ExecutionException { + if (methodFilters != null && !MethodFilter.matches(methodFilters, method)) { + return; + } + if (excludeMethodFilters != null && MethodFilter.matches(excludeMethodFilters, method)) { + return; + } + Future<?> task = threadPool.submit(new Runnable() { + public void run() { + waitToRun(); + try (OverrideScope s = config.apply()) { + compileMethod(method, classFileCounter); + } + } + }); + if (threadPool.getCorePoolSize() == 1) { + task.get(); + } + } + + /** + * Compiles a method and gathers some statistics. + */ + private void compileMethod(HotSpotResolvedJavaMethod method, int counter) { + try { + long start = System.currentTimeMillis(); + long allocatedAtStart = getCurrentThreadAllocatedBytes(); + + CompilationTask task = new CompilationTask(method, Compiler.INVOCATION_ENTRY_BCI, 0L, method.allocateCompileId(Compiler.INVOCATION_ENTRY_BCI), false); + task.runCompilation(); + + memoryUsed.getAndAdd(getCurrentThreadAllocatedBytes() - allocatedAtStart); + compileTime.getAndAdd(System.currentTimeMillis() - start); + compiledMethodsCounter.incrementAndGet(); + } catch (Throwable t) { + // Catch everything and print a message + println("CompileTheWorld (%d) : Error compiling method: %s", counter, method.format("%H.%n(%p):%r")); + t.printStackTrace(TTY.cachedOut); + } + } + + /** + * Determines if a method should be compiled (Cf. CompilationPolicy::can_be_compiled). + * + * @return true if it can be compiled, false otherwise + */ + private static boolean canBeCompiled(HotSpotResolvedJavaMethod javaMethod, int modifiers) { + if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) { + return false; + } + HotSpotVMConfig c = HotSpotJVMCIRuntime.runtime().getConfig(); + if (c.dontCompileHugeMethods && javaMethod.getCodeSize() > c.hugeMethodLimit) { + return false; + } + // Allow use of -XX:CompileCommand=dontinline to exclude problematic methods + if (!javaMethod.canBeInlined()) { + return false; + } + // Skip @Snippets for now + for (Annotation annotation : javaMethod.getAnnotations()) { + if (annotation.annotationType().getName().equals("com.oracle.graal.replacements.Snippet")) { + return false; + } + } + return true; + } + +}