Add a breakpoint manager that matches based on File/Line/Col Locations
authorMehdi Amini <joker.eph@gmail.com>
Mon, 6 Feb 2023 05:11:30 +0000 (21:11 -0800)
committerMehdi Amini <joker.eph@gmail.com>
Sat, 22 Apr 2023 04:28:27 +0000 (22:28 -0600)
This will match the locations attached to the IRunits passed in as context
with an action.

Differential Revision: https://reviews.llvm.org/D144815

mlir/include/mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h [new file with mode: 0644]
mlir/include/mlir/Debug/Observers/ActionLogging.h
mlir/include/mlir/Tools/mlir-opt/MlirOptMain.h
mlir/lib/Debug/BreakpointManagers/FileLineColLocBreakpointManager.cpp [new file with mode: 0644]
mlir/lib/Debug/CMakeLists.txt
mlir/lib/Debug/Observers/ActionLogging.cpp
mlir/lib/Tools/mlir-opt/MlirOptMain.cpp
mlir/test/Pass/action-logging-filter.mlir [new file with mode: 0644]
mlir/unittests/Debug/CMakeLists.txt
mlir/unittests/Debug/FileLineColLocBreakpointManagerTest.cpp [new file with mode: 0644]

diff --git a/mlir/include/mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h b/mlir/include/mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h
new file mode 100644 (file)
index 0000000..d4f9a6e
--- /dev/null
@@ -0,0 +1,137 @@
+//===- FileLineColLocBreakpointManager.h - TODO: add message ----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_TRACING_BREAKPOINTMANAGERS_FILELINECOLLOCBREAKPOINTMANAGER_H
+#define MLIR_TRACING_BREAKPOINTMANAGERS_FILELINECOLLOCBREAKPOINTMANAGER_H
+
+#include "mlir/Debug/BreakpointManager.h"
+#include "mlir/Debug/ExecutionContext.h"
+#include "mlir/IR/Action.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/Operation.h"
+#include "llvm/ADT/DenseMap.h"
+#include <memory>
+#include <optional>
+
+namespace mlir {
+namespace tracing {
+
+/// This breakpoing intends to match a FileLineColLocation, that is a tuple of
+/// file name, line number, and column number. Using -1 for  the column and the
+/// line number will match any column and line number respectively.
+class FileLineColLocBreakpoint
+    : public BreakpointBase<FileLineColLocBreakpoint> {
+public:
+  FileLineColLocBreakpoint(StringRef file, int64_t line, int64_t col)
+      : line(line), col(col) {}
+
+  void print(raw_ostream &os) const override {
+    os << "Location: " << file << ':' << line << ':' << col;
+  }
+
+  /// Parse a string representation in the form of "<file>:<line>:<col>". Return
+  /// a tuple with these three elements, the first one is a StringRef pointing
+  /// into the original string.
+  static FailureOr<std::tuple<StringRef, int64_t, int64_t>> parseFromString(
+      StringRef str, llvm::function_ref<void(Twine)> diag = [](Twine) {});
+
+private:
+  /// A filename on which to break.
+  StringRef file;
+
+  /// A particular line on which to break, or -1 to break on any line.
+  int64_t line;
+
+  /// A particular column on which to break, or -1 to break on any column
+  int64_t col;
+
+  friend class FileLineColLocBreakpointManager;
+};
+
+/// This breakpoint manager is responsible for matching
+/// FileLineColLocBreakpoint. It'll extract the location from the action context
+/// looking for a FileLineColLocation, and match it against the registered
+/// breakpoints.
+class FileLineColLocBreakpointManager
+    : public BreakpointManagerBase<FileLineColLocBreakpointManager> {
+public:
+  Breakpoint *match(const Action &action) const override {
+    for (const IRUnit &unit : action.getContextIRUnits()) {
+      if (auto *op = unit.dyn_cast<Operation *>()) {
+        if (auto match = matchFromLocation(op->getLoc()))
+          return *match;
+        continue;
+      }
+      if (auto *block = unit.dyn_cast<Block *>()) {
+        for (auto &op : block->getOperations()) {
+          if (auto match = matchFromLocation(op.getLoc()))
+            return *match;
+        }
+        continue;
+      }
+      if (Region *region = unit.dyn_cast<Region *>()) {
+        if (auto match = matchFromLocation(region->getLoc()))
+          return *match;
+        continue;
+      }
+    }
+    return {};
+  }
+
+  FileLineColLocBreakpoint *addBreakpoint(StringRef file, int64_t line,
+                                          int64_t col = -1) {
+    auto &breakpoint = breakpoints[std::make_tuple(file, line, col)];
+    if (!breakpoint)
+      breakpoint = std::make_unique<FileLineColLocBreakpoint>(file, line, col);
+    return breakpoint.get();
+  }
+
+private:
+  std::optional<Breakpoint *> matchFromLocation(Location initialLoc) const {
+    std::optional<Breakpoint *> match = std::nullopt;
+    initialLoc->walk([&](Location loc) {
+      auto fileLoc = loc.dyn_cast<FileLineColLoc>();
+      if (!fileLoc)
+        return WalkResult::advance();
+      StringRef file = fileLoc.getFilename();
+      int64_t line = fileLoc.getLine();
+      int64_t col = fileLoc.getColumn();
+      auto lookup = breakpoints.find(std::make_tuple(file, line, col));
+      if (lookup != breakpoints.end() && lookup->second->isEnabled()) {
+        match = lookup->second.get();
+        return WalkResult::interrupt();
+      }
+      // If not found, check with the -1 key if we have a breakpoint for any
+      // col.
+      lookup = breakpoints.find(std::make_tuple(file, line, -1));
+      if (lookup != breakpoints.end() && lookup->second->isEnabled()) {
+        match = lookup->second.get();
+        return WalkResult::interrupt();
+      }
+      // If not found, check with the -1 key if we have a breakpoint for any
+      // line.
+      lookup = breakpoints.find(std::make_tuple(file, -1, -1));
+      if (lookup != breakpoints.end() && lookup->second->isEnabled()) {
+        match = lookup->second.get();
+        return WalkResult::interrupt();
+      }
+      return WalkResult::advance();
+    });
+    return match;
+  }
+
+  /// A map from a (filename, line, column) -> breakpoint.
+  DenseMap<std::tuple<StringRef, int64_t, int64_t>,
+           std::unique_ptr<FileLineColLocBreakpoint>>
+      breakpoints;
+};
+
+} // namespace tracing
+} // namespace mlir
+
+#endif // MLIR_TRACING_BREAKPOINTMANAGERS_FILELINECOLLOCBREAKPOINTMANAGER_H
index bd1d565..2e45ccf 100644 (file)
@@ -30,11 +30,21 @@ struct ActionLogger : public ExecutionContext::Observer {
                      bool willExecute) override;
   void afterExecute(const ActionActiveStack *action) override;
 
+  /// If one of multiple breakpoint managers are set, only actions that are
+  /// matching a breakpoint will be logged.
+  void addBreakpointManager(const BreakpointManager *manager) {
+    breakpointManagers.push_back(manager);
+  }
+
 private:
+  /// Check if we should log this action or not.
+  bool shouldLog(const ActionActiveStack *action);
+
   raw_ostream &os;
   bool printActions;
   bool printBreakpoints;
   bool printIRUnits;
+  std::vector<const BreakpointManager *> breakpointManagers;
 };
 
 } // namespace tracing
index f54c29c..39f7cd5 100644 (file)
@@ -13,6 +13,7 @@
 #ifndef MLIR_TOOLS_MLIROPT_MLIROPTMAIN_H
 #define MLIR_TOOLS_MLIROPT_MLIROPTMAIN_H
 
+#include "mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h"
 #include "mlir/Support/LogicalResult.h"
 #include "llvm/ADT/StringRef.h"
 
@@ -29,6 +30,9 @@ namespace mlir {
 class DialectRegistry;
 class PassPipelineCLParser;
 class PassManager;
+namespace tracing {
+class FileLineColLocBreakpointManager;
+}
 
 /// Configuration options for the mlir-opt tool.
 /// This is intended to help building tools like mlir-opt by collecting the
@@ -82,6 +86,18 @@ public:
   /// Get the filename to use for logging actions.
   StringRef getLogActionsTo() const { return logActionsToFlag; }
 
+  /// Set a location breakpoint manager to filter out action logging based on
+  /// the attached IR location in the Action context. Ownership stays with the
+  /// caller.
+  void addLogActionLocFilter(tracing::BreakpointManager *breakpointManager) {
+    logActionLocationFilter.push_back(breakpointManager);
+  }
+
+  /// Get the location breakpoint managers to use to filter out action logging.
+  ArrayRef<tracing::BreakpointManager *> getLogActionsLocFilters() const {
+    return logActionLocationFilter;
+  }
+
   /// Set the callback to populate the pass manager.
   MlirOptMainConfig &
   setPassPipelineSetupFn(std::function<LogicalResult(PassManager &)> callback) {
@@ -160,6 +176,9 @@ protected:
   /// Log action execution to the given file (or "-" for stdout)
   std::string logActionsToFlag;
 
+  /// Location Breakpoints to filter the action logging.
+  std::vector<tracing::BreakpointManager *> logActionLocationFilter;
+
   /// The callback to populate the pass manager.
   std::function<LogicalResult(PassManager &)> passPipelineCallback;
 
diff --git a/mlir/lib/Debug/BreakpointManagers/FileLineColLocBreakpointManager.cpp b/mlir/lib/Debug/BreakpointManagers/FileLineColLocBreakpointManager.cpp
new file mode 100644 (file)
index 0000000..6fef83f
--- /dev/null
@@ -0,0 +1,47 @@
+//===- FileLineColLocBreakpointManager.cpp - MLIR Optimizer Driver --------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h"
+#include "mlir/IR/Diagnostics.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace mlir;
+using namespace mlir::tracing;
+
+FailureOr<std::tuple<StringRef, int64_t, int64_t>>
+FileLineColLocBreakpoint::parseFromString(StringRef str,
+                                          function_ref<void(Twine)> diag) {
+  // Watch at debug locations arguments are expected to be in the form:
+  // `fileName:line:col`, `fileName:line`, or `fileName`.
+
+  auto [file, lineCol] = str.split(':');
+  auto [lineStr, colStr] = lineCol.split(':');
+  if (file.empty()) {
+    if (diag)
+      diag("error: initializing FileLineColLocBreakpoint with empty file name");
+    return failure();
+  }
+
+  // Extract the line and column value
+  int64_t line = -1, col = -1;
+  if (!lineStr.empty() && lineStr.getAsInteger(0, line)) {
+    if (diag)
+      diag("error: initializing FileLineColLocBreakpoint with a non-numeric "
+           "line value: `" +
+           Twine(lineStr) + "`");
+    return failure();
+  }
+  if (!colStr.empty() && colStr.getAsInteger(0, col)) {
+    if (diag)
+      diag("error: initializing FileLineColLocBreakpoint with a non-numeric "
+           "col value: `" +
+           Twine(colStr) + "`");
+    return failure();
+  }
+  return std::tuple<StringRef, int64_t, int64_t>{file, line, col};
+}
index 481db88..e4b844a 100644 (file)
@@ -3,6 +3,7 @@ add_subdirectory(Observers)
 add_mlir_library(MLIRDebug
   DebugCounter.cpp
   ExecutionContext.cpp
+  BreakpointManagers/FileLineColLocBreakpointManager.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${MLIR_MAIN_INCLUDE_DIR}/mlir/Debug
index 7e7c5ac..add16e8 100644 (file)
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "mlir/Debug/Observers/ActionLogging.h"
+#include "mlir/Debug/BreakpointManager.h"
 #include "mlir/IR/Action.h"
 #include "llvm/Support/Threading.h"
 #include "llvm/Support/raw_ostream.h"
@@ -18,8 +19,20 @@ using namespace mlir::tracing;
 // ActionLogger
 //===----------------------------------------------------------------------===//
 
+bool ActionLogger::shouldLog(const ActionActiveStack *action) {
+  // If some condition was set, we ensured it is met before logging.
+  if (breakpointManagers.empty())
+    return true;
+  return llvm::any_of(breakpointManagers,
+                      [&](const BreakpointManager *manager) {
+                        return manager->match(action->getAction());
+                      });
+}
+
 void ActionLogger::beforeExecute(const ActionActiveStack *action,
                                  Breakpoint *breakpoint, bool willExecute) {
+  if (!shouldLog(action))
+    return;
   SmallVector<char> name;
   llvm::get_thread_name(name);
   if (name.empty()) {
@@ -51,6 +64,8 @@ void ActionLogger::beforeExecute(const ActionActiveStack *action,
 }
 
 void ActionLogger::afterExecute(const ActionActiveStack *action) {
+  if (!shouldLog(action))
+    return;
   SmallVector<char> name;
   llvm::get_thread_name(name);
   if (name.empty()) {
index 8f608ef..2832450 100644 (file)
@@ -32,6 +32,7 @@
 #include "mlir/Tools/ParseUtilities.h"
 #include "mlir/Tools/Plugins/DialectPlugin.h"
 #include "mlir/Tools/Plugins/PassPlugin.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/FileUtilities.h"
 #include "llvm/Support/InitLLVM.h"
@@ -81,6 +82,33 @@ struct MlirOptMainConfigCLOptions : public MlirOptMainConfig {
                  " '-' is passed"),
         cl::location(logActionsToFlag)};
 
+    static cl::list<std::string> logActionLocationFilter(
+        "log-mlir-actions-filter",
+        cl::desc(
+            "Comma separated list of locations to filter actions from logging"),
+        cl::CommaSeparated,
+        cl::cb<void, std::string>([&](const std::string &location) {
+          static bool register_once = [&] {
+            addLogActionLocFilter(&locBreakpointManager);
+            return true;
+          }();
+          (void)register_once;
+          static std::vector<std::string> locations;
+          locations.push_back(location);
+          StringRef locStr = locations.back();
+
+          // Parse the individual location filters and set the breakpoints.
+          auto diag = [](Twine msg) { llvm::errs() << msg << "\n"; };
+          auto locBreakpoint =
+              tracing::FileLineColLocBreakpoint::parseFromString(locStr, diag);
+          if (failed(locBreakpoint)) {
+            llvm::errs() << "Invalid location filter: " << locStr << "\n";
+            exit(1);
+          }
+          auto [file, line, col] = *locBreakpoint;
+          locBreakpointManager.addBreakpoint(file, line, col);
+        }));
+
     static cl::opt<bool, /*ExternalStorage=*/true> showDialects(
         "show-dialects",
         cl::desc("Print the list of registered dialects and exit"),
@@ -130,6 +158,9 @@ struct MlirOptMainConfigCLOptions : public MlirOptMainConfig {
   /// Pointer to static dialectPlugins variable in constructor, needed by
   /// setDialectPluginsCallback(DialectRegistry&).
   cl::list<std::string> *dialectPlugins = nullptr;
+
+  /// The breakpoint manager for the log action location filter.
+  tracing::FileLineColLocBreakpointManager locBreakpointManager;
 };
 } // namespace
 
@@ -199,6 +230,8 @@ public:
     logActionsFile->keep();
     raw_fd_ostream &logActionsStream = logActionsFile->os();
     actionLogger = std::make_unique<tracing::ActionLogger>(logActionsStream);
+    for (const auto *locationBreakpoint : config.getLogActionsLocFilters())
+      actionLogger->addBreakpointManager(locationBreakpoint);
 
     executionContext.registerObserver(actionLogger.get());
     context.registerActionHandler(executionContext);
@@ -207,6 +240,8 @@ public:
 private:
   std::unique_ptr<llvm::ToolOutputFile> logActionsFile;
   std::unique_ptr<tracing::ActionLogger> actionLogger;
+  std::vector<std::unique_ptr<tracing::FileLineColLocBreakpoint>>
+      locationBreakpoints;
   tracing::ExecutionContext executionContext;
 };
 
diff --git a/mlir/test/Pass/action-logging-filter.mlir b/mlir/test/Pass/action-logging-filter.mlir
new file mode 100644 (file)
index 0000000..e565b18
--- /dev/null
@@ -0,0 +1,60 @@
+// Run the canonicalize on each function, use the --log-mlir-actions-filter= option
+// to filter which action should be logged.
+
+func.func @a() {
+    return
+}
+
+func.func @b() {
+    return
+}
+
+func.func @c() {
+    return
+}
+
+////////////////////////////////////
+/// 1. All actions should be logged.
+
+// RUN: mlir-opt %s --log-actions-to=- -pass-pipeline="builtin.module(func.func(canonicalize))" -o %t --mlir-disable-threading | FileCheck %s
+// Specify the current file as filter, expect to see all actions.
+// RUN: mlir-opt %s --log-mlir-actions-filter=%s --log-actions-to=- -pass-pipeline="builtin.module(func.func(canonicalize))" -o %t --mlir-disable-threading | FileCheck %s
+
+// CHECK: [thread {{.*}}] begins (no breakpoint) Action `pass-execution-action`  running `Canonicalizer` on Operation `func.func` (func.func @a() {...}
+// CHECK-NEXT: [thread {{.*}}] completed `pass-execution-action`
+// CHECK-NEXT: [thread {{.*}}] begins (no breakpoint) Action `pass-execution-action`  running `Canonicalizer` on Operation `func.func` (func.func @b() {...}
+// CHECK-NEXT: [thread {{.*}}] completed `pass-execution-action`
+// CHECK-NEXT: [thread {{.*}}] begins (no breakpoint) Action `pass-execution-action`  running `Canonicalizer` on Operation `func.func` (func.func @c() {...}
+// CHECK-NEXT: [thread {{.*}}] completed `pass-execution-action`
+
+////////////////////////////////////
+/// 2. No match
+
+// Specify a non-existing file as filter, expect to see no actions.
+// RUN: mlir-opt %s --log-mlir-actions-filter=foo.mlir --log-actions-to=- -pass-pipeline="builtin.module(func.func(canonicalize))" -o %t --mlir-disable-threading | FileCheck %s --check-prefix=CHECK-NONE --allow-empty
+// Filter on a non-matching line, expect to see no actions.
+// RUN: mlir-opt %s --log-mlir-actions-filter=%s:1 --log-actions-to=- -pass-pipeline="builtin.module(func.func(canonicalize))" -o %t --mlir-disable-threading | FileCheck %s --check-prefix=CHECK-NONE --allow-empty
+
+// Invalid Filter
+// CHECK-NONE-NOT: Canonicalizer
+
+////////////////////////////////////
+/// 3. Matching filters
+
+// Filter the second function only
+// RUN: mlir-opt %s --log-mlir-actions-filter=%s:8 --log-actions-to=- -pass-pipeline="builtin.module(func.func(canonicalize))" -o %t --mlir-disable-threading | FileCheck %s --check-prefix=CHECK-SECOND
+
+// CHECK-SECOND-NOT: @a
+// CHECK-SECOND-NOT: @c
+// CHECK-SECOND: [thread {{.*}}] begins (no breakpoint) Action `pass-execution-action`  running `Canonicalizer` on Operation `func.func` (func.func @b() {...}
+// CHECK-SECOND-NEXT: [thread {{.*}}] completed `pass-execution-action`
+
+// Filter the first and third functions
+// RUN: mlir-opt %s --log-mlir-actions-filter=%s:4,%s:12 --log-actions-to=- -pass-pipeline="builtin.module(func.func(canonicalize))" -o %t --mlir-disable-threading | FileCheck %s  --check-prefix=CHECK-FIRST-THIRD
+
+// CHECK-FIRST-THIRD-NOT: Canonicalizer
+// CHECK-FIRST-THIRD: [thread {{.*}}] begins (no breakpoint) Action `pass-execution-action`  running `Canonicalizer` on Operation `func.func` (func.func @a() {...}
+// CHECK-FIRST-THIRD-NEXT: [thread {{.*}}] completed `pass-execution-action`
+// CHECK-FIRST-THIRD-NEXT: [thread {{.*}}] begins (no breakpoint) Action `pass-execution-action`  running `Canonicalizer` on Operation `func.func` (func.func @c() {...}
+// CHECK-FIRST-THIRD-NEXT: [thread {{.*}}] completed `pass-execution-action`
+// CHECK-FIRST-THIRD-NOT: Canonicalizer
index 5ea18d2..59728bc 100644 (file)
@@ -1,6 +1,7 @@
 add_mlir_unittest(MLIRDebugTests
   DebugCounterTest.cpp
   ExecutionContextTest.cpp
+  FileLineColLocBreakpointManagerTest.cpp
 )
 
 target_link_libraries(MLIRDebugTests
diff --git a/mlir/unittests/Debug/FileLineColLocBreakpointManagerTest.cpp b/mlir/unittests/Debug/FileLineColLocBreakpointManagerTest.cpp
new file mode 100644 (file)
index 0000000..e9cac39
--- /dev/null
@@ -0,0 +1,232 @@
+//===- FileLineColLocBreakpointManagerTest.cpp - --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/Debug/BreakpointManagers/FileLineColLocBreakpointManager.h"
+#include "mlir/Debug/ExecutionContext.h"
+#include "mlir/IR/Builders.h"
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/BuiltinTypes.h"
+#include "mlir/IR/Location.h"
+#include "mlir/IR/OperationSupport.h"
+#include "llvm/ADT/STLExtras.h"
+#include "gtest/gtest.h"
+
+using namespace mlir;
+using namespace mlir::tracing;
+
+static Operation *createOp(MLIRContext *context, Location loc,
+                           StringRef operationName,
+                           unsigned int numRegions = 0) {
+  context->allowUnregisteredDialects();
+  return Operation::create(loc, OperationName(operationName, context),
+                           std::nullopt, std::nullopt, std::nullopt,
+                           std::nullopt, numRegions);
+}
+
+namespace {
+struct FileLineColLocTestingAction
+    : public ActionImpl<FileLineColLocTestingAction> {
+  MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(FileLineColLocTestingAction)
+  static constexpr StringLiteral tag = "file-line-col-loc-testing-action";
+  FileLineColLocTestingAction(ArrayRef<IRUnit> irUnits)
+      : ActionImpl<FileLineColLocTestingAction>(irUnits) {}
+};
+
+TEST(FileLineColLocBreakpointManager, OperationMatch) {
+  // This test will process a sequence of operation and check various situation
+  // with a breakpoint hitting or not based on the location attached to the
+  // operation. When a breakpoint hits, the action is skipped and the counter is
+  // not incremented.
+  ExecutionContext executionCtx(
+      [](const ActionActiveStack *) { return ExecutionContext::Skip; });
+  int counter = 0;
+  auto counterInc = [&]() { counter++; };
+
+  // Setup
+
+  MLIRContext context;
+  // Miscellaneous information to define operations
+  std::vector<StringRef> fileNames = {
+      StringRef("foo.bar"), StringRef("baz.qux"), StringRef("quux.corge")};
+  std::vector<std::pair<unsigned, unsigned>> lineColLoc = {{42, 7}, {24, 3}};
+  Location callee = UnknownLoc::get(&context),
+           caller = UnknownLoc::get(&context), loc = UnknownLoc::get(&context);
+
+  // Set of operations over where we are going to be testing the functionality
+  std::vector<Operation *> operations = {
+      createOp(&context, CallSiteLoc::get(callee, caller),
+               "callSiteLocOperation"),
+      createOp(&context,
+               FileLineColLoc::get(&context, fileNames[0], lineColLoc[0].first,
+                                   lineColLoc[0].second),
+               "fileLineColLocOperation"),
+      createOp(&context, FusedLoc::get(&context, {}, Attribute()),
+               "fusedLocOperation"),
+      createOp(&context, NameLoc::get(StringAttr::get(&context, fileNames[2])),
+               "nameLocOperation"),
+      createOp(&context, OpaqueLoc::get<void *>(nullptr, loc),
+               "opaqueLocOperation"),
+      createOp(&context,
+               FileLineColLoc::get(&context, fileNames[1], lineColLoc[1].first,
+                                   lineColLoc[1].second),
+               "anotherFileLineColLocOperation"),
+      createOp(&context, UnknownLoc::get(&context), "unknownLocOperation"),
+  };
+
+  FileLineColLocBreakpointManager breakpointManager;
+  executionCtx.addBreakpointManager(&breakpointManager);
+
+  // Test
+
+  // Basic case is that no breakpoint is set and the counter is incremented for
+  // every op.
+  auto checkNoMatch = [&]() {
+    counter = 0;
+    for (auto enumeratedOp : llvm::enumerate(operations)) {
+      executionCtx(counterInc,
+                   FileLineColLocTestingAction({enumeratedOp.value()}));
+      EXPECT_EQ(counter, static_cast<int>(enumeratedOp.index() + 1));
+    }
+  };
+  checkNoMatch();
+
+  // Set a breakpoint matching only the second operation in the list.
+  auto *breakpoint = breakpointManager.addBreakpoint(
+      fileNames[0], lineColLoc[0].first, lineColLoc[0].second);
+  auto checkMatchIdxs = [&](DenseSet<int> idxs) {
+    counter = 0;
+    int reference = 0;
+    for (int i = 0; i < (int)operations.size(); ++i) {
+      executionCtx(counterInc, FileLineColLocTestingAction({operations[i]}));
+      if (!idxs.contains(i))
+        reference++;
+      EXPECT_EQ(counter, reference);
+    }
+  };
+  checkMatchIdxs({1});
+
+  // Check that disabling the breakpoing brings us back to the original
+  // behavior.
+  breakpoint->disable();
+  checkNoMatch();
+
+  // Adding a breakpoint that won't match any location shouldn't affect the
+  // behavior.
+  breakpointManager.addBreakpoint(StringRef("random.file"), 3, 14);
+  checkNoMatch();
+
+  // Set a breakpoint matching only the fifth operation in the list.
+  breakpointManager.addBreakpoint(fileNames[1], lineColLoc[1].first,
+                                  lineColLoc[1].second);
+  counter = 0;
+  checkMatchIdxs({5});
+
+  // Re-enable the breakpoint matching only the second operation in the list.
+  // We now expect matching of operations 1 and 5.
+  breakpoint->enable();
+  checkMatchIdxs({1, 5});
+
+  for (auto *op : operations) {
+    op->destroy();
+  }
+}
+
+TEST(FileLineColLocBreakpointManager, BlockMatch) {
+  // This test will process a block and check various situation with
+  // a breakpoint hitting or not based on the location attached.
+  // When a breakpoint hits, the action is skipped and the counter is not
+  // incremented.
+  ExecutionContext executionCtx(
+      [](const ActionActiveStack *) { return ExecutionContext::Skip; });
+  int counter = 0;
+  auto counterInc = [&]() { counter++; };
+
+  // Setup
+
+  MLIRContext context;
+  std::vector<StringRef> fileNames = {StringRef("grault.garply"),
+                                      StringRef("waldo.fred")};
+  std::vector<std::pair<unsigned, unsigned>> lineColLoc = {{42, 7}, {24, 3}};
+  Operation *frontOp = createOp(&context,
+                                FileLineColLoc::get(&context, fileNames.front(),
+                                                    lineColLoc.front().first,
+                                                    lineColLoc.front().second),
+                                "firstOperation");
+  Operation *backOp = createOp(&context,
+                               FileLineColLoc::get(&context, fileNames.back(),
+                                                   lineColLoc.back().first,
+                                                   lineColLoc.back().second),
+                               "secondOperation");
+  Block block;
+  block.push_back(frontOp);
+  block.push_back(backOp);
+
+  FileLineColLocBreakpointManager breakpointManager;
+  executionCtx.addBreakpointManager(&breakpointManager);
+
+  // Test
+
+  executionCtx(counterInc, FileLineColLocTestingAction({&block}));
+  EXPECT_EQ(counter, 1);
+
+  auto *breakpoint = breakpointManager.addBreakpoint(
+      fileNames.front(), lineColLoc.front().first, lineColLoc.front().second);
+  counter = 0;
+  executionCtx(counterInc, FileLineColLocTestingAction({&block}));
+  EXPECT_EQ(counter, 0);
+  breakpoint->disable();
+  executionCtx(counterInc, FileLineColLocTestingAction({&block}));
+  EXPECT_EQ(counter, 1);
+
+  breakpoint = breakpointManager.addBreakpoint(
+      fileNames.back(), lineColLoc.back().first, lineColLoc.back().second);
+  counter = 0;
+  executionCtx(counterInc, FileLineColLocTestingAction({&block}));
+  EXPECT_EQ(counter, 0);
+  breakpoint->disable();
+  executionCtx(counterInc, FileLineColLocTestingAction({&block}));
+  EXPECT_EQ(counter, 1);
+}
+
+TEST(FileLineColLocBreakpointManager, RegionMatch) {
+  // This test will process a region and check various situation with
+  // a breakpoint hitting or not based on the location attached.
+  // When a breakpoint hits, the action is skipped and the counter is not
+  // incremented.
+  ExecutionContext executionCtx(
+      [](const ActionActiveStack *) { return ExecutionContext::Skip; });
+  int counter = 0;
+  auto counterInc = [&]() { counter++; };
+
+  // Setup
+
+  MLIRContext context;
+  StringRef fileName("plugh.xyzzy");
+  unsigned line = 42, col = 7;
+  Operation *containerOp =
+      createOp(&context, FileLineColLoc::get(&context, fileName, line, col),
+               "containerOperation", 1);
+  Region &region = containerOp->getRegion(0);
+
+  FileLineColLocBreakpointManager breakpointManager;
+  executionCtx.addBreakpointManager(&breakpointManager);
+
+  // Test
+  counter = 0;
+  executionCtx(counterInc, FileLineColLocTestingAction({&region}));
+  EXPECT_EQ(counter, 1);
+  auto *breakpoint = breakpointManager.addBreakpoint(fileName, line, col);
+  executionCtx(counterInc, FileLineColLocTestingAction({&region}));
+  EXPECT_EQ(counter, 1);
+  breakpoint->disable();
+  executionCtx(counterInc, FileLineColLocTestingAction({&region}));
+  EXPECT_EQ(counter, 2);
+
+  containerOp->destroy();
+}
+} // namespace