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