forked from OSchip/llvm-project
240 lines
10 KiB
Markdown
240 lines
10 KiB
Markdown
# Debug Actions
|
||
|
||
[TOC]
|
||
|
||
This file documents the infrastructure for `Debug Actions`. This is a DEBUG only
|
||
API that allows for external entities to control various aspects of compiler
|
||
execution. This is conceptually similar to something like `DebugCounters` in
|
||
LLVM, but at a lower level. This framework doesn't make any assumptions about
|
||
how the higher level driver is controlling the execution, it merely provides a
|
||
framework for connecting the two together. A high level overview of the workflow
|
||
surrounding debug actions is shown below:
|
||
|
||
* Compiler developer defines an [`action`](#debug-action) that is taken by the
|
||
a pass, transformation, utility that they are developing.
|
||
* Depending on the needs, the developer dispatches various queries, pertaining
|
||
to this action, to an [`action manager`](#debug-action-manager) that will
|
||
provide an answer as to what behavior the action should take.
|
||
* An external entity registers an [`action handler`](#debug-action-handler)
|
||
with the action manager, and provides the logic to resolve queries on
|
||
actions.
|
||
|
||
The exact definition of an `external entity` is left opaque, to allow for more
|
||
interesting handlers. The set of possible action queries is detailed in the
|
||
[`action manager`](#debug-action-manager) section below.
|
||
|
||
## Debug Action
|
||
|
||
A `debug action` is essentially a marker for a type of action that may be
|
||
performed within the compiler. There are no constraints on the granularity of an
|
||
“action”, it can be as simple as “perform this fold” and as complex as “run this
|
||
pass pipeline”. An action is comprised of the following:
|
||
|
||
* Tag:
|
||
|
||
- A unique string identifier, similar to a command line flag or
|
||
DEBUG_TYPE.
|
||
|
||
* Description:
|
||
|
||
- A short description of what the action represents.
|
||
|
||
* Parameter Types:
|
||
|
||
- The types of values that are passed to queries related to this action,
|
||
to help guide decisions.
|
||
|
||
Below is an example action that may be provided by the
|
||
[pattern rewriter](PatternRewriter.md) framework to control the application of
|
||
rewrite patterns.
|
||
|
||
```c++
|
||
/// A debug action that allows for controlling the application of patterns.
|
||
/// A new action type can be defined by inheriting from `DebugAction`.
|
||
/// * The Tag is specified via a static `StringRef getTag()` method.
|
||
/// * The Description is specified via a static `StringRef getDescription()`
|
||
/// method.
|
||
/// * The parameters for the action are provided via template parameters when
|
||
/// inheriting from `DebugAction`.
|
||
struct ApplyPatternAction
|
||
: public DebugAction<Operation *, const Pattern &> {
|
||
static StringRef getTag() { return "apply-pattern"; }
|
||
static StringRef getDescription() {
|
||
return "Control the application of rewrite patterns";
|
||
}
|
||
};
|
||
```
|
||
|
||
## Debug Action Manager
|
||
|
||
The `DebugActionManager` orchestrates the various different queries relating to
|
||
debug actions, and is accessible via the `MLIRContext`. These queries allow for
|
||
external entities to control various aspects of the compiler via
|
||
[action handlers](#debug-action-handler). When resolving a query for an action,
|
||
the result from the most recently registered handler is used.
|
||
|
||
TODO: It may be interesting to support merging results from multiple action
|
||
handlers, but this is left for future work when driven by a real use case.
|
||
|
||
The set of available queries are shown below:
|
||
|
||
```c++
|
||
class DebugActionManager {
|
||
public:
|
||
/// Returns true if the given action type should be executed, false otherwise.
|
||
/// `Params` correspond to any action specific parameters that may be used to
|
||
/// guide the decision.
|
||
template <typename ActionType, typename... Params>
|
||
bool shouldExecute(Params &&... params);
|
||
};
|
||
```
|
||
|
||
Building on the example from the [previous section](#debug-action), an example
|
||
usage of the `shouldExecute` query is shown below:
|
||
|
||
```c++
|
||
/// A debug action that allows for controlling the application of patterns.
|
||
struct ApplyPatternAction
|
||
: public DebugAction<Operation *, const Pattern &> {
|
||
static StringRef getTag() { return "apply-pattern"; }
|
||
static StringRef getDescription() {
|
||
return "Control the application of rewrite patterns";
|
||
}
|
||
};
|
||
|
||
// ...
|
||
|
||
bool shouldApplyPattern(Operation *currentOp, const Pattern ¤tPattern) {
|
||
MLIRContext *context = currentOp->getContext();
|
||
DebugActionManager &manager = context->getDebugActionManager();
|
||
|
||
// Query the action manager to see if `currentPattern` should be applied to
|
||
// `currentOp`.
|
||
return manager.shouldExecute<ApplyPatternAction>(currentOp, currentPattern);
|
||
}
|
||
```
|
||
|
||
## Debug Action Handler
|
||
|
||
A debug action handler provides the internal implementation for the various
|
||
action related queries within the [`DebugActionManager`](#debug-action-manager).
|
||
Action handlers allow for external entities to control and inject external
|
||
information into the compiler. Handlers can be registered with the
|
||
`DebugActionManager` using `registerActionHandler`. There are two types of
|
||
handlers; action-specific handlers and generic handlers.
|
||
|
||
### Action Specific Handlers
|
||
|
||
Action specific handlers handle a specific debug action type, with the
|
||
parameters to its query methods mapping 1-1 to the parameter types of the action
|
||
type. An action specific handler can be defined by inheriting from the handler
|
||
base class defined at `ActionType::Handler` where `ActionType` is the specific
|
||
action that should be handled. An example using our running pattern example is
|
||
shown below:
|
||
|
||
```c++
|
||
struct MyPatternHandler : public ApplyPatternAction::Handler {
|
||
/// A variant of `shouldExecute` shown in the `DebugActionManager` class
|
||
/// above.
|
||
/// This method returns a FailureOr<bool>, where failure signifies that the
|
||
/// action was not handled (allowing for other handlers to process it), or the
|
||
/// boolean true/false signifying if the action should execute or not.
|
||
FailureOr<bool> shouldExecute(Operation *op,
|
||
const RewritePattern &pattern) final;
|
||
};
|
||
```
|
||
|
||
### Generic Handlers
|
||
|
||
A generic handler allows for handling queries on any action type. These types of
|
||
handlers are useful for implementing general functionality that doesn’t
|
||
necessarily need to interpret the exact action parameters, or can rely on an
|
||
external interpreter (such as the user). As these handlers are generic, they
|
||
take a set of opaque parameters that try to map the context of the action type
|
||
in a generic way. A generic handler can be defined by inheriting from
|
||
`DebugActionManager::GenericHandler`. An example is shown below:
|
||
|
||
```c++
|
||
struct MyPatternHandler : public DebugActionManager::GenericHandler {
|
||
/// The return type of this method is the same as the action-specific handler.
|
||
/// The arguments to this method map the concepts of an action type in an
|
||
/// opaque way. The arguments are provided in such a way so that the context
|
||
/// of the action is still somewhat user readable, or at least loggable as
|
||
/// such.
|
||
/// - actionTag: The tag specified by the action type.
|
||
/// - actionDesc: The description specified by the action type.
|
||
virtual FailureOr<bool> shouldExecute(StringRef actionTag,
|
||
StringRef actionDesc);
|
||
};
|
||
```
|
||
|
||
### Common Action Handlers
|
||
|
||
MLIR provides several common debug action handlers for immediate use that have
|
||
proven useful in general.
|
||
|
||
#### DebugCounter
|
||
|
||
When debugging a compiler issue,
|
||
["bisection"](https://en.wikipedia.org/wiki/Bisection_\(software_engineering\))
|
||
is a useful technique for locating the root cause of the issue. `Debug Counters`
|
||
enable using this technique for debug actions by attaching a counter value to a
|
||
specific debug action and enabling/disabling execution of this action based on
|
||
the value of the counter. The counter controls the execution of the action with
|
||
a "skip" and "count" value. The "skip" value is used to skip a certain number of
|
||
initial executions of a debug action. The "count" value is used to prevent a
|
||
debug action from executing after it has executed for a set number of times (not
|
||
including any executions that have been skipped). If the "skip" value is
|
||
negative, the action will always execute. If the "count" value is negative, the
|
||
action will always execute after the "skip" value has been reached. For example,
|
||
a counter for a debug action with `skip=47` and `count=2`, would skip the first
|
||
47 executions, then execute twice, and finally prevent any further executions.
|
||
With a bit of tooling, the values to use for the counter can be automatically
|
||
selected; allowing for finding the exact execution of a debug action that
|
||
potentially causes the bug being investigated.
|
||
|
||
Note: The DebugCounter action handler does not support multi-threaded execution,
|
||
and should only be used in MLIRContexts where multi-threading is disabled (e.g.
|
||
via `-mlir-disable-threading`).
|
||
|
||
##### CommandLine Configuration
|
||
|
||
The `DebugCounter` handler provides several that allow for configuring counters.
|
||
The main option is `mlir-debug-counter`, which accepts a comma separated list of
|
||
`<count-name>=<counter-value>`. A `<counter-name>` is the debug action tag to
|
||
attach the counter, suffixed with either `-skip` or `-count`. A `-skip` suffix
|
||
will set the "skip" value of the counter. A `-count` suffix will set the "count"
|
||
value of the counter. The `<counter-value>` component is a numeric value to use
|
||
for the counter. An example is shown below using `ApplyPatternAction` defined
|
||
above:
|
||
|
||
```shell
|
||
$ mlir-opt foo.mlir -mlir-debug-counter=apply-pattern-skip=47,apply-pattern-count=2
|
||
```
|
||
|
||
The above configuration would skip the first 47 executions of
|
||
`ApplyPatternAction`, then execute twice, and finally prevent any further
|
||
executions.
|
||
|
||
Note: Each counter currently only has one `skip` and one `count` value, meaning
|
||
that sequences of `skip`/`count` will not be chained.
|
||
|
||
The `mlir-print-debug-counter` option may be used to print out debug counter
|
||
information after all counters have been accumulated. The information is printed
|
||
in the following format:
|
||
|
||
```shell
|
||
DebugCounter counters:
|
||
<action-tag> : {<current-count>,<skip>,<count>}
|
||
```
|
||
|
||
For example, using the options above we can see how many times an action is
|
||
executed:
|
||
|
||
```shell
|
||
$ mlir-opt foo.mlir -mlir-debug-counter=apply-pattern-skip=-1 -mlir-print-debug-counter
|
||
|
||
DebugCounter counters:
|
||
apply-pattern : {370,-1,-1}
|
||
```
|