1 /*
  2  * Copyright (c) 2013, 2022, 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 package sun.hotspot.tools.ctw;
 25 
 26 import jdk.internal.access.SharedSecrets;
 27 import jdk.internal.misc.Unsafe;
 28 import jdk.internal.reflect.ConstantPool;
 29 import jdk.test.whitebox.WhiteBox;
 30 
 31 import java.lang.reflect.Executable;
 32 import java.util.Arrays;
 33 import java.util.Objects;
 34 import java.util.concurrent.Executor;
 35 import java.util.concurrent.atomic.AtomicLong;
 36 import java.util.stream.Collectors;
 37 
 38 /**
 39  * Provide method to compile whole class.
 40  * Also contains compiled methods and classes counters.
 41  */
 42 public class Compiler {
 43 
 44     // Call GC after compiling as many methods. This would remove the stale methods.
 45     // This threshold should balance the GC overhead and the cost of keeping lots
 46     // of stale methods around.
 47     private static final long GC_METHOD_THRESHOLD = Long.getLong("gcMethodThreshold", 100);
 48 
 49     private static final Unsafe UNSAFE = Unsafe.getUnsafe();
 50     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
 51     private static final AtomicLong METHOD_COUNT = new AtomicLong();
 52     private static final AtomicLong METHODS_SINCE_LAST_GC = new AtomicLong();
 53 
 54     private Compiler() { }
 55 
 56     /**
 57      * @return count of processed methods
 58      */
 59     public static long getMethodCount() {
 60         return METHOD_COUNT.get();
 61     }
 62 
 63     /**
 64      * Compiles all methods and constructors.
 65      *
 66      * @param aClass class to compile
 67      * @param id an id of the class
 68      * @param executor executor used for compile task invocation
 69      * @throws NullPointerException if {@code class} or {@code executor}
 70      *                              is {@code null}
 71      */
 72     public static void compileClass(Class<?> aClass, long id, Executor executor) {
 73         Objects.requireNonNull(aClass);
 74         Objects.requireNonNull(executor);
 75         ConstantPool constantPool = SharedSecrets.getJavaLangAccess().
 76                 getConstantPool(aClass);
 77         if (Utils.COMPILE_THE_WORLD_PRELOAD_CLASSES) {
 78             preloadClasses(aClass.getName(), id, constantPool);
 79         }
 80         UNSAFE.ensureClassInitialized(aClass);
 81         compileClinit(aClass, id);
 82         long methodCount = 0;
 83         for (Executable e : aClass.getDeclaredConstructors()) {
 84             ++methodCount;
 85             executor.execute(new CompileMethodCommand(id, e));
 86         }
 87         for (Executable e : aClass.getDeclaredMethods()) {
 88             ++methodCount;
 89             executor.execute(new CompileMethodCommand(id, e));
 90         }
 91         METHOD_COUNT.addAndGet(methodCount);
 92 
 93         // See if we need to schedule a GC
 94         while (true) {
 95             long current = METHODS_SINCE_LAST_GC.get();
 96             long update = current + methodCount;
 97             if (update >= GC_METHOD_THRESHOLD) {
 98                 update = 0;
 99             }
100             if (METHODS_SINCE_LAST_GC.compareAndSet(current, update)) {
101                 if (update == 0) {
102                     executor.execute(() -> System.gc());
103                 }
104                 break;
105             }
106         }
107     }
108 
109     private static void preloadClasses(String className, long id,
110             ConstantPool constantPool) {
111         try {
112             for (int i = 0, n = constantPool.getSize(); i < n; ++i) {
113                 try {
114                     constantPool.getClassAt(i);
115                 } catch (IllegalArgumentException ignore) {
116                 }
117             }
118         } catch (Throwable t) {
119             CompileTheWorld.OUT.println(String.format("[%d]\t%s\tWARNING preloading failed : %s",
120                     id, className, t));
121             t.printStackTrace(CompileTheWorld.ERR);
122         }
123     }
124 
125     private static void compileClinit(Class<?> aClass, long id) {
126         int startLevel = Utils.INITIAL_COMP_LEVEL;
127         int endLevel = Utils.TIERED_COMPILATION ? Utils.TIERED_STOP_AT_LEVEL : startLevel;
128         for (int i = startLevel; i <= endLevel; ++i) {
129             try {
130                 WHITE_BOX.enqueueInitializerForCompilation(aClass, i);
131             } catch (Throwable t) {
132                 CompileTheWorld.OUT.println(String.format("[%d]\t%s::<clinit>\tERROR at level %d : %s",
133                         id, aClass.getName(), i, t));
134                 t.printStackTrace(CompileTheWorld.ERR);
135             }
136         }
137     }
138 
139     /**
140      * Compilation of method.
141      * Will compile method on all available comp levels.
142      */
143     private static class CompileMethodCommand implements Runnable {
144         private final long classId;
145         private final String className;
146         private final Executable method;
147 
148         /**
149          * @param classId   id of class
150          * @param method    compiled for compilation
151          */
152         public CompileMethodCommand(long classId, Executable method) {
153             this.classId = classId;
154             this.className = method.getDeclaringClass().getName();
155             this.method = method;
156         }
157 
158         @Override
159         public final void run() {
160             int compLevel = Utils.INITIAL_COMP_LEVEL;
161             if (Utils.TIERED_COMPILATION) {
162                 for (int i = compLevel; i <= Utils.TIERED_STOP_AT_LEVEL; ++i) {
163                     WHITE_BOX.deoptimizeMethod(method);
164                     compileAtLevel(i);
165                 }
166             } else {
167                 compileAtLevel(compLevel);
168             }
169 
170             // Make the method eligible for sweeping sooner
171             WHITE_BOX.deoptimizeMethod(method);
172         }
173 
174         private void waitCompilation() {
175             if (!Utils.BACKGROUND_COMPILATION) {
176                 return;
177             }
178             final Object obj = new Object();
179             synchronized (obj) {
180                 for (int i = 0;
181                      i < 10 && WHITE_BOX.isMethodQueuedForCompilation(method);
182                      ++i) {
183                     try {
184                         obj.wait(1000);
185                     } catch (InterruptedException e) {
186                         Thread.currentThread().interrupt();
187                     }
188                 }
189             }
190         }
191 
192         private void compileAtLevel(int compLevel) {
193             if (WHITE_BOX.isMethodCompilable(method, compLevel)) {
194                 try {
195                     WHITE_BOX.enqueueMethodForCompilation(method, compLevel);
196                     waitCompilation();
197                     int tmp = WHITE_BOX.getMethodCompilationLevel(method);
198                     if (tmp != compLevel) {
199                         log("WARNING compilation level = " + tmp
200                                 + ", but not " + compLevel);
201                     } else if (Utils.IS_VERBOSE) {
202                         log("compilation level = " + tmp + ". OK");
203                     }
204                 } catch (Throwable t) {
205                     log("ERROR at level " + compLevel);
206                     t.printStackTrace(CompileTheWorld.ERR);
207                 }
208             } else if (Utils.IS_VERBOSE) {
209                 log("not compilable at " + compLevel);
210             }
211         }
212 
213         private String methodName() {
214             return String.format("%s::%s(%s)",
215                     className,
216                     method.getName(),
217                     Arrays.stream(method.getParameterTypes())
218                           .map(Class::getName)
219                           .collect(Collectors.joining(", ")));
220         }
221 
222         private void log(String message) {
223             StringBuilder builder = new StringBuilder("[")
224                     .append(classId)
225                     .append("]\t")
226                     .append(methodName());
227             if (message != null) {
228                 builder.append('\t')
229                        .append(message);
230             }
231             CompileTheWorld.ERR.println(builder);
232         }
233     }
234 
235 }