1 /*
   2  * Copyright (c) 2017, 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.List;
  38 import java.util.concurrent.Executor;
  39 import java.util.concurrent.ExecutorService;
  40 import java.util.concurrent.TimeUnit;
  41 import java.util.function.Predicate;
  42 import java.util.regex.Pattern;
  43 import java.util.stream.Collectors;
  44 
  45 /**
  46  * Runs CompileTheWorld for exact one target. If an error occurs during
  47  * compilation of class N, this driver class saves error information and
  48  * restarts CTW from class N + 1. All saved errors are reported at the end.
  49  * <pre>
  50  * Usage: <target to compile>
  51  * </pre>
  52  */
  53 public class CtwRunner {
  54     private static final Predicate<String> IS_CLASS_LINE = Pattern.compile(
  55             "^\\[\\d+\\]\\s*\\S+\\s*$").asPredicate();
  56 
  57     public static void main(String[] args) throws Exception {
  58         if (args.length != 1) {
  59             throw new Error("Usage: <artifact to compile>");
  60         }
  61         new CtwRunner(args[0]).run();
  62     }
  63 
  64     private final List<Throwable> errors;
  65     private final Path targetPath;
  66     private final String targetName;
  67 
  68     private CtwRunner(String target) {
  69         if (target.equals("modules")) {
  70             targetPath = Paths
  71                     .get(Utils.TEST_JDK)
  72                     .resolve("lib")
  73                     .resolve(target);
  74         } else {
  75             targetPath = Paths.get(target).toAbsolutePath();
  76         }
  77         targetName = targetPath.getFileName().toString();
  78         errors = new ArrayList<>();
  79     }
  80 
  81 
  82     private void run() {
  83         startCtwforAllClasses();
  84         if (!errors.isEmpty()) {
  85             StringBuilder sb = new StringBuilder();
  86             sb.append("There were ")
  87               .append(errors.size())
  88               .append(" errors:[");
  89             System.err.println(sb.toString());
  90             for (Throwable e : errors) {
  91                 sb.append("{")
  92                   .append(e.getMessage())
  93                   .append("}");
  94                 e.printStackTrace(System.err);
  95                 System.err.println();
  96             }
  97             sb.append("]");
  98             throw new AssertionError(sb.toString());
  99         }
 100     }
 101 
 102 
 103     private void startCtwforAllClasses() {
 104         long classStart = 0L;
 105         long classCount = classCount();
 106         Asserts.assertGreaterThan(classCount, 0L,
 107                 targetPath + " does not have any classes");
 108         boolean done = false;
 109         while (!done) {
 110             String[] cmd = cmd(classStart);
 111             try {
 112                 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
 113                         /* addTestVmAndJavaOptions = */ true,
 114                         cmd);
 115                 String commandLine = pb.command()
 116                         .stream()
 117                         .collect(Collectors.joining(" "));
 118                 String phase = phaseName(classStart);
 119                 Path out = Paths.get(".", phase + ".out");
 120                 System.out.printf("%s %dms START : [%s]%n" +
 121                         "cout/cerr are redirected to %s%n",
 122                         phase, TimeUnit.NANOSECONDS.toMillis(System.nanoTime()),
 123                         commandLine, out);
 124                 int exitCode = pb.redirectErrorStream(true)
 125                         .redirectOutput(out.toFile())
 126                         .start()
 127                         .waitFor();
 128                 System.out.printf("%s %dms END : exit code = %d%n",
 129                         phase, TimeUnit.NANOSECONDS.toMillis(System.nanoTime()),
 130                         exitCode);
 131                 Pair<String, Long> lastClass = getLastClass(out);
 132                 if (exitCode == 0) {
 133                     long lastIndex = lastClass == null ? -1 : lastClass.second;
 134                     if (lastIndex != classCount) {
 135                         errors.add(new Error(phase + ": Unexpected zero exit code"
 136                                 + "before finishing all compilations."
 137                                 + " lastClass[" + lastIndex
 138                                 + "] != classCount[" + classCount + "]"));
 139                     } else {
 140                         System.out.println("Executed CTW for all " + classCount
 141                                 + " classes in " + targetPath);
 142                     }
 143                     done = true;
 144                 } else {
 145                     if (lastClass == null) {
 146                         errors.add(new Error(phase + ": failed during preload"
 147                                 + " with classStart = " + classStart));
 148                         // skip one class
 149                         ++classStart;
 150                     } else {
 151                         errors.add(new Error(phase + ": failed during"
 152                                 + " compilation of class #" + lastClass.second
 153                                 + " : " + lastClass.first));
 154                         // continue with the next class
 155                         classStart = lastClass.second + 1;
 156                     }
 157                 }
 158             } catch (Exception e) {
 159                 throw new Error("failed to run from " + classStart, e);
 160             }
 161         }
 162     }
 163 
 164     private long classCount() {
 165         List<PathHandler> phs = PathHandler.create(targetPath.toString());
 166         long result = phs.stream()
 167                          .mapToLong(PathHandler::classCount)
 168                          .sum();
 169         phs.forEach(PathHandler::close);
 170         return result;
 171     }
 172 
 173     private Pair<String, Long> getLastClass(Path errFile) {
 174         try (BufferedReader reader = Files.newBufferedReader(errFile)) {
 175             String line = reader.lines()
 176                     .filter(IS_CLASS_LINE)
 177                     .reduce((a, b) -> b)
 178                     .orElse(null);
 179             if (line != null) {
 180                 int open = line.indexOf('[') + 1;
 181                 int close = line.indexOf(']');
 182                 long index = Long.parseLong(line.substring(open, close));
 183                 String name = line.substring(close + 1).trim().replace('.', '/');
 184                 return new Pair<>(name, index);
 185             }
 186         } catch (IOException ioe) {
 187             throw new Error("can not read " + errFile + " : "
 188                     + ioe.getMessage(), ioe);
 189         }
 190         return null;
 191     }
 192 
 193     private String[] cmd(long classStart) {
 194         String phase = phaseName(classStart);
 195         return new String[] {
 196                 "-Xbatch",
 197                 "-XX:-UseCounterDecay",
 198                 "-XX:-ShowMessageBoxOnError",
 199                 "-XX:+UnlockDiagnosticVMOptions",
 200                 // define phase start
 201                 "-DCompileTheWorldStartAt=" + classStart,
 202                 // CTW library uses WhiteBox API
 203                 "-XX:+WhiteBoxAPI", "-Xbootclasspath/a:.",
 204                 // export jdk.internal packages used by CTW library
 205                 "--add-exports", "java.base/jdk.internal.jimage=ALL-UNNAMED",
 206                 "--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED",
 207                 "--add-exports", "java.base/jdk.internal.reflect=ALL-UNNAMED",
 208                 // enable diagnostic logging
 209                 "-XX:+LogCompilation",
 210                 // use phase specific log, hs_err and ciReplay files
 211                 String.format("-XX:LogFile=hotspot_%s_%%p.log", phase),
 212                 String.format("-XX:ErrorFile=hs_err_%s_%%p.log", phase),
 213                 String.format("-XX:ReplayDataFile=replay_%s_%%p.log", phase),
 214                 // MethodHandle MUST NOT be compiled
 215                 "-XX:CompileCommand=exclude,java/lang/invoke/MethodHandle.*",
 216                 // CTW entry point
 217                 CompileTheWorld.class.getName(),
 218                 targetPath.toString(),
 219         };
 220     }
 221 
 222     private String phaseName(long classStart) {
 223         return String.format("%s_%d", targetName, classStart);
 224     }
 225 
 226 }