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
24package sun.hotspot.tools.ctw;
25
26import jdk.test.lib.Asserts;
27import jdk.test.lib.Utils;
28import jdk.test.lib.process.ProcessTools;
29import jdk.test.lib.util.Pair;
30
31import java.io.BufferedReader;
32import java.io.IOException;
33import java.nio.file.Files;
34import java.nio.file.Path;
35import java.nio.file.Paths;
36import java.util.ArrayList;
37import java.util.List;
38import java.util.concurrent.Executor;
39import java.util.concurrent.ExecutorService;
40import java.util.concurrent.TimeUnit;
41import java.util.function.Predicate;
42import java.util.regex.Pattern;
43import 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 */
53public 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}
227