| //===- 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 = static_cast<void *>(const_cast<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 = static_cast<void *>(const_cast<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); | |
| } |