/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */


package org.graalvm.compiler.core;

import static org.graalvm.compiler.core.CompilationWrapper.ExceptionAction.ExitVM;
import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationBailoutAction;
import static org.graalvm.compiler.core.GraalCompilerOptions.CompilationFailureAction;
import static org.graalvm.compiler.core.GraalCompilerOptions.ExitVMOnException;
import static org.graalvm.compiler.core.GraalCompilerOptions.MaxCompilationProblemsPerAction;
import static org.graalvm.compiler.debug.DebugContext.VERBOSE_LEVEL;
import static org.graalvm.compiler.debug.DebugOptions.Dump;
import static org.graalvm.compiler.debug.DebugOptions.DumpPath;
import static org.graalvm.compiler.debug.DebugOptions.MethodFilter;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Map;

import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.debug.DiagnosticsOutputDirectory;
import org.graalvm.compiler.debug.PathUtilities;
import org.graalvm.compiler.debug.TTY;
import org.graalvm.compiler.options.EnumOptionKey;
import org.graalvm.compiler.options.OptionValues;

import jdk.vm.ci.code.BailoutException;

Wrapper for a compilation that centralizes what action to take based on GraalCompilerOptions.CompilationBailoutAction and GraalCompilerOptions.CompilationFailureAction when an uncaught exception occurs during compilation.
/** * Wrapper for a compilation that centralizes what action to take based on * {@link GraalCompilerOptions#CompilationBailoutAction} and * {@link GraalCompilerOptions#CompilationFailureAction} when an uncaught exception occurs during * compilation. */
public abstract class CompilationWrapper<T> {
Actions to take upon an exception being raised during compilation performed via CompilationWrapper. The actions are with respect to what the user sees on the console. The compilation requester determines what ultimate action is taken in CompilationWrapper.handleException(Throwable). The actions are in ascending order of verbosity.
/** * Actions to take upon an exception being raised during compilation performed via * {@link CompilationWrapper}. The actions are with respect to what the user sees on the * console. The compilation requester determines what ultimate action is taken in * {@link CompilationWrapper#handleException(Throwable)}. * * The actions are in ascending order of verbosity. */
public enum ExceptionAction {
Print nothing to the console.
/** * Print nothing to the console. */
Silent,
Print a stack trace to the console.
/** * Print a stack trace to the console. */
Print,
An exception causes the compilation to be retried with extra diagnostics enabled.
/** * An exception causes the compilation to be retried with extra diagnostics enabled. */
Diagnose,
Same as Diagnose except that the VM process is exited after retrying.
/** * Same as {@link #Diagnose} except that the VM process is exited after retrying. */
ExitVM;
Gets the action that is one level less verbose than this action, bottoming out at the least verbose action.
/** * Gets the action that is one level less verbose than this action, bottoming out at the * least verbose action. */
ExceptionAction quieter() { assert ExceptionAction.Silent.ordinal() == 0; int index = Math.max(ordinal() - 1, 0); return values()[index]; } } private final DiagnosticsOutputDirectory outputDirectory; private final Map<ExceptionAction, Integer> problemsHandledPerAction;
Params:
  • outputDirectory – object used to access a directory for dumping if the compilation is re-executed
  • problemsHandledPerAction – map used to count the number of compilation failures or bailouts handled by each action. This is provided by the caller as it is expected to be shared between instances of CompilationWrapper.
/** * @param outputDirectory object used to access a directory for dumping if the compilation is * re-executed * @param problemsHandledPerAction map used to count the number of compilation failures or * bailouts handled by each action. This is provided by the caller as it is expected * to be shared between instances of {@link CompilationWrapper}. */
public CompilationWrapper(DiagnosticsOutputDirectory outputDirectory, Map<ExceptionAction, Integer> problemsHandledPerAction) { this.outputDirectory = outputDirectory; this.problemsHandledPerAction = problemsHandledPerAction; }
Handles an uncaught exception.
Params:
Returns:a value representing the result of a failed compilation (may be null)
/** * Handles an uncaught exception. * * @param t an exception thrown during {@link #run(DebugContext)} * @return a value representing the result of a failed compilation (may be {@code null}) */
protected abstract T handleException(Throwable t);
Gets the action to take based on the value of actionKey in options. Subclasses can override this to choose a different action based on factors such as whether actionKey has been explicitly set in options for example.
Params:
  • cause – the cause of the bailout or failure
/** * Gets the action to take based on the value of {@code actionKey} in {@code options}. * * Subclasses can override this to choose a different action based on factors such as whether * {@code actionKey} has been explicitly set in {@code options} for example. * * @param cause the cause of the bailout or failure */
protected ExceptionAction lookupAction(OptionValues options, EnumOptionKey<ExceptionAction> actionKey, Throwable cause) { if (actionKey == CompilationFailureAction) { if (ExitVMOnException.getValue(options)) { assert CompilationFailureAction.getDefaultValue() != ExceptionAction.ExitVM; assert ExitVMOnException.getDefaultValue() != true; if (CompilationFailureAction.hasBeenSet(options) && CompilationFailureAction.getValue(options) != ExceptionAction.ExitVM) { TTY.printf("WARNING: Ignoring %s=%s since %s=true has been explicitly specified.%n", CompilationFailureAction.getName(), CompilationFailureAction.getValue(options), ExitVMOnException.getName()); } return ExceptionAction.ExitVM; } } return actionKey.getValue(options); }
Perform the compilation wrapped by this object.
Params:
  • debug – the debug context to use for the compilation
/** * Perform the compilation wrapped by this object. * * @param debug the debug context to use for the compilation */
protected abstract T performCompilation(DebugContext debug);
Gets a value that represents the input to the compilation.
/** * Gets a value that represents the input to the compilation. */
@Override public abstract String toString();
Creates the DebugContext to use when retrying a compilation.
Params:
  • options – the options for configuring the debug context
/** * Creates the {@link DebugContext} to use when retrying a compilation. * * @param options the options for configuring the debug context */
protected abstract DebugContext createRetryDebugContext(OptionValues options); @SuppressWarnings("try") public final T run(DebugContext initialDebug) { try { return performCompilation(initialDebug); } catch (Throwable cause) { OptionValues initialOptions = initialDebug.getOptions(); String causeType = "failure"; EnumOptionKey<ExceptionAction> actionKey; if (cause instanceof BailoutException) { actionKey = CompilationBailoutAction; causeType = "bailout"; } else { actionKey = CompilationFailureAction; causeType = "failure"; } ExceptionAction action = lookupAction(initialOptions, actionKey, cause); action = adjustAction(initialOptions, actionKey, action); if (action == ExceptionAction.Silent) { return handleException(cause); } if (action == ExceptionAction.Print) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (PrintStream ps = new PrintStream(baos)) { ps.printf("%s: Compilation of %s failed: ", Thread.currentThread(), this); cause.printStackTrace(ps); ps.printf("To disable compilation %s notifications, set %s to %s (e.g., -Dgraal.%s=%s).%n", causeType, actionKey.getName(), ExceptionAction.Silent, actionKey.getName(), ExceptionAction.Silent); ps.printf("To capture more information for diagnosing or reporting a compilation %s, " + "set %s to %s or %s (e.g., -Dgraal.%s=%s).%n", causeType, actionKey.getName(), ExceptionAction.Diagnose, ExceptionAction.ExitVM, actionKey.getName(), ExceptionAction.Diagnose); } synchronized (CompilationFailureAction) { // Synchronize to prevent compilation exception // messages from interleaving. TTY.println(baos.toString()); } return handleException(cause); } // action is Diagnose or ExitVM if (Dump.hasBeenSet(initialOptions)) { // If dumping is explicitly enabled, Graal is being debugged // so don't interfere with what the user is expecting to see. return handleException(cause); } String dir = this.outputDirectory.getPath(); if (dir == null) { return handleException(cause); } String dumpName = PathUtilities.sanitizeFileName(toString()); File dumpPath = new File(dir, dumpName); dumpPath.mkdirs(); if (!dumpPath.exists()) { TTY.println("Warning: could not create diagnostics directory " + dumpPath); return handleException(cause); } String message; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (PrintStream ps = new PrintStream(baos)) { ps.printf("%s: Compilation of %s failed: ", Thread.currentThread(), this); cause.printStackTrace(ps); ps.printf("To disable compilation %s notifications, set %s to %s (e.g., -Dgraal.%s=%s).%n", causeType, actionKey.getName(), ExceptionAction.Silent, actionKey.getName(), ExceptionAction.Silent); ps.printf("To print a message for a compilation %s without retrying the compilation, " + "set %s to %s (e.g., -Dgraal.%s=%s).%n", causeType, actionKey.getName(), ExceptionAction.Print, actionKey.getName(), ExceptionAction.Print); ps.println("Retrying compilation of " + this); message = baos.toString(); } synchronized (CompilationFailureAction) { // Synchronize here to serialize retry compilations. This // mitigates retry compilation storms. TTY.println(message); File retryLogFile = new File(dumpPath, "retry.log"); try (PrintStream ps = new PrintStream(new FileOutputStream(retryLogFile))) { ps.print(message); } catch (IOException ioe) { TTY.printf("Error writing to %s: %s%n", retryLogFile, ioe); } OptionValues retryOptions = new OptionValues(initialOptions, Dump, ":" + VERBOSE_LEVEL, MethodFilter, null, DumpPath, dumpPath.getPath()); try (DebugContext retryDebug = createRetryDebugContext(retryOptions)) { T res = performCompilation(retryDebug); maybeExitVM(action); return res; } catch (Throwable ignore) { // Failures during retry are silent T res = handleException(cause); maybeExitVM(action); return res; } } } } private void maybeExitVM(ExceptionAction action) { if (action == ExitVM) { synchronized (ExceptionAction.class) { try { // Give other compiler threads a chance to flush // error handling output. ExceptionAction.class.wait(2000); } catch (InterruptedException e) { } TTY.println("Exiting VM after retry compilation of " + this); System.exit(-1); } } }
Adjusts initialAction if necessary based on GraalCompilerOptions.MaxCompilationProblemsPerAction.
/** * Adjusts {@code initialAction} if necessary based on * {@link GraalCompilerOptions#MaxCompilationProblemsPerAction}. */
private ExceptionAction adjustAction(OptionValues initialOptions, EnumOptionKey<ExceptionAction> actionKey, ExceptionAction initialAction) { ExceptionAction action = initialAction; int maxProblems = MaxCompilationProblemsPerAction.getValue(initialOptions); synchronized (problemsHandledPerAction) { while (action != ExceptionAction.Silent) { int problems = problemsHandledPerAction.getOrDefault(action, 0); if (problems >= maxProblems) { if (problems == maxProblems) { TTY.printf("Warning: adjusting %s from %s to %s after %s (%d) failed compilations%n", actionKey, action, action.quieter(), MaxCompilationProblemsPerAction, maxProblems); // Ensure that the message above is only printed once problemsHandledPerAction.put(action, problems + 1); } action = action.quieter(); } else { break; } } problemsHandledPerAction.put(action, problemsHandledPerAction.getOrDefault(action, 0) + 1); } return action; } }