1 /*
  2  * Copyright (c) 2017, 2023, 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.test.lib.Asserts;
 27 import jdk.test.lib.Utils;
 28 import jdk.test.lib.process.ProcessTools;
 29 import jdk.test.lib.util.Pair;
 30 
 31 import java.io.BufferedReader;
 32 import java.io.IOException;
 33 import java.nio.file.Files;
 34 import java.nio.file.Path;
 35 import java.nio.file.Paths;
 36 import java.util.ArrayList;
 37 import java.util.Arrays;
 38 import java.util.List;
 39 import java.util.concurrent.TimeUnit;
 40 import java.util.function.Predicate;
 41 import java.util.regex.Pattern;
 42 import java.util.stream.Collectors;
 43 
 44 /**
 45  * Runs CompileTheWorld for exact one target. If an error occurs during
 46  * compilation of class N, this driver class saves error information and
 47  * restarts CTW from class N + 1. All saved errors are reported at the end.
 48  * <pre>
 49  * Usage: <target to compile>
 50  * </pre>
 51  */
 52 public class CtwRunner {
 53     private static final Predicate<String> IS_CLASS_LINE = Pattern.compile(
 54             "^\\[\\d+\\]\\s*\\S+\\s*$").asPredicate();
 55 
 56     /**
 57      * Value of {@code -Dsun.hotspot.tools.ctwrunner.ctw_extra_args}. Extra
 58      * comma-separated arguments to pass to CTW subprocesses.
 59      */
 60     private static final String CTW_EXTRA_ARGS
 61             = System.getProperty("sun.hotspot.tools.ctwrunner.ctw_extra_args", "");
 62 
 63 
 64     private static final String USAGE = "Usage: CtwRunner <artifact to compile> [start[%] stop[%]]";
 65 
 66     public static void main(String[] args) throws Exception {
 67         CtwRunner runner;
 68         switch (args.length) {
 69             case 1: runner = new CtwRunner(args[0]); break;
 70             case 3: runner = new CtwRunner(args[0], args[1], args[2]); break;
 71             default: throw new Error(USAGE);
 72         }
 73 
 74         runner.run();
 75     }
 76 
 77     private final List<Throwable> errors;
 78     private final String target;
 79     private final Path targetPath;
 80     private final String targetName;
 81 
 82     private final int start, stop;
 83     private final boolean isStartStopPercentage;
 84 
 85     private CtwRunner(String target, String start, String stop) {
 86         if (target.startsWith("modules")) {
 87             targetPath = Paths
 88                     .get(Utils.TEST_JDK)
 89                     .resolve("lib")
 90                     .resolve("modules");
 91             if (target.equals("modules")){
 92                 target = targetPath.toString();
 93             }
 94             targetName = target.replace(':', '_')
 95                                .replace('.', '_')
 96                                .replace(',', '_');
 97         } else {
 98             targetPath = Paths.get(target).toAbsolutePath();
 99             targetName = targetPath.getFileName().toString();
100         }
101         this.target = target;
102         errors = new ArrayList<>();
103 
104         if (start.endsWith("%") && stop.endsWith("%")) {
105             int startPercentage = Integer.parseInt(start.substring(0, start.length() - 1));
106             int stopPercentage = Integer.parseInt(stop.substring(0, stop.length() - 1));
107             if (startPercentage < 0 || startPercentage > 100 ||
108                 stopPercentage < 0 || stopPercentage > 100) {
109                 throw new Error(USAGE);
110             }
111             this.start = startPercentage;
112             this.stop = stopPercentage;
113             this.isStartStopPercentage = true;
114         } else if (!start.endsWith("%") && !stop.endsWith("%")) {
115             this.start = Integer.parseInt(start);
116             this.stop = Integer.parseInt(stop);
117             this.isStartStopPercentage = false;
118         } else {
119             throw new Error(USAGE);
120         }
121     }
122 
123     private CtwRunner(String target) {
124         this(target, "0%", "100%");
125     }
126 
127     private void run() {
128         startCtwforAllClasses();
129         if (!errors.isEmpty()) {
130             StringBuilder sb = new StringBuilder();
131             sb.append("There were ")
132               .append(errors.size())
133               .append(" errors:[");
134             System.err.println(sb.toString());
135             for (Throwable e : errors) {
136                 sb.append("{")
137                   .append(e.getMessage())
138                   .append("}");
139                 e.printStackTrace(System.err);
140                 System.err.println();
141             }
142             sb.append("]");
143             throw new AssertionError(sb.toString());
144         }
145     }
146 
147     private long start(long totalClassCount) {
148         if (isStartStopPercentage) {
149             return totalClassCount * start / 100;
150         } else if (start > totalClassCount) {
151             System.err.println("WARNING: start [" + start + "] > totalClassCount [" + totalClassCount + "]");
152             return totalClassCount;
153         } else {
154             return start;
155         }
156     }
157 
158     private long stop(long totalClassCount) {
159         if (isStartStopPercentage) {
160             return totalClassCount * stop / 100;
161         } else if (stop > totalClassCount) {
162             System.err.println("WARNING: stop [" + start + "] > totalClassCount [" + totalClassCount + "]");
163             return totalClassCount;
164         } else {
165             return stop;
166         }
167     }
168 
169     private void startCtwforAllClasses() {
170         long totalClassCount = classCount();
171 
172         long classStart = start(totalClassCount);
173         long classStop = stop(totalClassCount);
174 
175         long classCount = classStop - classStart;
176         Asserts.assertGreaterThan(classCount, 0L,
177                 target + "(at " + targetPath + ") does not have any classes");
178 
179         System.out.printf("Compiling %d classes (of %d total classes) starting at %d and ending at %d\n",
180                           classCount, totalClassCount, classStart, classStop);
181 
182         boolean done = false;
183         while (!done) {
184             String[] cmd = cmd(classStart, classStop);
185             try {
186                 ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(cmd);
187                 String commandLine = pb.command()
188                         .stream()
189                         .collect(Collectors.joining(" "));
190                 String phase = phaseName(classStart);
191                 Path out = Paths.get(".", phase + ".out");
192                 Path err = Paths.get(".", phase + ".err");
193                 System.out.printf("%s %dms START : [%s]%n" +
194                         "cout/cerr are redirected to %s%n",
195                         phase, TimeUnit.NANOSECONDS.toMillis(System.nanoTime()),
196                         commandLine, phase);
197                 int exitCode = pb.redirectOutput(out.toFile())
198                                  .redirectError(err.toFile())
199                                  .start()
200                                  .waitFor();
201                 System.out.printf("%s %dms END : exit code = %d%n",
202                         phase, TimeUnit.NANOSECONDS.toMillis(System.nanoTime()),
203                         exitCode);
204                 Pair<String, Long> lastClass = getLastClass(out);
205                 if (exitCode == 0) {
206                     long lastIndex = lastClass == null ? -1 : lastClass.second;
207                     if (lastIndex != classStop) {
208                         errors.add(new Error(phase + ": Unexpected zero exit code"
209                                 + "before finishing all compilations."
210                                 + " lastClass[" + lastIndex
211                                 + "] != classStop[" + classStop + "]"));
212                     } else {
213                         System.out.println("Executed CTW for all " + classCount
214                                 + " classes in " + target + "(at " + targetPath + ")");
215                     }
216                     done = true;
217                 } else {
218                     if (lastClass == null) {
219                         errors.add(new Error(phase + ": failed during preload"
220                                 + " with classStart = " + classStart));
221                         // skip one class
222                         ++classStart;
223                     } else {
224                         errors.add(new Error(phase + ": failed during"
225                                 + " compilation of class #" + lastClass.second
226                                 + " : " + lastClass.first));
227                         // continue with the next class
228                         classStart = lastClass.second + 1;
229                     }
230                 }
231             } catch (Exception e) {
232                 throw new Error("failed to run from " + classStart, e);
233             }
234         }
235     }
236 
237     private long classCount() {
238         List<PathHandler> phs = PathHandler.create(target);
239         long result = phs.stream()
240                          .mapToLong(PathHandler::classCount)
241                          .sum();
242         phs.forEach(PathHandler::close);
243         return result;
244     }
245 
246     private Pair<String, Long> getLastClass(Path errFile) {
247         try (BufferedReader reader = Files.newBufferedReader(errFile)) {
248             String line = reader.lines()
249                     .filter(IS_CLASS_LINE)
250                     .reduce((a, b) -> b)
251                     .orElse(null);
252             if (line != null) {
253                 int open = line.indexOf('[') + 1;
254                 int close = line.indexOf(']');
255                 long index = Long.parseLong(line.substring(open, close));
256                 String name = line.substring(close + 1).trim().replace('.', '/');
257                 return new Pair<>(name, index);
258             }
259         } catch (IOException ioe) {
260             throw new Error("can not read " + errFile + " : "
261                     + ioe.getMessage(), ioe);
262         }
263         return null;
264     }
265 
266     private String[] cmd(long classStart, long classStop) {
267         String phase = phaseName(classStart);
268         Path file = Paths.get(phase + ".cmd");
269         var rng = Utils.getRandomInstance();
270 
271         ArrayList<String> Args = new ArrayList<String>(Arrays.asList(
272                 "-Xbatch",
273                 "-XX:-UseCounterDecay",
274                 "-XX:-ShowMessageBoxOnError",
275                 "-XX:+UnlockDiagnosticVMOptions",
276                 // redirect VM output to cerr so it won't collide w/ ctw output
277                 "-XX:+DisplayVMOutputToStderr",
278                 // define phase start
279                 "-DCompileTheWorldStartAt=" + classStart,
280                 "-DCompileTheWorldStopAt=" + classStop,
281                 // CTW library uses WhiteBox API
282                 "-XX:+WhiteBoxAPI", "-Xbootclasspath/a:.",
283                 // export jdk.internal packages used by CTW library
284                 "--add-exports", "java.base/jdk.internal.jimage=ALL-UNNAMED",
285                 "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED",
286                 "--add-exports", "java.base/jdk.internal.reflect=ALL-UNNAMED",
287                 "--add-exports", "java.base/jdk.internal.access=ALL-UNNAMED",
288                 // enable diagnostic logging
289                 "-XX:+LogCompilation",
290                 // use phase specific log, hs_err and ciReplay files
291                 String.format("-XX:LogFile=hotspot_%s_%%p.log", phase),
292                 String.format("-XX:ErrorFile=hs_err_%s_%%p.log", phase),
293                 String.format("-XX:ReplayDataFile=replay_%s_%%p.log", phase),
294                 // MethodHandle MUST NOT be compiled
295                 "-XX:CompileCommand=exclude,java/lang/invoke/MethodHandle.*",
296                 // Stress* are c2-specific stress flags, so IgnoreUnrecognizedVMOptions is needed
297                 "-XX:+IgnoreUnrecognizedVMOptions",
298                 "-XX:+StressLCM",
299                 "-XX:+StressGCM",
300                 "-XX:+StressIGVN",
301                 "-XX:+StressCCP",
302                 // StressSeed is uint
303                 "-XX:StressSeed=" + Math.abs(rng.nextInt())));
304 
305         for (String arg : CTW_EXTRA_ARGS.split(",")) {
306             Args.add(arg);
307         }
308 
309         // CTW entry point
310         Args.add(CompileTheWorld.class.getName());
311         Args.add(target);
312 
313         try {
314             Files.write(file, Args);
315         } catch (IOException e) {
316             throw new Error("can't create " + file, e);
317         }
318         return new String[]{ "@" + file.toAbsolutePath() };
319     }
320 
321     private String phaseName(long classStart) {
322         return String.format("%s_%d", targetName, classStart);
323     }
324 
325 }