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 &currentPattern) {
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