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