1 /*
2  * Copyright (c) 2017, 2018, 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 
25 package org.graalvm.compiler.core;
26 
27 import static org.graalvm.compiler.core.CompilationWrapper.ExceptionAction.ExitVM;
28 import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationBailoutAction;
29 import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationFailureAction;
30 import static org.graalvm.compiler.core.GraalCompilerOptions.ExitVMOnException;
31 import static org.graalvm.compiler.core.GraalCompilerOptions.MaxCompilationProblemsPerAction;
32 import static org.graalvm.compiler.debug.DebugContext.VERBOSE_LEVEL;
33 import static org.graalvm.compiler.debug.DebugOptions.Dump;
34 import static org.graalvm.compiler.debug.DebugOptions.DumpPath;
35 import static org.graalvm.compiler.debug.DebugOptions.MethodFilter;
36 
37 import java.io.ByteArrayOutputStream;
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.PrintStream;
42 import java.util.Map;
43 
44 import org.graalvm.compiler.debug.DebugContext;
45 import org.graalvm.compiler.debug.DiagnosticsOutputDirectory;
46 import org.graalvm.compiler.debug.PathUtilities;
47 import org.graalvm.compiler.debug.TTY;
48 import org.graalvm.compiler.options.EnumOptionKey;
49 import org.graalvm.compiler.options.OptionValues;
50 
51 import jdk.vm.ci.code.BailoutException;
52 
53 /**
54  * Wrapper for a compilation that centralizes what action to take based on
55  * {@link GraalCompilerOptions#CompilationBailoutAction} and
56  * {@link GraalCompilerOptions#CompilationFailureAction} when an uncaught exception occurs during
57  * compilation.
58  */
59 public abstract class CompilationWrapper<T> {
60 
61     /**
62      * Actions to take upon an exception being raised during compilation performed via
63      * {@link CompilationWrapper}. The actions are with respect to what the user sees on the
64      * console. The compilation requester determines what ultimate action is taken in
65      * {@link CompilationWrapper#handleException(Throwable)}.
66      *
67      * The actions are in ascending order of verbosity.
68      */
69     public enum ExceptionAction {
70         /**
71          * Print nothing to the console.
72          */
73         Silent,
74         /**
75          * Print a stack trace to the console.
76          */
77         Print,
78         /**
79          * An exception causes the compilation to be retried with extra diagnostics enabled.
80          */
81         Diagnose,
82         /**
83          * Same as {@link #Diagnose} except that the VM process is exited after retrying.
84          */
85         ExitVM;
86 
87         private static final ExceptionAction[] VALUES = values();
88 
89         /**
90          * Gets the action that is one level less verbose than this action, bottoming out at the
91          * least verbose action.
92          */
quieter()93         ExceptionAction quieter() {
94             assert ExceptionAction.Silent.ordinal() == 0;
95             int index = Math.max(ordinal() - 1, 0);
96             return VALUES[index];
97         }
98     }
99 
100     private final DiagnosticsOutputDirectory outputDirectory;
101 
102     private final Map<ExceptionAction, Integer> problemsHandledPerAction;
103 
104     /**
105      * @param outputDirectory object used to access a directory for dumping if the compilation is
106      *            re-executed
107      * @param problemsHandledPerAction map used to count the number of compilation failures or
108      *            bailouts handled by each action. This is provided by the caller as it is expected
109      *            to be shared between instances of {@link CompilationWrapper}.
110      */
CompilationWrapper(DiagnosticsOutputDirectory outputDirectory, Map<ExceptionAction, Integer> problemsHandledPerAction)111     public CompilationWrapper(DiagnosticsOutputDirectory outputDirectory, Map<ExceptionAction, Integer> problemsHandledPerAction) {
112         this.outputDirectory = outputDirectory;
113         this.problemsHandledPerAction = problemsHandledPerAction;
114     }
115 
116     /**
117      * Handles an uncaught exception.
118      *
119      * @param t an exception thrown during {@link #run(DebugContext)}
120      * @return a value representing the result of a failed compilation (may be {@code null})
121      */
handleException(Throwable t)122     protected abstract T handleException(Throwable t);
123 
124     /**
125      * Gets the action to take based on the value of {@code actionKey} in {@code options}.
126      *
127      * Subclasses can override this to choose a different action based on factors such as whether
128      * {@code actionKey} has been explicitly set in {@code options} for example.
129      *
130      * @param cause the cause of the bailout or failure
131      */
lookupAction(OptionValues options, EnumOptionKey<ExceptionAction> actionKey, Throwable cause)132     protected ExceptionAction lookupAction(OptionValues options, EnumOptionKey<ExceptionAction> actionKey, Throwable cause) {
133         if (actionKey == CompilationFailureAction) {
134             if (ExitVMOnException.getValue(options)) {
135                 assert CompilationFailureAction.getDefaultValue() != ExceptionAction.ExitVM;
136                 assert ExitVMOnException.getDefaultValue() != true;
137                 if (CompilationFailureAction.hasBeenSet(options) && CompilationFailureAction.getValue(options) != ExceptionAction.ExitVM) {
138                     TTY.printf("WARNING: Ignoring %s=%s since %s=true has been explicitly specified.%n",
139                                     CompilationFailureAction.getName(), CompilationFailureAction.getValue(options),
140                                     ExitVMOnException.getName());
141                 }
142                 return ExceptionAction.ExitVM;
143             }
144         }
145         return actionKey.getValue(options);
146     }
147 
148     /**
149      * Perform the compilation wrapped by this object.
150      *
151      * @param debug the debug context to use for the compilation
152      */
performCompilation(DebugContext debug)153     protected abstract T performCompilation(DebugContext debug);
154 
155     /**
156      * Gets a value that represents the input to the compilation.
157      */
158     @Override
toString()159     public abstract String toString();
160 
161     /**
162      * Creates the {@link DebugContext} to use when retrying a compilation.
163      *
164      * @param options the options for configuring the debug context
165      * @param logStream the log stream to use in the debug context
166      */
createRetryDebugContext(OptionValues options, PrintStream logStream)167     protected abstract DebugContext createRetryDebugContext(OptionValues options, PrintStream logStream);
168 
169     @SuppressWarnings("try")
run(DebugContext initialDebug)170     public final T run(DebugContext initialDebug) {
171         try {
172             return performCompilation(initialDebug);
173         } catch (Throwable cause) {
174             OptionValues initialOptions = initialDebug.getOptions();
175 
176             String causeType = "failure";
177             EnumOptionKey<ExceptionAction> actionKey;
178             if (cause instanceof BailoutException) {
179                 actionKey = CompilationBailoutAction;
180                 causeType = "bailout";
181             } else {
182                 actionKey = CompilationFailureAction;
183                 causeType = "failure";
184             }
185             synchronized (CompilationFailureAction) {
186                 // Serialize all compilation failure handling.
187                 // This prevents retry compilation storms and interleaving
188                 // of compilation exception messages.
189                 // It also allows for reliable testing of CompilationWrapper
190                 // by avoiding a race whereby retry compilation output from a
191                 // forced crash (i.e., use of GraalCompilerOptions.CrashAt)
192                 // is truncated.
193 
194                 ExceptionAction action = lookupAction(initialOptions, actionKey, cause);
195 
196                 action = adjustAction(initialOptions, actionKey, action);
197 
198                 if (action == ExceptionAction.Silent) {
199                     return handleException(cause);
200                 }
201 
202                 if (action == ExceptionAction.Print) {
203                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
204                     try (PrintStream ps = new PrintStream(baos)) {
205                         ps.printf("%s: Compilation of %s failed: ", Thread.currentThread(), this);
206                         cause.printStackTrace(ps);
207                         ps.printf("To disable compilation %s notifications, set %s to %s (e.g., -Dgraal.%s=%s).%n",
208                                         causeType,
209                                         actionKey.getName(), ExceptionAction.Silent,
210                                         actionKey.getName(), ExceptionAction.Silent);
211                         ps.printf("To capture more information for diagnosing or reporting a compilation %s, " +
212                                         "set %s to %s or %s (e.g., -Dgraal.%s=%s).%n",
213                                         causeType,
214                                         actionKey.getName(), ExceptionAction.Diagnose,
215                                         ExceptionAction.ExitVM,
216                                         actionKey.getName(), ExceptionAction.Diagnose);
217                     }
218                     TTY.print(baos.toString());
219                     return handleException(cause);
220                 }
221 
222                 // action is Diagnose or ExitVM
223 
224                 if (Dump.hasBeenSet(initialOptions)) {
225                     // If dumping is explicitly enabled, Graal is being debugged
226                     // so don't interfere with what the user is expecting to see.
227                     return handleException(cause);
228                 }
229 
230                 File dumpPath = null;
231                 try {
232                     String dir = this.outputDirectory.getPath();
233                     if (dir != null) {
234                         String dumpName = PathUtilities.sanitizeFileName(toString());
235                         dumpPath = new File(dir, dumpName);
236                         dumpPath.mkdirs();
237                         if (!dumpPath.exists()) {
238                             TTY.println("Warning: could not create diagnostics directory " + dumpPath);
239                             dumpPath = null;
240                         }
241                     }
242                 } catch (Throwable t) {
243                     TTY.println("Warning: could not create Graal diagnostic directory");
244                     t.printStackTrace(TTY.out);
245                 }
246 
247                 String message;
248                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
249                 try (PrintStream ps = new PrintStream(baos)) {
250                     ps.printf("%s: Compilation of %s failed:%n", Thread.currentThread(), this);
251                     cause.printStackTrace(ps);
252                     ps.printf("To disable compilation %s notifications, set %s to %s (e.g., -Dgraal.%s=%s).%n",
253                                     causeType,
254                                     actionKey.getName(), ExceptionAction.Silent,
255                                     actionKey.getName(), ExceptionAction.Silent);
256                     ps.printf("To print a message for a compilation %s without retrying the compilation, " +
257                                     "set %s to %s (e.g., -Dgraal.%s=%s).%n",
258                                     causeType,
259                                     actionKey.getName(), ExceptionAction.Print,
260                                     actionKey.getName(), ExceptionAction.Print);
261                     if (dumpPath != null) {
262                         ps.println("Retrying compilation of " + this);
263                     } else {
264                         ps.println("Not retrying compilation of " + this + " as the dump path could not be created.");
265                     }
266                     message = baos.toString();
267                 }
268 
269                 TTY.print(message);
270                 if (dumpPath == null) {
271                     return handleException(cause);
272                 }
273 
274                 File retryLogFile = new File(dumpPath, "retry.log");
275                 try (PrintStream ps = new PrintStream(new FileOutputStream(retryLogFile))) {
276                     ps.print(message);
277                 } catch (IOException ioe) {
278                     TTY.printf("Error writing to %s: %s%n", retryLogFile, ioe);
279                 }
280 
281                 OptionValues retryOptions = new OptionValues(initialOptions,
282                                 Dump, ":" + VERBOSE_LEVEL,
283                                 MethodFilter, null,
284                                 DumpPath, dumpPath.getPath());
285 
286                 ByteArrayOutputStream logBaos = new ByteArrayOutputStream();
287                 PrintStream ps = new PrintStream(logBaos);
288                 try (DebugContext retryDebug = createRetryDebugContext(retryOptions, ps)) {
289                     T res = performCompilation(retryDebug);
290                     ps.println("There was no exception during retry.");
291                     maybeExitVM(action);
292                     return res;
293                 } catch (Throwable e) {
294                     ps.println("Exception during retry:");
295                     e.printStackTrace(ps);
296                     // Failures during retry are silent
297                     T res = handleException(cause);
298                     maybeExitVM(action);
299                     return res;
300                 } finally {
301                     ps.close();
302                     try (FileOutputStream fos = new FileOutputStream(retryLogFile, true)) {
303                         fos.write(logBaos.toByteArray());
304                     } catch (Throwable e) {
305                         TTY.printf("Error writing to %s: %s%n", retryLogFile, e);
306                     }
307                 }
308             }
309         }
310     }
311 
maybeExitVM(ExceptionAction action)312     private void maybeExitVM(ExceptionAction action) {
313         if (action == ExitVM) {
314             TTY.println("Exiting VM after retry compilation of " + this);
315             System.exit(-1);
316         }
317     }
318 
319     /**
320      * Adjusts {@code initialAction} if necessary based on
321      * {@link GraalCompilerOptions#MaxCompilationProblemsPerAction}.
322      */
adjustAction(OptionValues initialOptions, EnumOptionKey<ExceptionAction> actionKey, ExceptionAction initialAction)323     private ExceptionAction adjustAction(OptionValues initialOptions, EnumOptionKey<ExceptionAction> actionKey, ExceptionAction initialAction) {
324         ExceptionAction action = initialAction;
325         int maxProblems = MaxCompilationProblemsPerAction.getValue(initialOptions);
326         if (action != ExceptionAction.ExitVM) {
327             synchronized (problemsHandledPerAction) {
328                 while (action != ExceptionAction.Silent) {
329                     int problems = problemsHandledPerAction.getOrDefault(action, 0);
330                     if (problems >= maxProblems) {
331                         if (problems == maxProblems) {
332                             TTY.printf("Warning: adjusting %s from %s to %s after %s (%d) failed compilations%n", actionKey, action, action.quieter(),
333                                             MaxCompilationProblemsPerAction, maxProblems);
334                             // Ensure that the message above is only printed once
335                             problemsHandledPerAction.put(action, problems + 1);
336                         }
337                         action = action.quieter();
338                     } else {
339                         break;
340                     }
341                 }
342                 problemsHandledPerAction.put(action, problemsHandledPerAction.getOrDefault(action, 0) + 1);
343             }
344         }
345         return action;
346     }
347 }
348