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 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.DebugCloseable; 45 import org.graalvm.compiler.debug.DebugContext; 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.EnumOptionKey; 50 import org.graalvm.compiler.options.OptionValues; 51 52 import jdk.vm.ci.code.BailoutException; 53 54 /** 55 * Wrapper for a compilation that centralizes what action to take based on 56 * {@link GraalCompilerOptions#CompilationBailoutAction} and 57 * {@link GraalCompilerOptions#CompilationFailureAction} when an uncaught exception occurs during 58 * compilation. 59 */ 60 public abstract class CompilationWrapper<T> { 61 62 /** 63 * Actions to take upon an exception being raised during compilation performed via 64 * {@link CompilationWrapper}. The actions are with respect to what the user sees on the 65 * console. The compilation requester determines what ultimate action is taken in 66 * {@link CompilationWrapper#handleException(Throwable)}. 67 * 68 * The actions are in ascending order of verbosity. 69 */ 70 public enum ExceptionAction { 71 /** 72 * Print nothing to the console. 73 */ 74 Silent, 75 /** 76 * Print a stack trace to the console. 77 */ 78 Print, 79 /** 80 * An exception causes the compilation to be retried with extra diagnostics enabled. 81 */ 82 Diagnose, 83 /** 84 * Same as {@link #Diagnose} except that the VM process is exited after retrying. 85 */ 86 ExitVM; 87 88 private static final ExceptionAction[] VALUES = values(); 89 90 /** 91 * Gets the action that is one level less verbose than this action, bottoming out at the 92 * least verbose action. 93 */ 94 ExceptionAction quieter() { 95 assert ExceptionAction.Silent.ordinal() == 0; 96 int index = Math.max(ordinal() - 1, 0); 97 return VALUES[index]; 98 } 99 } 100 101 private final DiagnosticsOutputDirectory outputDirectory; 102 103 private final Map<ExceptionAction, Integer> problemsHandledPerAction; 104 105 /** 106 * @param outputDirectory object used to access a directory for dumping if the compilation is 107 * re-executed 108 * @param problemsHandledPerAction map used to count the number of compilation failures or 109 * bailouts handled by each action. This is provided by the caller as it is expected 110 * to be shared between instances of {@link CompilationWrapper}. 111 */ 112 public CompilationWrapper(DiagnosticsOutputDirectory outputDirectory, Map<ExceptionAction, Integer> problemsHandledPerAction) { 113 this.outputDirectory = outputDirectory; 114 this.problemsHandledPerAction = problemsHandledPerAction; 115 } 116 117 /** 118 * Handles an uncaught exception. 119 * 120 * @param t an exception thrown during {@link #run(DebugContext)} 121 * @return a value representing the result of a failed compilation (may be {@code null}) 122 */ 123 protected abstract T handleException(Throwable t); 124 125 /** 126 * Gets the action to take based on the value of {@code actionKey} in {@code options}. 127 * 128 * Subclasses can override this to choose a different action based on factors such as whether 129 * {@code actionKey} has been explicitly set in {@code options} for example. 130 * 131 * @param cause the cause of the bailout or failure 132 */ 133 protected ExceptionAction lookupAction(OptionValues options, EnumOptionKey<ExceptionAction> actionKey, Throwable cause) { 134 if (actionKey == CompilationFailureAction) { 135 if (ExitVMOnException.getValue(options)) { 136 assert CompilationFailureAction.getDefaultValue() != ExceptionAction.ExitVM; 137 assert ExitVMOnException.getDefaultValue() != true; 138 if (CompilationFailureAction.hasBeenSet(options) && CompilationFailureAction.getValue(options) != ExceptionAction.ExitVM) { 139 TTY.printf("WARNING: Ignoring %s=%s since %s=true has been explicitly specified.%n", 140 CompilationFailureAction.getName(), CompilationFailureAction.getValue(options), 141 ExitVMOnException.getName()); 142 } 143 return ExceptionAction.ExitVM; 144 } 145 } 146 return actionKey.getValue(options); 147 } 148 149 /** 150 * Perform the compilation wrapped by this object. 151 * 152 * @param debug the debug context to use for the compilation 153 */ 154 protected abstract T performCompilation(DebugContext debug); 155 156 /** 157 * Gets a value that represents the input to the compilation. 158 */ 159 @Override 160 public abstract String toString(); 161 162 /** 163 * Creates the {@link DebugContext} to use when retrying a compilation. 164 * 165 * @param options the options for configuring the debug context 166 */ 167 protected abstract DebugContext createRetryDebugContext(OptionValues options); 168 169 @SuppressWarnings("try") 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 String dir = this.outputDirectory.getPath(); 231 if (dir == null) { 232 return handleException(cause); 233 } 234 String dumpName = PathUtilities.sanitizeFileName(toString()); 235 File dumpPath = new File(dir, dumpName); 236 dumpPath.mkdirs(); 237 if (!dumpPath.exists()) { 238 TTY.println("Warning: could not create diagnostics directory " + dumpPath); 239 return handleException(cause); 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: ", Thread.currentThread(), this); 246 cause.printStackTrace(ps); 247 ps.printf("To disable compilation %s notifications, set %s to %s (e.g., -Dgraal.%s=%s).%n", 248 causeType, 249 actionKey.getName(), ExceptionAction.Silent, 250 actionKey.getName(), ExceptionAction.Silent); 251 ps.printf("To print a message for a compilation %s without retrying the compilation, " + 252 "set %s to %s (e.g., -Dgraal.%s=%s).%n", 253 causeType, 254 actionKey.getName(), ExceptionAction.Print, 255 actionKey.getName(), ExceptionAction.Print); 256 ps.println("Retrying compilation of " + this); 257 message = baos.toString(); 258 } 259 260 TTY.print(message); 261 File retryLogFile = new File(dumpPath, "retry.log"); 262 try (PrintStream ps = new PrintStream(new FileOutputStream(retryLogFile))) { 263 ps.print(message); 264 } catch (IOException ioe) { 265 TTY.printf("Error writing to %s: %s%n", retryLogFile, ioe); 266 } 267 268 OptionValues retryOptions = new OptionValues(initialOptions, 269 Dump, ":" + VERBOSE_LEVEL, 270 MethodFilter, null, 271 DumpPath, dumpPath.getPath()); 272 273 try (DebugContext retryDebug = createRetryDebugContext(retryOptions); DebugCloseable s = retryDebug.disableIntercept()) { 274 T res = performCompilation(retryDebug); 275 maybeExitVM(action); 276 return res; 277 } catch (Throwable ignore) { 278 // Failures during retry are silent 279 T res = handleException(cause); 280 maybeExitVM(action); 281 return res; 282 } 283 } 284 } 285 } 286 287 private void maybeExitVM(ExceptionAction action) { 288 if (action == ExitVM) { 289 TTY.println("Exiting VM after retry compilation of " + this); 290 System.exit(-1); 291 } 292 } 293 294 /** 295 * Adjusts {@code initialAction} if necessary based on 296 * {@link GraalCompilerOptions#MaxCompilationProblemsPerAction}. 297 */ 298 private ExceptionAction adjustAction(OptionValues initialOptions, EnumOptionKey<ExceptionAction> actionKey, ExceptionAction initialAction) { 299 ExceptionAction action = initialAction; 300 int maxProblems = MaxCompilationProblemsPerAction.getValue(initialOptions); 301 if (action != ExceptionAction.ExitVM) { 302 synchronized (problemsHandledPerAction) { 303 while (action != ExceptionAction.Silent) { 304 int problems = problemsHandledPerAction.getOrDefault(action, 0); 305 if (problems >= maxProblems) { 306 if (problems == maxProblems) { 307 TTY.printf("Warning: adjusting %s from %s to %s after %s (%d) failed compilations%n", actionKey, action, action.quieter(), 308 MaxCompilationProblemsPerAction, maxProblems); 309 // Ensure that the message above is only printed once 310 problemsHandledPerAction.put(action, problems + 1); 311 } 312 action = action.quieter(); 313 } else { 314 break; 315 } 316 } 317 problemsHandledPerAction.put(action, problemsHandledPerAction.getOrDefault(action, 0) + 1); 318 } 319 } 320 return action; 321 } 322 }