//===- 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 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(controlOption); } void mlirDebuggerPrintContext() { DebuggerState &state = getGlobalDebuggerState(); if (!state.actionActiveStack) { llvm::outs() << "No active action.\n"; return; } const ArrayRef &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 units = state.actionActiveStack->getAction().getContextIRUnits(); if (index < 0 || index >= static_cast(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(*unit)) { state.cursor = op->getBlock(); } else if (auto *region = llvm::dyn_cast_if_present(*unit)) { state.cursor = region->getParentOp(); } else if (auto *block = llvm::dyn_cast_if_present(*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(*unit)) { if (index < 0 || index >= static_cast(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(*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(*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(*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(*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(*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(*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(*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(*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)->enable(); } void mlirDebuggerDisableBreakpoint(BreakpointHandle breakpoint) { reinterpret_cast(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(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); }