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