River Riddle ff6e7cf558 Introduce a new API for emitting diagnostics with Diagnostic and InFlightDiagnostic.
The Diagnostic class contains all of the information necessary to report a diagnostic to the DiagnosticEngine. It should generally not be constructed directly, and instead used transitively via InFlightDiagnostic. A diagnostic is currently comprised of several different elements:
    * A severity level.
    * A source Location.
    * A list of DiagnosticArguments that help compose and comprise the output message.
      * A DiagnosticArgument represents any value that may be part of the diagnostic, e.g. string, integer, Type, Attribute, etc.
      * Arguments can be added to the diagnostic via the stream(<<) operator.
    * (In a future cl) A list of attached notes.
      * These are in the form of other diagnostics that provide supplemental information to the main diagnostic, but do not have context on their own.

    The InFlightDiagnostic class represents an RAII wrapper around a Diagnostic that is set to be reported with the diagnostic engine. This allows for the user to modify a diagnostic that is inflight. The internally wrapped diagnostic can be reported directly or automatically upon destruction.

    These classes allow for more natural composition of diagnostics by removing the restriction that the message of a diagnostic is comprised of a single Twine. They should also allow for nice incremental improvements to the diagnostics experience in the future, e.g. formatv style diagnostics.

    Simple Example:

    emitError(loc, "integer bitwidth is limited to " + Twine(IntegerType::kMaxWidth) + " bits");
    emitError(loc) << "integer bitwidth is limited to " << IntegerType::kMaxWidth << " bits";

--

PiperOrigin-RevId: 246526439
2019-05-06 08:26:34 -07:00

576 lines
20 KiB
C++

//===- Pass.cpp - Pass infrastructure implementation ----------------------===//
//
// Copyright 2019 The MLIR Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// =============================================================================
//
// This file implements common pass infrastructure.
//
//===----------------------------------------------------------------------===//
#include "mlir/Pass/Pass.h"
#include "PassDetail.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Module.h"
#include "mlir/Pass/PassManager.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Threading.h"
using namespace mlir;
using namespace mlir::detail;
static llvm::cl::opt<bool>
disableThreads("disable-pass-threading",
llvm::cl::desc("Disable multithreading in the pass manager"),
llvm::cl::init(false));
//===----------------------------------------------------------------------===//
// Pass
//===----------------------------------------------------------------------===//
/// Out of line virtual method to ensure vtables and metadata are emitted to a
/// single .o file.
void Pass::anchor() {}
/// Forwarding function to execute this pass.
LogicalResult FunctionPassBase::run(Function *fn,
FunctionAnalysisManager &fam) {
// Initialize the pass state.
passState.emplace(fn, fam);
// Instrument before the pass has run.
auto pi = fam.getPassInstrumentor();
if (pi)
pi->runBeforePass(this, fn);
// Invoke the virtual runOnFunction function.
runOnFunction();
// Invalidate any non preserved analyses.
fam.invalidate(passState->preservedAnalyses);
// Instrument after the pass has run.
bool passFailed = passState->irAndPassFailed.getInt();
if (pi) {
if (passFailed)
pi->runAfterPassFailed(this, fn);
else
pi->runAfterPass(this, fn);
}
// Return if the pass signaled a failure.
return failure(passFailed);
}
/// Forwarding function to execute this pass.
LogicalResult ModulePassBase::run(Module *module, ModuleAnalysisManager &mam) {
// Initialize the pass state.
passState.emplace(module, mam);
// Instrument before the pass has run.
auto pi = mam.getPassInstrumentor();
if (pi)
pi->runBeforePass(this, module);
// Invoke the virtual runOnModule function.
runOnModule();
// Invalidate any non preserved analyses.
mam.invalidate(passState->preservedAnalyses);
// Instrument after the pass has run.
bool passFailed = passState->irAndPassFailed.getInt();
if (pi) {
if (passFailed)
pi->runAfterPassFailed(this, module);
else
pi->runAfterPass(this, module);
}
// Return if the pass signaled a failure.
return failure(passFailed);
}
//===----------------------------------------------------------------------===//
// PassExecutor
//===----------------------------------------------------------------------===//
FunctionPassExecutor::FunctionPassExecutor(const FunctionPassExecutor &rhs)
: PassExecutor(Kind::FunctionExecutor) {
for (auto &pass : rhs.passes)
addPass(pass->clone());
}
/// Run all of the passes in this manager over the current function.
LogicalResult detail::FunctionPassExecutor::run(Function *function,
FunctionAnalysisManager &fam) {
// Run each of the held passes.
for (auto &pass : passes)
if (failed(pass->run(function, fam)))
return failure();
return success();
}
/// Run all of the passes in this manager over the current module.
LogicalResult detail::ModulePassExecutor::run(Module *module,
ModuleAnalysisManager &mam) {
// Run each of the held passes.
for (auto &pass : passes)
if (failed(pass->run(module, mam)))
return failure();
return success();
}
//===----------------------------------------------------------------------===//
// ModuleToFunctionPassAdaptor
//===----------------------------------------------------------------------===//
/// Utility to run the given function and analysis manager on a provided
/// function pass executor.
static LogicalResult runFunctionPipeline(FunctionPassExecutor &fpe,
Function *func,
FunctionAnalysisManager &fam) {
// Run the function pipeline over the provided function.
auto result = fpe.run(func, fam);
// Clear out any computed function analyses. These analyses won't be used
// any more in this pipeline, and this helps reduce the current working set
// of memory. If preserving these analyses becomes important in the future
// we can re-evalutate this.
fam.clear();
return result;
}
/// Run the held function pipeline over all non-external functions within the
/// module.
void ModuleToFunctionPassAdaptor::runOnModule() {
ModuleAnalysisManager &mam = getAnalysisManager();
for (auto &func : getModule()) {
// Skip external functions.
if (func.isExternal())
continue;
// Run the held function pipeline over the current function.
auto fam = mam.slice(&func);
if (failed(runFunctionPipeline(fpe, &func, fam)))
return signalPassFailure();
// Clear out any computed function analyses. These analyses won't be used
// any more in this pipeline, and this helps reduce the current working set
// of memory. If preserving these analyses becomes important in the future
// we can re-evalutate this.
fam.clear();
}
}
namespace {
/// A utility class to ensure that diagnostics are emitted in a deterministic
/// order when executing a pipeline asynchronously with
/// ModuleToFunctionPassAdaptorParallel.
struct ParallelDiagnosticHandler {
struct ThreadDiagnostic {
ThreadDiagnostic(size_t id, Location loc, StringRef msg,
DiagnosticSeverity kind)
: id(id), loc(loc), msg(msg), kind(kind) {}
bool operator<(const ThreadDiagnostic &rhs) const { return id < rhs.id; }
/// The function id for this diagnostic, this is used for ordering.
/// Note: This id corresponds to the ordered position of the current
/// function within its parent module.
size_t id;
/// Information for the diagnostic.
Location loc;
std::string msg;
DiagnosticSeverity kind;
};
ParallelDiagnosticHandler(MLIRContext &ctx)
: prevHandler(ctx.getDiagEngine().getHandler()), context(ctx) {
ctx.getDiagEngine().setHandler(
[this](Location loc, StringRef message, DiagnosticSeverity kind) {
uint64_t tid = llvm::get_threadid();
llvm::sys::SmartScopedLock<true> lock(mutex);
// Append a new diagnostic.
diagnostics.emplace_back(threadToFuncID[tid], loc, message, kind);
});
}
~ParallelDiagnosticHandler() {
// Restore the previous diagnostic handler.
context.getDiagEngine().setHandler(prevHandler);
// Early exit if there are no diagnostics, this is the common case.
if (diagnostics.empty())
return;
// Emit the diagnostics back to the context.
emitDiagnostics(
[&](Location loc, StringRef message, DiagnosticSeverity kind) {
return context.getDiagEngine().emit(loc, kind) << message;
});
}
/// Utility method to emit any held diagnostics.
void emitDiagnostics(
std::function<void(Location, StringRef, DiagnosticSeverity)> emitFn) {
// Stable sort all of the diagnostics that were emitted. This creates a
// deterministic ordering for the diagnostics based upon which function they
// were emitted for.
std::stable_sort(diagnostics.begin(), diagnostics.end());
// Emit each diagnostic to the context again.
for (ThreadDiagnostic &diag : diagnostics)
emitFn(diag.loc, diag.msg, diag.kind);
}
/// Set the function id for the current thread.
void setFuncIDForThread(size_t funcID) {
uint64_t tid = llvm::get_threadid();
llvm::sys::SmartScopedLock<true> lock(mutex);
threadToFuncID[tid] = funcID;
}
/// The previous context diagnostic handler.
DiagnosticEngine::HandlerTy prevHandler;
/// A smart mutex to lock access to the internal state.
llvm::sys::SmartMutex<true> mutex;
/// A mapping between the thread id and the current function id.
DenseMap<uint64_t, size_t> threadToFuncID;
/// An unordered list of diagnostics that were emitted.
std::vector<ThreadDiagnostic> diagnostics;
/// The context to emit the diagnostics to.
MLIRContext &context;
};
/// A utility stack trace entry that dumps any dangling diagnostics held by a
/// ParallelDiagnosticHandler in the event of a crash.
struct PrettyStackTraceParallelDiagnosticEntry
: public llvm::PrettyStackTraceEntry {
PrettyStackTraceParallelDiagnosticEntry(
ParallelDiagnosticHandler &parallelHandler)
: parallelHandler(parallelHandler) {}
void print(raw_ostream &os) const override {
// Early exit if there are no diagnostics, this is the common case.
if (parallelHandler.diagnostics.empty())
return;
os << "In-Flight Diagnostics:\n";
parallelHandler.emitDiagnostics(
[&](Location loc, StringRef message, DiagnosticSeverity severity) {
os.indent(4);
// Print each diagnostic with the format:
// "<location>: <kind>: <msg>"
if (!loc.isa<UnknownLoc>())
os << loc << ": ";
switch (severity) {
case DiagnosticSeverity::Error:
os << "error: ";
break;
case DiagnosticSeverity::Warning:
os << "warning: ";
break;
case DiagnosticSeverity::Note:
os << "note: ";
break;
case DiagnosticSeverity::Remark:
os << "remark: ";
break;
}
os << message << '\n';
});
}
// A reference to the parallel handler to dump on the event of a crash.
ParallelDiagnosticHandler &parallelHandler;
};
} // end anonymous namespace
// Run the held function pipeline synchronously across the functions within
// the module.
void ModuleToFunctionPassAdaptorParallel::runOnModule() {
ModuleAnalysisManager &mam = getAnalysisManager();
// Create the async executors if they haven't been created, or if the main
// function pipeline has changed.
if (asyncExecutors.empty() || asyncExecutors.front().size() != fpe.size())
asyncExecutors = {llvm::hardware_concurrency(), fpe};
// Run a prepass over the module to collect the functions to execute a over.
// This ensures that an analysis manager exists for each function, as well as
// providing a queue of functions to execute over.
std::vector<std::pair<Function *, FunctionAnalysisManager>> funcAMPairs;
for (auto &func : getModule())
if (!func.isExternal())
funcAMPairs.emplace_back(&func, mam.slice(&func));
// A parallel diagnostic handler that provides deterministic diagnostic
// ordering.
ParallelDiagnosticHandler diagHandler(getContext());
// A pretty stack entry to print any dangling diagnostics in the event of a
// crash.
PrettyStackTraceParallelDiagnosticEntry diagCrashEntry(diagHandler);
// An index for the current function/analysis manager pair.
std::atomic<unsigned> funcIt(0);
// An atomic failure variable for the async executors.
std::atomic<bool> passFailed(false);
llvm::parallel::for_each(
llvm::parallel::par, asyncExecutors.begin(),
std::next(asyncExecutors.begin(),
std::min(asyncExecutors.size(), funcAMPairs.size())),
[&](FunctionPassExecutor &executor) {
for (auto e = funcAMPairs.size(); !passFailed && funcIt < e;) {
// Get the next available function index.
unsigned nextID = funcIt++;
if (nextID >= e)
break;
// Set the function id for this thread in the diagnostic handler.
diagHandler.setFuncIDForThread(nextID);
// Run the executor over the current function.
auto &it = funcAMPairs[nextID];
if (failed(runFunctionPipeline(executor, it.first, it.second))) {
passFailed = true;
break;
}
}
});
// Signal a failure if any of the executors failed.
if (passFailed)
signalPassFailure();
}
//===----------------------------------------------------------------------===//
// PassManager
//===----------------------------------------------------------------------===//
namespace {
/// Pass to verify a function and signal failure if necessary.
class FunctionVerifier : public FunctionPass<FunctionVerifier> {
void runOnFunction() {
if (failed(getFunction().verify()))
signalPassFailure();
markAllAnalysesPreserved();
}
};
/// Pass to verify a module and signal failure if necessary.
class ModuleVerifier : public ModulePass<ModuleVerifier> {
void runOnModule() {
if (failed(getModule().verify()))
signalPassFailure();
markAllAnalysesPreserved();
}
};
} // end anonymous namespace
PassManager::PassManager(bool verifyPasses)
: mpe(new ModulePassExecutor()), verifyPasses(verifyPasses),
passTiming(false) {}
PassManager::~PassManager() {}
/// Run the passes within this manager on the provided module.
LogicalResult PassManager::run(Module *module) {
ModuleAnalysisManager mam(module, instrumentor.get());
return mpe->run(module, mam);
}
/// Add an opaque pass pointer to the current manager. This takes ownership
/// over the provided pass pointer.
void PassManager::addPass(Pass *pass) {
switch (pass->getKind()) {
case Pass::Kind::FunctionPass:
addPass(cast<FunctionPassBase>(pass));
break;
case Pass::Kind::ModulePass:
addPass(cast<ModulePassBase>(pass));
break;
}
}
/// Add a module pass to the current manager. This takes ownership over the
/// provided pass pointer.
void PassManager::addPass(ModulePassBase *pass) {
nestedExecutorStack.clear();
mpe->addPass(pass);
// Add a verifier run if requested.
if (verifyPasses)
mpe->addPass(new ModuleVerifier());
}
/// Add a function pass to the current manager. This takes ownership over the
/// provided pass pointer. This will automatically create a function pass
/// executor if necessary.
void PassManager::addPass(FunctionPassBase *pass) {
detail::FunctionPassExecutor *fpe;
if (nestedExecutorStack.empty()) {
/// Create an executor adaptor for this pass.
if (disableThreads || !llvm::llvm_is_multithreaded()) {
// If multi-threading is disabled, then create a synchronous adaptor.
auto *adaptor = new ModuleToFunctionPassAdaptor();
addPass(adaptor);
fpe = &adaptor->getFunctionExecutor();
} else {
auto *adaptor = new ModuleToFunctionPassAdaptorParallel();
addPass(adaptor);
fpe = &adaptor->getFunctionExecutor();
}
/// Add the executor to the stack.
nestedExecutorStack.push_back(fpe);
} else {
fpe = cast<detail::FunctionPassExecutor>(nestedExecutorStack.back());
}
fpe->addPass(pass);
// Add a verifier run if requested.
if (verifyPasses)
fpe->addPass(new FunctionVerifier());
}
/// Add the provided instrumentation to the pass manager. This takes ownership
/// over the given pointer.
void PassManager::addInstrumentation(PassInstrumentation *pi) {
if (!instrumentor)
instrumentor.reset(new PassInstrumentor());
instrumentor->addInstrumentation(pi);
}
//===----------------------------------------------------------------------===//
// AnalysisManager
//===----------------------------------------------------------------------===//
/// Returns a pass instrumentation object for the current function.
PassInstrumentor *FunctionAnalysisManager::getPassInstrumentor() const {
return parent->getPassInstrumentor();
}
/// Create an analysis slice for the given child function.
FunctionAnalysisManager ModuleAnalysisManager::slice(Function *function) {
assert(function->getModule() == moduleAnalyses.getIRUnit() &&
"function has a different parent module");
auto it = functionAnalyses.try_emplace(function, function);
return {this, &it.first->second};
}
/// Invalidate any non preserved analyses.
void ModuleAnalysisManager::invalidate(const detail::PreservedAnalyses &pa) {
// If all analyses were preserved, then there is nothing to do here.
if (pa.isAll())
return;
// Invalidate the module analyses directly.
moduleAnalyses.invalidate(pa);
// If no analyses were preserved, then just simply clear out the function
// analysis results.
if (pa.isNone()) {
functionAnalyses.clear();
return;
}
// Otherwise, invalidate each function analyses.
for (auto &analysisPair : functionAnalyses)
analysisPair.second.invalidate(pa);
}
//===----------------------------------------------------------------------===//
// PassInstrumentation
//===----------------------------------------------------------------------===//
PassInstrumentation::~PassInstrumentation() {}
//===----------------------------------------------------------------------===//
// PassInstrumentor
//===----------------------------------------------------------------------===//
namespace mlir {
namespace detail {
struct PassInstrumentorImpl {
/// Mutex to keep instrumentation access thread-safe.
llvm::sys::SmartMutex<true> mutex;
/// Set of registered instrumentations.
std::vector<std::unique_ptr<PassInstrumentation>> instrumentations;
};
} // end namespace detail
} // end namespace mlir
PassInstrumentor::PassInstrumentor() : impl(new PassInstrumentorImpl()) {}
PassInstrumentor::~PassInstrumentor() {}
/// See PassInstrumentation::runBeforePass for details.
void PassInstrumentor::runBeforePass(Pass *pass, const llvm::Any &ir) {
llvm::sys::SmartScopedLock<true> instrumentationLock(impl->mutex);
for (auto &instr : impl->instrumentations)
instr->runBeforePass(pass, ir);
}
/// See PassInstrumentation::runAfterPass for details.
void PassInstrumentor::runAfterPass(Pass *pass, const llvm::Any &ir) {
llvm::sys::SmartScopedLock<true> instrumentationLock(impl->mutex);
for (auto &instr : llvm::reverse(impl->instrumentations))
instr->runAfterPass(pass, ir);
}
/// See PassInstrumentation::runAfterPassFailed for details.
void PassInstrumentor::runAfterPassFailed(Pass *pass, const llvm::Any &ir) {
llvm::sys::SmartScopedLock<true> instrumentationLock(impl->mutex);
for (auto &instr : llvm::reverse(impl->instrumentations))
instr->runAfterPassFailed(pass, ir);
}
/// See PassInstrumentation::runBeforeAnalysis for details.
void PassInstrumentor::runBeforeAnalysis(llvm::StringRef name, AnalysisID *id,
const llvm::Any &ir) {
llvm::sys::SmartScopedLock<true> instrumentationLock(impl->mutex);
for (auto &instr : impl->instrumentations)
instr->runBeforeAnalysis(name, id, ir);
}
/// See PassInstrumentation::runAfterAnalysis for details.
void PassInstrumentor::runAfterAnalysis(llvm::StringRef name, AnalysisID *id,
const llvm::Any &ir) {
llvm::sys::SmartScopedLock<true> instrumentationLock(impl->mutex);
for (auto &instr : llvm::reverse(impl->instrumentations))
instr->runAfterAnalysis(name, id, ir);
}
/// Add the given instrumentation to the collection. This takes ownership over
/// the given pointer.
void PassInstrumentor::addInstrumentation(PassInstrumentation *pi) {
llvm::sys::SmartScopedLock<true> instrumentationLock(impl->mutex);
impl->instrumentations.emplace_back(pi);
}
constexpr AnalysisID mlir::detail::PreservedAnalyses::allAnalysesID;