[Mlir-commits] [mlir] [mlir][scf] Declare ControlFlow as a dependent dialect (PR #216852)

Alessandro Potenza llvmlistbot at llvm.org
Mon Aug 17 14:50:53 PDT 2026


https://github.com/alepot55 created https://github.com/llvm/llvm-project/pull/216852

The canonicalization of a multi-block `scf.execute_region` creates `cf.br`
operations (`SCF.cpp`, `MultiBlockExecuteInliner`), but the SCF dialect does not
declare `cf::ControlFlowDialect` among its `dependentDialects`. On input where
nothing else has loaded the ControlFlow dialect, `mlir-opt --canonicalize` dies:

```mlir
llvm.func @f(%c: i1, %p: !llvm.ptr, %x: i64) {
  scf.execute_region {
    llvm.cond_br %c, ^bb1, ^bb2
  ^bb1:
    llvm.store %x, %p : i64, !llvm.ptr
    scf.yield
  ^bb2:
    scf.yield
  }
  llvm.return
}
```

```
LLVM ERROR: cf.br created with unregistered dialect. If this is intended, please
call allowUnregisteredDialects() on the MLIRContext, or use
-allow-unregistered-dialect with the MLIR tool used.
```

**The decisive control:** prepend an unrelated function that merely mentions
`cf.br` and the exact same `@f` canonicalizes cleanly. Correctness here depends
on whether some other operation in the file happened to load the ControlFlow
dialect first, which is the symptom a `dependentDialects` entry exists to prevent.

The repro needs an `llvm.func` rather than a `func.func` parent only because
Func's inliner extension already loads ControlFlow; that is why no in-tree test
caught this. I checked: no existing test exercises the broken path (no test file
runs canonicalization over `scf.` IR without either `func.func` or a `cf` op).

Build modes, stated precisely: with assertions the abort comes from the
`Operation` constructor (`Operation.cpp`, guarded by `#ifndef NDEBUG`); in a pure
NDEBUG build the op is built as unregistered and the failure surfaces in the pass
verifier (`Verifier.cpp`, not NDEBUG-guarded). Either way `mlir-opt --canonicalize`
fails on valid input, so the test fails before and passes after in every configuration.

No CMake change is needed: `MLIRControlFlowDialect` is already in `LINK_LIBS PUBLIC`
for `MLIRSCFDialect`, and `SCF.cpp` already includes `ControlFlowOps.h`. There is no
dependency cycle: `cf` depends only on `arith`, which declares none.

### Verified by execution

- Crash reproduces on `d4e78d7f5`; new test fails on unpatched `mlir-opt`, passes with the patch.
- No regression: `Dialect/SCF` 46/46, plus MemRef, Affine, Vector, XeGPU, Transforms (368 total).
- Loading `cf` cannot perturb unrelated output: `ControlFlowDialect::initialize` registers no
  dialect-level canonicalization patterns and no `OpAsmDialectInterface`, so no aliases change.

---

Assisted-by: Claude (Anthropic)

This patch was written with AI assistance, disclosed per the LLVM AI Tool Use Policy.
Everything reported above as verified was verified by building and running.


>From 97cf699537d869feca9c54991606173776724486 Mon Sep 17 00:00:00 2001
From: Alessandro Potenza <ap.alessandro.potenza at gmail.com>
Date: Mon, 17 Aug 2026 23:00:38 +0200
Subject: [PATCH] [mlir][scf] Declare ControlFlow as a dependent dialect

The canonicalization of a multi-block `scf.execute_region` inlines the
region into its parent and materializes `cf.br` operations for the region
entry edge and for every `scf.yield`. The SCF dialect never declared a
dependency on the ControlFlow dialect, so those `cf.br` ops are built even
when `cf` was never loaded into the MLIRContext.

The failure is masked in almost every test because something else in the
input happens to load `cf` first: any `cf` operation in the file loads it
at parse time, and loading the Func dialect loads it too, since the Func
inliner extension calls `getOrLoadDialect<cf::ControlFlowDialect>()`.
Outside of `func.func`, and with no `cf` op anywhere else in the file,
`mlir-opt --canonicalize` fails on valid input.

Add `cf::ControlFlowDialect` to the dialect's `dependentDialects` and add
a regression test that keeps the repro free of both `func.func` and `cf`.

`MLIRControlFlowDialect` is already a public link dependency of
`MLIRSCFDialect`, and `SCF.cpp` already includes `ControlFlowOps.h` before
the generated dialect definition, so no build changes are needed.
---
 mlir/include/mlir/Dialect/SCF/IR/SCFOps.td    |  4 ++-
 .../SCF/canonicalize-dependent-dialects.mlir  | 25 +++++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletion(-)
 create mode 100644 mlir/test/Dialect/SCF/canonicalize-dependent-dialects.mlir

diff --git a/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td b/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
index 1e7bba2cdad35..a0af0c59a37b5 100644
--- a/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
+++ b/mlir/include/mlir/Dialect/SCF/IR/SCFOps.td
@@ -42,7 +42,9 @@ def SCF_Dialect : Dialect {
     and then lowered to some final target like LLVM or SPIR-V.
   }];
 
-  let dependentDialects = ["arith::ArithDialect"];
+  // The canonicalization of a multi-block `scf.execute_region` materializes
+  // `cf.br` operations, so the ControlFlow dialect must always be loaded.
+  let dependentDialects = ["arith::ArithDialect", "cf::ControlFlowDialect"];
 }
 
 // Base class for SCF dialect ops.
diff --git a/mlir/test/Dialect/SCF/canonicalize-dependent-dialects.mlir b/mlir/test/Dialect/SCF/canonicalize-dependent-dialects.mlir
new file mode 100644
index 0000000000000..d5eb932eb59d9
--- /dev/null
+++ b/mlir/test/Dialect/SCF/canonicalize-dependent-dialects.mlir
@@ -0,0 +1,25 @@
+// RUN: mlir-opt %s --canonicalize | FileCheck %s
+
+// Canonicalizing a multi-block `scf.execute_region` materializes `cf.br` ops,
+// so the SCF dialect has to declare a dependency on the ControlFlow dialect.
+// This test deliberately contains no `cf` operation and does not use
+// `func.func`: parsing a `cf` operation, or loading the Func dialect (whose
+// inliner extension loads ControlFlow), would load the dialect for unrelated
+// reasons and mask the missing dependency.
+
+// CHECK-LABEL: llvm.func @multi_block_execute_region
+//   CHECK-NOT:   scf.execute_region
+//       CHECK:   llvm.cond_br
+//       CHECK:   llvm.store
+//       CHECK:   llvm.return
+llvm.func @multi_block_execute_region(%c: i1, %p: !llvm.ptr, %x: i64) {
+  scf.execute_region {
+    llvm.cond_br %c, ^bb1, ^bb2
+  ^bb1:
+    llvm.store %x, %p : i64, !llvm.ptr
+    scf.yield
+  ^bb2:
+    scf.yield
+  }
+  llvm.return
+}



More information about the Mlir-commits mailing list