370 lines
12 KiB
C++
370 lines
12 KiB
C++
|
//===- DebuggerExecutionContextHook.cpp - Debugger Support ----------------===//
|
||
|
//
|
||
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||
|
// See https://llvm.org/LICENSE.txt for license information.
|
||
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||
|
//
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
#include "mlir/Debug/DebuggerExecutionContextHook.h"
|
||
|
|
||
|
#include "mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h"
|
||
|
#include "mlir/Debug/BreakpointManagers/TagBreakpointManager.h"
|
||
|
|
||
|
using namespace mlir;
|
||
|
using namespace mlir::tracing;
|
||
|
|
||
|
namespace {
|
||
|
/// This structure tracks the state of the interactive debugger.
|
||
|
struct DebuggerState {
|
||
|
/// This variable keeps track of the current control option. This is set by
|
||
|
/// the debugger when control is handed over to it.
|
||
|
ExecutionContext::Control debuggerControl = ExecutionContext::Apply;
|
||
|
|
||
|
/// The breakpoint manager that allows the debugger to set breakpoints on
|
||
|
/// action tags.
|
||
|
TagBreakpointManager tagBreakpointManager;
|
||
|
|
||
|
/// The breakpoint manager that allows the debugger to set breakpoints on
|
||
|
/// FileLineColLoc locations.
|
||
|
FileLineColLocBreakpointManager fileLineColLocBreakpointManager;
|
||
|
|
||
|
/// Map of breakpoint IDs to breakpoint objects.
|
||
|
DenseMap<unsigned, Breakpoint *> breakpointIdsMap;
|
||
|
|
||
|
/// The current stack of actiive actions.
|
||
|
const tracing::ActionActiveStack *actionActiveStack;
|
||
|
|
||
|
/// This is a "cursor" in the IR, it is used for the debugger to navigate the
|
||
|
/// IR associated to the actions.
|
||
|
IRUnit cursor;
|
||
|
};
|
||
|
} // namespace
|
||
|
|
||
|
static DebuggerState &getGlobalDebuggerState() {
|
||
|
static LLVM_THREAD_LOCAL DebuggerState debuggerState;
|
||
|
return debuggerState;
|
||
|
}
|
||
|
|
||
|
extern "C" {
|
||
|
void mlirDebuggerSetControl(int controlOption) {
|
||
|
getGlobalDebuggerState().debuggerControl =
|
||
|
static_cast<ExecutionContext::Control>(controlOption);
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerPrintContext() {
|
||
|
DebuggerState &state = getGlobalDebuggerState();
|
||
|
if (!state.actionActiveStack) {
|
||
|
llvm::outs() << "No active action.\n";
|
||
|
return;
|
||
|
}
|
||
|
const ArrayRef<IRUnit> &units =
|
||
|
state.actionActiveStack->getAction().getContextIRUnits();
|
||
|
llvm::outs() << units.size() << " available IRUnits:\n";
|
||
|
for (const IRUnit &unit : units) {
|
||
|
llvm::outs() << " - ";
|
||
|
unit.print(
|
||
|
llvm::outs(),
|
||
|
OpPrintingFlags().useLocalScope().skipRegions().enableDebugInfo());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerPrintActionBacktrace(bool withContext) {
|
||
|
DebuggerState &state = getGlobalDebuggerState();
|
||
|
if (!state.actionActiveStack) {
|
||
|
llvm::outs() << "No active action.\n";
|
||
|
return;
|
||
|
}
|
||
|
state.actionActiveStack->print(llvm::outs(), withContext);
|
||
|
}
|
||
|
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
// Cursor Management
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
void mlirDebuggerCursorPrint(bool withRegion) {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
if (!state.cursor) {
|
||
|
llvm::outs() << "No active MLIR cursor, select from the context first\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor.print(llvm::outs(), OpPrintingFlags()
|
||
|
.skipRegions(!withRegion)
|
||
|
.useLocalScope()
|
||
|
.enableDebugInfo());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerCursorSelectIRUnitFromContext(int index) {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
if (!state.actionActiveStack) {
|
||
|
llvm::outs() << "No active MLIR Action stack\n";
|
||
|
return;
|
||
|
}
|
||
|
ArrayRef<IRUnit> units =
|
||
|
state.actionActiveStack->getAction().getContextIRUnits();
|
||
|
if (index < 0 || index >= static_cast<int>(units.size())) {
|
||
|
llvm::outs() << "Index invalid, bounds: [0, " << units.size()
|
||
|
<< "] but got " << index << "\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = units[index];
|
||
|
state.cursor.print(llvm::outs());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerCursorSelectParentIRUnit() {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
if (!state.cursor) {
|
||
|
llvm::outs() << "No active MLIR cursor, select from the context first\n";
|
||
|
return;
|
||
|
}
|
||
|
IRUnit *unit = &state.cursor;
|
||
|
if (auto *op = llvm::dyn_cast_if_present<Operation *>(*unit)) {
|
||
|
state.cursor = op->getBlock();
|
||
|
} else if (auto *region = llvm::dyn_cast_if_present<Region *>(*unit)) {
|
||
|
state.cursor = region->getParentOp();
|
||
|
} else if (auto *block = llvm::dyn_cast_if_present<Block *>(*unit)) {
|
||
|
state.cursor = block->getParent();
|
||
|
} else {
|
||
|
llvm::outs() << "Current cursor is not a valid IRUnit";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor.print(llvm::outs());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerCursorSelectChildIRUnit(int index) {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
if (!state.cursor) {
|
||
|
llvm::outs() << "No active MLIR cursor, select from the context first\n";
|
||
|
return;
|
||
|
}
|
||
|
IRUnit *unit = &state.cursor;
|
||
|
if (auto *op = llvm::dyn_cast_if_present<Operation *>(*unit)) {
|
||
|
if (index < 0 || index >= static_cast<int>(op->getNumRegions())) {
|
||
|
llvm::outs() << "Index invalid, op has " << op->getNumRegions()
|
||
|
<< " but got " << index << "\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = &op->getRegion(index);
|
||
|
} else if (auto *region = llvm::dyn_cast_if_present<Region *>(*unit)) {
|
||
|
auto block = region->begin();
|
||
|
int count = 0;
|
||
|
while (block != region->end() && count != index) {
|
||
|
++block;
|
||
|
++count;
|
||
|
}
|
||
|
|
||
|
if (block == region->end()) {
|
||
|
llvm::outs() << "Index invalid, region has " << count << " block but got "
|
||
|
<< index << "\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = &*block;
|
||
|
} else if (auto *block = llvm::dyn_cast_if_present<Block *>(*unit)) {
|
||
|
auto op = block->begin();
|
||
|
int count = 0;
|
||
|
while (op != block->end() && count != index) {
|
||
|
++op;
|
||
|
++count;
|
||
|
}
|
||
|
|
||
|
if (op == block->end()) {
|
||
|
llvm::outs() << "Index invalid, block has " << count
|
||
|
<< "operations but got " << index << "\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = &*op;
|
||
|
} else {
|
||
|
llvm::outs() << "Current cursor is not a valid IRUnit";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor.print(llvm::outs());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerCursorSelectPreviousIRUnit() {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
if (!state.cursor) {
|
||
|
llvm::outs() << "No active MLIR cursor, select from the context first\n";
|
||
|
return;
|
||
|
}
|
||
|
IRUnit *unit = &state.cursor;
|
||
|
if (auto *op = llvm::dyn_cast_if_present<Operation *>(*unit)) {
|
||
|
Operation *previous = op->getPrevNode();
|
||
|
if (!previous) {
|
||
|
llvm::outs() << "No previous operation in the current block\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = previous;
|
||
|
} else if (auto *region = llvm::dyn_cast_if_present<Region *>(*unit)) {
|
||
|
llvm::outs() << "Has region\n";
|
||
|
Operation *parent = region->getParentOp();
|
||
|
if (!parent) {
|
||
|
llvm::outs() << "No parent operation for the current region\n";
|
||
|
return;
|
||
|
}
|
||
|
if (region->getRegionNumber() == 0) {
|
||
|
llvm::outs() << "No previous region in the current operation\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor =
|
||
|
®ion->getParentOp()->getRegion(region->getRegionNumber() - 1);
|
||
|
} else if (auto *block = llvm::dyn_cast_if_present<Block *>(*unit)) {
|
||
|
Block *previous = block->getPrevNode();
|
||
|
if (!previous) {
|
||
|
llvm::outs() << "No previous block in the current region\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = previous;
|
||
|
} else {
|
||
|
llvm::outs() << "Current cursor is not a valid IRUnit";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor.print(llvm::outs());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerCursorSelectNextIRUnit() {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
if (!state.cursor) {
|
||
|
llvm::outs() << "No active MLIR cursor, select from the context first\n";
|
||
|
return;
|
||
|
}
|
||
|
IRUnit *unit = &state.cursor;
|
||
|
if (auto *op = llvm::dyn_cast_if_present<Operation *>(*unit)) {
|
||
|
Operation *next = op->getNextNode();
|
||
|
if (!next) {
|
||
|
llvm::outs() << "No next operation in the current block\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = next;
|
||
|
} else if (auto *region = llvm::dyn_cast_if_present<Region *>(*unit)) {
|
||
|
Operation *parent = region->getParentOp();
|
||
|
if (!parent) {
|
||
|
llvm::outs() << "No parent operation for the current region\n";
|
||
|
return;
|
||
|
}
|
||
|
if (region->getRegionNumber() == parent->getNumRegions() - 1) {
|
||
|
llvm::outs() << "No next region in the current operation\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor =
|
||
|
®ion->getParentOp()->getRegion(region->getRegionNumber() + 1);
|
||
|
} else if (auto *block = llvm::dyn_cast_if_present<Block *>(*unit)) {
|
||
|
Block *next = block->getNextNode();
|
||
|
if (!next) {
|
||
|
llvm::outs() << "No next block in the current region\n";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor = next;
|
||
|
} else {
|
||
|
llvm::outs() << "Current cursor is not a valid IRUnit";
|
||
|
return;
|
||
|
}
|
||
|
state.cursor.print(llvm::outs());
|
||
|
llvm::outs() << "\n";
|
||
|
}
|
||
|
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
// Breakpoint Management
|
||
|
//===----------------------------------------------------------------------===//
|
||
|
|
||
|
void mlirDebuggerEnableBreakpoint(BreakpointHandle breakpoint) {
|
||
|
reinterpret_cast<Breakpoint *>(breakpoint)->enable();
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerDisableBreakpoint(BreakpointHandle breakpoint) {
|
||
|
reinterpret_cast<Breakpoint *>(breakpoint)->disable();
|
||
|
}
|
||
|
|
||
|
BreakpointHandle mlirDebuggerAddTagBreakpoint(const char *tag) {
|
||
|
DebuggerState &state = getGlobalDebuggerState();
|
||
|
Breakpoint *breakpoint =
|
||
|
state.tagBreakpointManager.addBreakpoint(StringRef(tag, strlen(tag)));
|
||
|
int breakpointId = state.breakpointIdsMap.size() + 1;
|
||
|
state.breakpointIdsMap[breakpointId] = breakpoint;
|
||
|
return reinterpret_cast<BreakpointHandle>(breakpoint);
|
||
|
}
|
||
|
|
||
|
void mlirDebuggerAddRewritePatternBreakpoint(const char *patternNameInfo) {}
|
||
|
|
||
|
void mlirDebuggerAddFileLineColLocBreakpoint(const char *file, int line,
|
||
|
int col) {
|
||
|
getGlobalDebuggerState().fileLineColLocBreakpointManager.addBreakpoint(
|
||
|
StringRef(file, strlen(file)), line, col);
|
||
|
}
|
||
|
|
||
|
} // extern "C"
|
||
|
|
||
|
LLVM_ATTRIBUTE_NOINLINE void mlirDebuggerBreakpointHook() {
|
||
|
static LLVM_THREAD_LOCAL void *volatile sink;
|
||
|
sink = (void *)&sink;
|
||
|
}
|
||
|
|
||
|
static void preventLinkerDeadCodeElim() {
|
||
|
static void *volatile sink;
|
||
|
static bool initialized = [&]() {
|
||
|
sink = (void *)mlirDebuggerSetControl;
|
||
|
sink = (void *)mlirDebuggerEnableBreakpoint;
|
||
|
sink = (void *)mlirDebuggerDisableBreakpoint;
|
||
|
sink = (void *)mlirDebuggerPrintContext;
|
||
|
sink = (void *)mlirDebuggerPrintActionBacktrace;
|
||
|
sink = (void *)mlirDebuggerCursorPrint;
|
||
|
sink = (void *)mlirDebuggerCursorSelectIRUnitFromContext;
|
||
|
sink = (void *)mlirDebuggerCursorSelectParentIRUnit;
|
||
|
sink = (void *)mlirDebuggerCursorSelectChildIRUnit;
|
||
|
sink = (void *)mlirDebuggerCursorSelectPreviousIRUnit;
|
||
|
sink = (void *)mlirDebuggerCursorSelectNextIRUnit;
|
||
|
sink = (void *)mlirDebuggerAddTagBreakpoint;
|
||
|
sink = (void *)mlirDebuggerAddRewritePatternBreakpoint;
|
||
|
sink = (void *)mlirDebuggerAddFileLineColLocBreakpoint;
|
||
|
sink = (void *)&sink;
|
||
|
return true;
|
||
|
}();
|
||
|
(void)initialized;
|
||
|
}
|
||
|
|
||
|
static tracing::ExecutionContext::Control
|
||
|
debuggerCallBackFunction(const tracing::ActionActiveStack *actionStack) {
|
||
|
preventLinkerDeadCodeElim();
|
||
|
// Invoke the breakpoint hook, the debugger is supposed to trap this.
|
||
|
// The debugger controls the execution from there by invoking
|
||
|
// `mlirDebuggerSetControl()`.
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
state.actionActiveStack = actionStack;
|
||
|
getGlobalDebuggerState().debuggerControl = ExecutionContext::Apply;
|
||
|
actionStack->getAction().print(llvm::outs());
|
||
|
llvm::outs() << "\n";
|
||
|
mlirDebuggerBreakpointHook();
|
||
|
return getGlobalDebuggerState().debuggerControl;
|
||
|
}
|
||
|
|
||
|
namespace {
|
||
|
/// Manage the stack of actions that are currently active.
|
||
|
class DebuggerObserver : public ExecutionContext::Observer {
|
||
|
void beforeExecute(const ActionActiveStack *action, Breakpoint *breakpoint,
|
||
|
bool willExecute) override {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
state.actionActiveStack = action;
|
||
|
}
|
||
|
void afterExecute(const ActionActiveStack *action) override {
|
||
|
auto &state = getGlobalDebuggerState();
|
||
|
state.actionActiveStack = action->getParent();
|
||
|
state.cursor = nullptr;
|
||
|
}
|
||
|
};
|
||
|
} // namespace
|
||
|
|
||
|
void mlir::setupDebuggerExecutionContextHook(
|
||
|
tracing::ExecutionContext &executionContext) {
|
||
|
executionContext.setCallback(debuggerCallBackFunction);
|
||
|
DebuggerState &state = getGlobalDebuggerState();
|
||
|
static DebuggerObserver observer;
|
||
|
executionContext.registerObserver(&observer);
|
||
|
executionContext.addBreakpointManager(&state.fileLineColLocBreakpointManager);
|
||
|
executionContext.addBreakpointManager(&state.tagBreakpointManager);
|
||
|
}
|