1 /*
   2  * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package com.oracle.graal.hotspot;
  24 
  25 import java.io.File;
  26 import java.lang.reflect.Constructor;
  27 import java.lang.reflect.Method;
  28 import java.lang.reflect.Modifier;
  29 import java.net.*;
  30 import java.util.Enumeration;
  31 import java.util.jar.*;
  32 
  33 import com.oracle.graal.api.meta.*;
  34 import com.oracle.graal.bytecode.Bytecodes;
  35 import com.oracle.graal.debug.*;
  36 import com.oracle.graal.hotspot.bridge.*;
  37 import com.oracle.graal.hotspot.meta.*;
  38 import com.oracle.graal.nodes.*;
  39 import com.oracle.graal.phases.*;
  40 import com.oracle.graal.replacements.*;
  41 
  42 /**
  43  * This class implements compile-the-world functionality in Graal.
  44  */
  45 public final class CompileTheWorld {
  46 
  47     /**
  48      * This is our magic token to trigger reading files from the boot class path.
  49      */
  50     public final static String SUN_BOOT_CLASS_PATH = "sun.boot.class.path";
  51 
  52     // Some runtime instances we need.
  53     private final HotSpotGraalRuntime graalRuntime = HotSpotGraalRuntime.getInstance();
  54     private final VMToCompilerImpl vmToCompiler = (VMToCompilerImpl) graalRuntime.getVMToCompiler();
  55 
  56     /** List of Zip/Jar files to compile (see {@link GraalOptions.CompileTheWorld} */
  57     private final String files;
  58 
  59     /** Class index to start compilation at (see {@link GraalOptions.CompileTheWorldStartAt} */
  60     private final int startAt;
  61 
  62     /** Class index to stop compilation at (see {@link GraalOptions.CompileTheWorldStopAt} */
  63     private final int stopAt;
  64 
  65     // Counters
  66     private int classFileCounter = 0;
  67     private int compiledMethodsCounter = 0;
  68     private long compileTime = 0;
  69 
  70     /**
  71      * Create a compile-the-world instance with default values from
  72      * {@link GraalOptions.CompileTheWorld}, {@link GraalOptions.CompileTheWorldStartAt} and
  73      * {@link GraalOptions.CompileTheWorldStopAt}.
  74      */
  75     public CompileTheWorld() {
  76         this(GraalOptions.CompileTheWorld, GraalOptions.CompileTheWorldStartAt, GraalOptions.CompileTheWorldStopAt);
  77     }
  78 
  79     /**
  80      * Create a compile-the-world instance.
  81      * 
  82      * @param files {@link File.pathSeparator} separated list of Zip/Jar files
  83      * @param startAt index of the class file to start compilation at
  84      * @param stopAt index of the class file to stop compilation at
  85      */
  86     public CompileTheWorld(String files, int startAt, int stopAt) {
  87         this.files = files;
  88         this.startAt = startAt;
  89         this.stopAt = stopAt;
  90 
  91         // We don't want the VM to exit when a method fails to compile.
  92         GraalOptions.ExitVMOnException = false;
  93     }
  94 
  95     /**
  96      * Compile all methods in all classes in the Zip/Jar files in
  97      * {@link GraalOptions.CompileTheWorld}. If the GraalOptions.CompileTheWorld contains the magic
  98      * token {@link SUN_BOOT_CLASS_PATH} passed up from HotSpot we take the files from the boot
  99      * class path.
 100      * 
 101      * @throws Throwable
 102      */
 103     public void compile() throws Throwable {
 104         if (SUN_BOOT_CLASS_PATH.equals(files)) {
 105             final String[] entries = System.getProperty(SUN_BOOT_CLASS_PATH).split(File.pathSeparator);
 106             String files = "";
 107             for (int i = 0; i < entries.length; i++) {
 108                 final String entry = entries[i];
 109 
 110                 // We stop at rt.jar, unless it is the first boot class path entry.
 111                 if (entry.endsWith("rt.jar") && (i > 0)) {
 112                     break;
 113                 }
 114                 if (i > 0) {
 115                     files += File.pathSeparator;
 116                 }
 117                 files += entry;
 118             }
 119             compile(files);
 120         } else {
 121             compile(files);
 122         }
 123     }
 124 
 125     /**
 126      * Compile all methods in all classes in the Zip/Jar files passed in files.
 127      * 
 128      * @param files {@link File.pathSeparator} separated list of Zip/Jar files
 129      * @throws Throwable
 130      */
 131     private void compile(String files) throws Throwable {
 132         final String[] entries = files.split(File.pathSeparator);
 133 
 134         for (int i = 0; i < entries.length; i++) {
 135             final String entry = entries[i];
 136 
 137             // For now we only compile all methods in all classes in zip/jar files.
 138             if (!entry.endsWith(".zip") && !entry.endsWith(".jar")) {
 139                 TTY.println("CompileTheWorld : Skipped classes in " + entry);
 140                 TTY.println();
 141                 continue;
 142             }
 143 
 144             TTY.println("CompileTheWorld : Compiling all classes in " + entry);
 145             TTY.println();
 146 
 147             URL url = new URL("jar", "", "file:" + entry + "!/");
 148             ClassLoader loader = new URLClassLoader(new URL[]{url});
 149 
 150             JarFile jarFile = new JarFile(entry);
 151             Enumeration<JarEntry> e = jarFile.entries();
 152 
 153             while (e.hasMoreElements()) {
 154                 JarEntry je = e.nextElement();
 155                 if (je.isDirectory() || !je.getName().endsWith(".class")) {
 156                     continue;
 157                 }
 158 
 159                 // Are we done?
 160                 if (classFileCounter >= stopAt) {
 161                     break;
 162                 }
 163 
 164                 String className = je.getName().substring(0, je.getName().length() - ".class".length());
 165                 className = className.replace('/', '.');
 166                 classFileCounter++;
 167 
 168                 try {
 169                     // Load and initialize class
 170                     Class<?> javaClass = Class.forName(className, true, loader);
 171 
 172                     // Pre-load all classes in the constant pool.
 173                     try {
 174                         HotSpotResolvedObjectType objectType = (HotSpotResolvedObjectType) HotSpotResolvedObjectType.fromClass(javaClass);
 175                         ConstantPool constantPool = objectType.constantPool();
 176                         for (int cpi = 1; cpi < constantPool.length(); cpi++) {
 177                             constantPool.loadReferencedType(cpi, Bytecodes.LDC);
 178                         }
 179                     } catch (Throwable t) {
 180                         // If something went wrong during pre-loading we just ignore it.
 181                         TTY.println("CompileTheWorld (%d) : Preloading failed for %s", classFileCounter, className);
 182                     }
 183 
 184                     // Are we compiling this class?
 185                     if (classFileCounter >= startAt) {
 186                         TTY.println("CompileTheWorld (%d) : %s", classFileCounter, className);
 187 
 188                         // Enqueue each constructor/method in the class for compilation.
 189                         for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
 190                             HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) graalRuntime.getRuntime().lookupJavaConstructor(constructor);
 191                             if (canBeCompiled(javaMethod, constructor.getModifiers())) {
 192                                 compileMethod(javaMethod);
 193                             }
 194                         }
 195                         for (Method method : javaClass.getDeclaredMethods()) {
 196                             HotSpotResolvedJavaMethod javaMethod = (HotSpotResolvedJavaMethod) graalRuntime.getRuntime().lookupJavaMethod(method);
 197                             if (canBeCompiled(javaMethod, method.getModifiers())) {
 198                                 compileMethod(javaMethod);
 199                             }
 200                         }
 201                     }
 202                 } catch (Throwable t) {
 203                     TTY.println("CompileTheWorld (%d) : Skipping %s", classFileCounter, className);
 204                 }
 205             }
 206             jarFile.close();
 207         }
 208 
 209         TTY.println();
 210         TTY.println("CompileTheWorld : Done (%d classes, %d methods, %d ms)", classFileCounter, compiledMethodsCounter, compileTime);
 211     }
 212 
 213     /**
 214      * Helper method to schedule a method for compilation and gather some statistics.
 215      */
 216     private void compileMethod(HotSpotResolvedJavaMethod method) {
 217         try {
 218             long start = System.currentTimeMillis();
 219             vmToCompiler.compileMethod(method, StructuredGraph.INVOCATION_ENTRY_BCI, true, 10);
 220             compileTime += (System.currentTimeMillis() - start);
 221             compiledMethodsCounter++;
 222             method.reprofile();  // makes the method also not-entrant
 223         } catch (Throwable t) {
 224             // Catch everything and print a message
 225             TTY.println("CompileTheWorld (%d) : Error compiling method: %s", classFileCounter, MetaUtil.format("%H.%n(%p):%r", method));
 226             t.printStackTrace(TTY.cachedOut);
 227         }
 228     }
 229 
 230     /**
 231      * Helper method for CompileTheWorld to determine if a method should be compiled (Cf.
 232      * CompilationPolicy::can_be_compiled).
 233      * 
 234      * @return true if it can be compiled, false otherwise
 235      */
 236     private static boolean canBeCompiled(HotSpotResolvedJavaMethod javaMethod, int modifiers) {
 237         if (Modifier.isAbstract(modifiers) || Modifier.isNative(modifiers)) {
 238             return false;
 239         }
 240         // This number is from HotSpot:
 241         final int HugeMethodLimit = 8000;
 242         if (javaMethod.getCodeSize() > HugeMethodLimit) {
 243             return false;
 244         }
 245         // Skip @Snippets for now
 246         if (javaMethod.getAnnotation(Snippet.class) != null) {
 247             return false;
 248         }
 249         return true;
 250     }
 251 
 252 }