[Mlir-commits] [mlir] b0528a5 - Add user doc on the website for the Action framework

Mehdi Amini llvmlistbot at llvm.org
Mon Apr 24 14:34:36 PDT 2023


Author: Mehdi Amini
Date: 2023-04-24T14:34:15-07:00
New Revision: b0528a53eab403194b713995e4b22d4cff12818b

URL: https://github.com/llvm/llvm-project/commit/b0528a53eab403194b713995e4b22d4cff12818b
DIFF: https://github.com/llvm/llvm-project/commit/b0528a53eab403194b713995e4b22d4cff12818b.diff

LOG: 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

Added: 
    mlir/docs/ActionTracing.md
    mlir/test/mlir-opt/debugcounter.mlir

Modified: 
    

Removed: 
    mlir/docs/DebugActions.md


################################################################################
diff  --git a/mlir/docs/ActionTracing.md b/mlir/docs/ActionTracing.md
new file mode 100644
index 0000000000000..81a94dd083fc6
--- /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<MyCustomAction> {
+public:
+  using Base = tracing::ActionImpl<MyCustomAction>;
+  /// 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<IRUnit> 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<ApplyPatternAction>(
+    [&]() {
+      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<MyCustomAction> {
+public:
+  using Base = tracing::ActionImpl<MyCustomAction>;
+  /// 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<IRUnit> 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<ApplyPatternAction>(
+    [&]() {
+      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<void(function_ref<void()>, 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"](<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 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 `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:
+<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=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<Control(const ActionActiveStack *)>;` 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=<filename>`, and optionally filtering the output with `--log-mlir-actions-filter=<FileLineCol>`.
+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 a40459c6161d6..0000000000000
--- 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<ApplyPatternAction, 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 
diff erent 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<ApplyPatternAction, 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 &currentPattern) {
-  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}
-```

diff  --git a/mlir/test/mlir-opt/debugcounter.mlir b/mlir/test/mlir-opt/debugcounter.mlir
new file mode 100644
index 0000000000000..5d1779560a513
--- /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}


        


More information about the Mlir-commits mailing list