1# Debug Actions 2 3[TOC] 4 5This file documents the infrastructure for `Debug Actions`. This is a DEBUG only 6API that allows for external entities to control various aspects of compiler 7execution. This is conceptually similar to something like `DebugCounters` in 8LLVM, but at a lower level. This framework doesn't make any assumptions about 9how the higher level driver is controlling the execution, it merely provides a 10framework for connecting the two together. A high level overview of the workflow 11surrounding debug actions is shown below: 12 13* Compiler developer defines an [`action`](#debug-action) that is taken by the 14 a pass, transformation, utility that they are developing. 15* Depending on the needs, the developer dispatches various queries, pertaining 16 to this action, to an [`action manager`](#debug-action-manager) that will 17 provide an answer as to what behavior the action should take. 18* An external entity registers an [`action handler`](#debug-action-handler) 19 with the action manager, and provides the logic to resolve queries on 20 actions. 21 22The exact definition of an `external entity` is left opaque, to allow for more 23interesting handlers. The set of possible action queries is detailed in the 24[`action manager`](#debug-action-manager) section below. 25 26## Debug Action 27 28A `debug action` is essentially a marker for a type of action that may be 29performed within the compiler. There are no constraints on the granularity of an 30“action”, it can be as simple as “perform this fold” and as complex as “run this 31pass pipeline”. An action is comprised of the following: 32 33* Tag: 34 35 - A unique string identifier, similar to a command line flag or 36 DEBUG_TYPE. 37 38* Description: 39 40 - A short description of what the action represents. 41 42* Parameter Types: 43 44 - The types of values that are passed to queries related to this action, 45 to help guide decisions. 46 47Below is an example action that may be provided by the 48[pattern rewriter](PatternRewriter.md) framework to control the application of 49rewrite patterns. 50 51```c++ 52/// A debug action that allows for controlling the application of patterns. 53/// A new action type can be defined by inheriting from `DebugAction`. 54/// * The Tag is specified via a static `StringRef getTag()` method. 55/// * The Description is specified via a static `StringRef getDescription()` 56/// method. 57/// * The parameters for the action are provided via template parameters when 58/// inheriting from `DebugAction`. 59struct ApplyPatternAction 60 : public DebugAction<Operation *, const Pattern &> { 61 static StringRef getTag() { return "apply-pattern"; } 62 static StringRef getDescription() { 63 return "Control the application of rewrite patterns"; 64 } 65}; 66``` 67 68## Debug Action Manager 69 70The `DebugActionManager` orchestrates the various different queries relating to 71debug actions, and is accessible via the `MLIRContext`. These queries allow for 72external entities to control various aspects of the compiler via 73[action handlers](#debug-action-handler). When resolving a query for an action, 74the result from the most recently registered handler is used. 75 76TODO: It may be interesting to support merging results from multiple action 77handlers, but this is left for future work when driven by a real use case. 78 79The set of available queries are shown below: 80 81```c++ 82class DebugActionManager { 83public: 84 /// Returns true if the given action type should be executed, false otherwise. 85 /// `Params` correspond to any action specific parameters that may be used to 86 /// guide the decision. 87 template <typename ActionType, typename... Params> 88 bool shouldExecute(Params &&... params); 89}; 90``` 91 92Building on the example from the [previous section](#debug-action), an example 93usage of the `shouldExecute` query is shown below: 94 95```c++ 96/// A debug action that allows for controlling the application of patterns. 97struct ApplyPatternAction 98 : public DebugAction<Operation *, const Pattern &> { 99 static StringRef getTag() { return "apply-pattern"; } 100 static StringRef getDescription() { 101 return "Control the application of rewrite patterns"; 102 } 103}; 104 105// ... 106 107bool shouldApplyPattern(Operation *currentOp, const Pattern ¤tPattern) { 108 MLIRContext *context = currentOp->getContext(); 109 DebugActionManager &manager = context->getDebugActionManager(); 110 111 // Query the action manager to see if `currentPattern` should be applied to 112 // `currentOp`. 113 return manager.shouldExecute<ApplyPatternAction>(currentOp, currentPattern); 114} 115``` 116 117## Debug Action Handler 118 119A debug action handler provides the internal implementation for the various 120action related queries within the [`DebugActionManager`](#debug-action-manager). 121Action handlers allow for external entities to control and inject external 122information into the compiler. Handlers can be registered with the 123`DebugActionManager` using `registerActionHandler`. There are two types of 124handlers; action-specific handlers and generic handlers. 125 126### Action Specific Handlers 127 128Action specific handlers handle a specific debug action type, with the 129parameters to its query methods mapping 1-1 to the parameter types of the action 130type. An action specific handler can be defined by inheriting from the handler 131base class defined at `ActionType::Handler` where `ActionType` is the specific 132action that should be handled. An example using our running pattern example is 133shown below: 134 135```c++ 136struct MyPatternHandler : public ApplyPatternAction::Handler { 137 /// A variant of `shouldExecute` shown in the `DebugActionManager` class 138 /// above. 139 /// This method returns a FailureOr<bool>, where failure signifies that the 140 /// action was not handled (allowing for other handlers to process it), or the 141 /// boolean true/false signifying if the action should execute or not. 142 FailureOr<bool> shouldExecute(Operation *op, 143 const RewritePattern &pattern) final; 144}; 145``` 146 147### Generic Handlers 148 149A generic handler allows for handling queries on any action type. These types of 150handlers are useful for implementing general functionality that doesn’t 151necessarily need to interpret the exact action parameters, or can rely on an 152external interpreter (such as the user). As these handlers are generic, they 153take a set of opaque parameters that try to map the context of the action type 154in a generic way. A generic handler can be defined by inheriting from 155`DebugActionManager::GenericHandler`. An example is shown below: 156 157```c++ 158struct MyPatternHandler : public DebugActionManager::GenericHandler { 159 /// The return type of this method is the same as the action-specific handler. 160 /// The arguments to this method map the concepts of an action type in an 161 /// opaque way. The arguments are provided in such a way so that the context 162 /// of the action is still somewhat user readable, or at least loggable as 163 /// such. 164 /// - actionTag: The tag specified by the action type. 165 /// - actionDesc: The description specified by the action type. 166 virtual FailureOr<bool> shouldExecute(StringRef actionTag, 167 StringRef actionDesc); 168}; 169``` 170 171### Common Action Handlers 172 173MLIR provides several common debug action handlers for immediate use that have 174proven useful in general. 175 176#### DebugCounter 177 178When debugging a compiler issue, 179["bisection"](https://en.wikipedia.org/wiki/Bisection_\(software_engineering\)) 180is a useful technique for locating the root cause of the issue. `Debug Counters` 181enable using this technique for debug actions by attaching a counter value to a 182specific debug action and enabling/disabling execution of this action based on 183the value of the counter. The counter controls the execution of the action with 184a "skip" and "count" value. The "skip" value is used to skip a certain number of 185initial executions of a debug action. The "count" value is used to prevent a 186debug action from executing after it has executed for a set number of times (not 187including any executions that have been skipped). If the "skip" value is 188negative, the action will always execute. If the "count" value is negative, the 189action will always execute after the "skip" value has been reached. For example, 190a counter for a debug action with `skip=47` and `count=2`, would skip the first 19147 executions, then execute twice, and finally prevent any further executions. 192With a bit of tooling, the values to use for the counter can be automatically 193selected; allowing for finding the exact execution of a debug action that 194potentially causes the bug being investigated. 195 196Note: The DebugCounter action handler does not support multi-threaded execution, 197and should only be used in MLIRContexts where multi-threading is disabled (e.g. 198via `-mlir-disable-threading`). 199 200##### CommandLine Configuration 201 202The `DebugCounter` handler provides several that allow for configuring counters. 203The main option is `mlir-debug-counter`, which accepts a comma separated list of 204`<count-name>=<counter-value>`. A `<counter-name>` is the debug action tag to 205attach the counter, suffixed with either `-skip` or `-count`. A `-skip` suffix 206will set the "skip" value of the counter. A `-count` suffix will set the "count" 207value of the counter. The `<counter-value>` component is a numeric value to use 208for the counter. An example is shown below using `ApplyPatternAction` defined 209above: 210 211```shell 212$ mlir-opt foo.mlir -mlir-debug-counter=apply-pattern-skip=47,apply-pattern-count=2 213``` 214 215The above configuration would skip the first 47 executions of 216`ApplyPatternAction`, then execute twice, and finally prevent any further 217executions. 218 219Note: Each counter currently only has one `skip` and one `count` value, meaning 220that sequences of `skip`/`count` will not be chained. 221 222The `mlir-print-debug-counter` option may be used to print out debug counter 223information after all counters have been accumulated. The information is printed 224in the following format: 225 226```shell 227DebugCounter counters: 228<action-tag> : {<current-count>,<skip>,<count>} 229``` 230 231For example, using the options above we can see how many times an action is 232executed: 233 234```shell 235$ mlir-opt foo.mlir -mlir-debug-counter=apply-pattern-skip=-1 -mlir-print-debug-counter 236 237DebugCounter counters: 238apply-pattern : {370,-1,-1} 239``` 240