From b0528a53eab403194b713995e4b22d4cff12818b Mon Sep 17 00:00:00 2001 From: Mehdi Amini Date: Sun, 23 Apr 2023 16:55:15 -0600 Subject: [PATCH] Add user doc on the website for the Action framework The old DebugAction documentation is deleted: the code in-tree does not match it anymore. Differential Revision: https://reviews.llvm.org/D149037 --- mlir/docs/ActionTracing.md | 274 +++++++++++++++++++++++++++++++++++ mlir/docs/DebugActions.md | 241 ------------------------------ mlir/test/mlir-opt/debugcounter.mlir | 15 ++ 3 files changed, 289 insertions(+), 241 deletions(-) create mode 100644 mlir/docs/ActionTracing.md delete mode 100644 mlir/docs/DebugActions.md create mode 100644 mlir/test/mlir-opt/debugcounter.mlir diff --git a/mlir/docs/ActionTracing.md b/mlir/docs/ActionTracing.md new file mode 100644 index 0000000..81a94dd --- /dev/null +++ b/mlir/docs/ActionTracing.md @@ -0,0 +1,274 @@ +# Action: Tracing and Debugging MLIR-based Compilers + +\[TOC\] + +See also the[slides](https://mlir.llvm.org/OpenMeetings/2023-02-23-Actions.pdf) +and the [recording](<>) from the MLIR Open Meeting where this feature was +demoed. + +## Overview + +`Action` are means to encapsulate any transformation of any granularity in a way +that can be intercepted by the framework for debugging or tracing purposes, +including skipping a transformation programmatically (think about "compiler +fuel" or "debug counters" in LLVM). As such, "executing a pass" is an Action, so +is "try to apply one canonicalization pattern", or "tile this loop". + +In MLIR, passes and patterns are the main abstractions to encapsulate general IR +transformations. The primary way of observing transformations along the way is +to enable “debug printing” of the IR (e.g. -mlir-print-ir-after-all to print +after each pass execution). On top of this, finer grain tracing may be available +with -debug which enables more detailed logs from the transformations +themselves. However, this method has some scaling issues: it is limited to a +single stream of text that can be gigantic and requires tedious crawling through +this log a posteriori. Iterating through multiple runs of collecting such logs +and analyzing it can be very time consuming and often not very practical beyond +small input programs. + +The `Action` 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 +`Action` execution is shown below: + +- Compiler developer defines an `Action` class, that is representing the + transformation or utility that they are developing. +- Depending on the needs, the developer identifies single unit of + transformations, and dispatch them to the `MLIRContext` for execution. +- An external entity registers an "action handler" with the action manager, and + provides the logic surrounding the transformation execution. + +The exact definition of an `external entity` is left opaque, to allow for more +interesting handlers. + +## Wrapping a Transformation in an Action + +There are two parts for getting started with enabling tracing through Action in +existing or new code: 1) defining an actual `Action` class, and 2) encapsulating +the transformation in a lambda function. + +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: + +```c++ +/// A custom Action can be defined minimally by deriving from +/// `tracing::ActionImpl`. +class MyCustomAction : public tracing::ActionImpl { +public: + using Base = tracing::ActionImpl; + /// Actions are initialized with an array of IRUnit (that is either Operation, + /// Block, or Region) that provide context for the IR affected by a transformation. + MyCustomAction(ArrayRef irUnits) + : Base(irUnits) {} + /// This tag should uniquely identify this action, it can be matched for filtering + /// during processing. + static constexpr StringLiteral tag = "unique-tag-for-my-action"; + static constexpr StringLiteral desc = + "This action will encapsulate a some very specific transformation"; +}; +``` + +Any transformation can then be dispatch with this `Action` through the +`MLIRContext`: + +```c++ +context->executeAction( + [&]() { + rewriter.setInsertionPoint(op); + + ... + }, + /*IRUnits=*/{op, region}); +``` + +An action can also carry arbitrary payload, for example we can extend the +`MyCustomAction` class above with the following member: + +```c++ +/// A custom Action can be defined minimally by deriving from +/// `tracing::ActionImpl`. It can has any members! +class MyCustomAction : public tracing::ActionImpl { +public: + using Base = tracing::ActionImpl; + /// Actions are initialized with an array of IRUnit (that is either Operation, + /// Block, or Region) that provide context for the IR affected by a transformation. + /// Other constructor arguments can also be required here. + MyCustomAction(ArrayRef irUnits, int count, PaddingStyle padding) + : Base(irUnits), count(count), padding(padding) {} + /// This tag should uniquely identify this action, it can be matched for filtering + /// during processing. + static constexpr StringLiteral tag = "unique-tag-for-my-action"; + static constexpr StringLiteral desc = + "This action will encapsulate a some very specific transformation"; + /// Extra members can be carried by the Action + int count; + PaddingStyle padding; +}; +``` + +These new members must then be passed as arguments when dispatching an `Action`: + +```c++ +context->executeAction( + [&]() { + rewriter.setInsertionPoint(op); + + ... + }, + /*IRUnits=*/{op, region}, + /*count=*/count, + /*padding=*/padding); +``` + +## Intercepting Actions + +When a transformation is executed through an `Action`, it can be directly +intercepted via a handler that can be set on the `MLIRContext`: + +```c++ + /// Signatures for the action handler that can be registered with the context. + using HandlerTy = + std::function, const tracing::Action &)>; + + /// Register a handler for handling actions that are dispatched through this + /// context. A nullptr handler can be set to disable a previously set handler. + void registerActionHandler(HandlerTy handler); +``` + +This handler takes two arguments: the first on is the transformation wrapped in +a callback, and the second is a reference to the associated action object. The +handler has full control of the execution, as such it can also decide to return +without executing the callback, skipping the transformation entirely! + +## MLIR-provided Handlers + +MLIR provides some predefined action handlers for immediate use that are +believed to be useful for most projects built with MLIR. + +### Debug Counters + +When debugging a compiler issue, +["bisection"]() +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 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 +`=`. A `` 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 `` component is a numeric value to use +for the counter. An example is shown below using `MyCustomAction` defined above: + +```shell +$ mlir-opt foo.mlir -mlir-debug-counter=unique-tag-for-my-action-skip=47,unique-tag-for-my-action-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: + : {,,} +``` + +For example, using the options above we can see how many times an action is +executed: + +```shell +$ mlir-opt foo.mlir -mlir-debug-counter=unique-tag-for-my-action-skip=-1 -mlir-print-debug-counter --pass-pipeline="builtin.module(func.func(my-pass))" --mlir-disable-threading + +DebugCounter counters: +unique-tag-for-my-action : {370,-1,-1} +``` + +### ExecutionContext + +The `ExecutionContext` is a component that provides facility to unify the kind +of functionalities that most compiler debuggers tool would need, exposed in a +composable way. + +![IMG](/actions/ActionTracing_ExecutionContext.png) + +The `ExecutionContext` is itself registered as a handler with the MLIRContext +and tracks all executed actions, keeping a per-thread stack of action execution. +It acts as a middleware that handles the flow of action execution while allowing +injection and control from a debugger. + +- Multiple `Observers` can be registered with the `ExecutionContext`. When an + action is dispatched for execution, it is passed to each of the `Observers` + before and after executing the transformation. +- Multiple `BreakpointManager` can be registered with the `ExecutionContext`. + When an action is dispatched for execution, it is passed to each of the + registered `BreakpointManager` until one matches the action and return a valid + `Breakpoint` object. In this case, the "callback" set by the client on the + `ExecutionContext` is invoked, otherwise the transformation is directly + executed. +- A single callback: + `using CallbackTy = function_ref;` can be + registered with the `ExecutionContext`, it is invoked when a `BreakPoint` is + hit by an `Action`. The returned value of type `Control` is an enum + instructing the `ExecutionContext` of how to proceed next: + ```c++ + /// Enum that allows the client of the context to control the execution of the + /// action. + /// - Apply: The action is executed. + /// - Skip: The action is skipped. + /// - Step: The action is executed and the execution is paused before the next + /// action, including for nested actions encountered before the + /// current action finishes. + /// - Next: The action is executed and the execution is paused after the + /// current action finishes before the next action. + /// - Finish: The action is executed and the execution is paused only when we + /// reach the parent/enclosing operation. If there are no enclosing + /// operation, the execution continues without stopping. + enum Control { Apply = 1, Skip = 2, Step = 3, Next = 4, Finish = 5 }; + ``` + Since the callback actually controls the execution, there can be only one registered at any given time. + +#### Debugger ExecutionContext Hook + +MLIR provides a callback for the `ExecutionContext` that implements a small runtime +suitable for debuggers like `gdb` or `lldb` to interactively control the execution. +It can be setup with `mlir::setupDebuggerExecutionContextHook(executionContext);` or +using `mlir-opt` with the `--mlir-enable-debugger-hook` flag. This runtime exposes a +set of C API function that can be called from a debugger to: +- set breakpoints matching either action tags, or the `FileLineCol` locations of the IR associated with the action. +- set the `Control` flag to be returned to the `ExecutionContext`. +- control a "cursor" allowing to navigate through the IR and inspect it from the IR context associated with the action. + +The implementation of this runtime can serve as an example for other implementation +of programmatic control of the execution. + +#### Logging Observer + +One observer is provided that allows to log action execution on a provided stream. +It can be exercised with `mlir-opt` using `--log-actions-to=`, and optionally filtering the output with `--log-mlir-actions-filter=`. +This observer is not thread-safe at the moment. diff --git a/mlir/docs/DebugActions.md b/mlir/docs/DebugActions.md deleted file mode 100644 index a40459c..0000000 --- a/mlir/docs/DebugActions.md +++ /dev/null @@ -1,241 +0,0 @@ -# Debug Actions - -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. - -[TOC] - -## 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. -/// * `DebugAction` is a CRTP class, so the first template parameter is the -/// action type class itself. -/// * The parameters for the action are provided via additional template -/// parameters when inheriting from `DebugAction`. -struct ApplyPatternAction - : public DebugAction { - 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 - 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 { - 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(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, 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 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 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 -`=`. A `` 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 `` 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: - : {,,} -``` - -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} -``` diff --git a/mlir/test/mlir-opt/debugcounter.mlir b/mlir/test/mlir-opt/debugcounter.mlir new file mode 100644 index 0000000..5d17795 --- /dev/null +++ b/mlir/test/mlir-opt/debugcounter.mlir @@ -0,0 +1,15 @@ +// This test exercise the example in docs/ActionTracing.md ; changes here should +// probably be reflected there. + +// RUN: mlir-opt %s -mlir-debug-counter=unique-tag-for-my-action-skip=-1 -mlir-print-debug-counter --pass-pipeline="builtin.module(func.func(canonicalize))" --mlir-disable-threading 2>&1 | FileCheck %s --check-prefix=CHECK-UKNOWN-TAG +// RUN: mlir-opt %s -mlir-debug-counter=pass-execution-skip=1 -mlir-print-debug-counter --pass-pipeline="builtin.module(func.func(canonicalize))" --mlir-disable-threading 2>&1 | FileCheck %s --check-prefix=CHECK-PASS + +func.func @foo() { + return +} + +// CHECK-UKNOWN-TAG: DebugCounter counters: +// CHECK-UKNOWN-TAG: unique-tag-for-my-action : {0,-1,-1} + +// CHECK-PASS: DebugCounter counters: +// CHECK-PASS: pass-execution : {1,1,-1} -- 2.7.4