[clang] [CIR] Add verifier for CIR try op (PR #181419)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Fri Feb 13 12:54:49 PST 2026


https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/181419

This adds a verifier to enforce the requirement that every catch handler in a cir.try operation must begin with a cir.catch_param operation.

>From dceb54570a59b0af3401ffb951cf3e0349c9bf09 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Thu, 12 Feb 2026 18:09:17 -0800
Subject: [PATCH] [CIR] Add verifier for CIR try op

This adds a verifier to enforce the requirement that every catch handler
in a cir.try operation must begin with a cir.catch_param operation.
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td |  1 +
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp      | 32 ++++++++++++++
 clang/test/CIR/IR/invalid-try-catch.cir      | 45 ++++++++++++++++++++
 clang/test/CIR/IR/try-catch.cir              |  8 ++++
 4 files changed, 86 insertions(+)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 7085580d99718..496033d34d137 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -6279,6 +6279,7 @@ def CIR_TryOp : CIR_Op<"try",[
     }]>
   ];
 
+  let hasVerifier = 1;
   let hasLLVMLowering = false;
 }
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 4a25407d0f3cf..a8ef838111c29 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -3774,6 +3774,38 @@ mlir::ValueRange cir::TryOp::getSuccessorInputs(RegionSuccessor successor) {
                               : ValueRange();
 }
 
+LogicalResult cir::TryOp::verify() {
+  mlir::ArrayAttr handlerTypes = getHandlerTypes();
+  if (!handlerTypes) {
+    if (!getHandlerRegions().empty())
+      return emitOpError(
+          "handler regions must be empty when no handler types are present");
+    return success();
+  }
+
+  mlir::MutableArrayRef<mlir::Region> handlerRegions = getHandlerRegions();
+
+  // The parser and builder won't allow this to happen, but the loop below
+  // relies on the sizes being the same, so we check it here.
+  if (handlerRegions.size() != handlerTypes.size())
+    return emitOpError(
+        "number of handler regions and handler types must match");
+
+  for (const auto &[typeAttr, handlerRegion] :
+       llvm::zip(handlerTypes, handlerRegions)) {
+    // The unwind region does not require a cir.catch_param.
+    if (mlir::isa<cir::UnwindAttr>(typeAttr))
+      continue;
+
+    mlir::Block &entryBlock = handlerRegion.front();
+    if (entryBlock.empty() || !mlir::isa<cir::CatchParamOp>(entryBlock.front()))
+      return emitOpError(
+          "catch handler region must start with 'cir.catch_param'");
+  }
+
+  return success();
+}
+
 static void
 printTryHandlerRegions(mlir::OpAsmPrinter &printer, cir::TryOp op,
                        mlir::MutableArrayRef<mlir::Region> handlerRegions,
diff --git a/clang/test/CIR/IR/invalid-try-catch.cir b/clang/test/CIR/IR/invalid-try-catch.cir
index c11abb9dd449b..18002e6db4467 100644
--- a/clang/test/CIR/IR/invalid-try-catch.cir
+++ b/clang/test/CIR/IR/invalid-try-catch.cir
@@ -136,6 +136,51 @@ cir.func dso_local @invalid_catch_all_with_type_info() {
 
 // -----
 
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+module {
+
+cir.global "private" constant external @_ZTIi : !cir.ptr<!u8i>
+
+cir.func dso_local @catch_handler_missing_catch_param() {
+  cir.scope {
+    // expected-error @below {{catch handler region must start with 'cir.catch_param'}}
+    cir.try {
+      cir.yield
+    } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] {
+      cir.yield
+    } unwind {
+      cir.resume
+    }
+  }
+  cir.return
+}
+
+}
+
+// -----
+
+!void = !cir.void
+
+module {
+
+cir.func dso_local @catch_all_handler_missing_catch_param() {
+  cir.scope {
+    // expected-error @below {{catch handler region must start with 'cir.catch_param'}}
+    cir.try {
+      cir.yield
+    } catch all {
+      cir.yield
+    }
+  }
+  cir.return
+}
+
+}
+
+// -----
+
 module {
 
 cir.func dso_local @invalid_unwind_with_catch_all() {
diff --git a/clang/test/CIR/IR/try-catch.cir b/clang/test/CIR/IR/try-catch.cir
index 8ffce067ba043..3a5fe1a1573cc 100644
--- a/clang/test/CIR/IR/try-catch.cir
+++ b/clang/test/CIR/IR/try-catch.cir
@@ -1,6 +1,8 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 !u8i = !cir.int<u, 8>
+!s32i = !cir.int<s, 32>
+!void = !cir.void
 
 module {
 
@@ -12,6 +14,7 @@ cir.func dso_local @empty_try_block_with_catch_all() {
     cir.try {
       cir.yield
     } catch all {
+      %0 = cir.catch_param : !cir.ptr<!void>
       cir.yield
     }
   }
@@ -23,6 +26,7 @@ cir.func dso_local @empty_try_block_with_catch_all() {
 // CHECK:     cir.try {
 // CHECK:       cir.yield
 // CHECK:     } catch all {
+// CHECK:       %[[CP:.*]] = cir.catch_param : !cir.ptr<!void>
 // CHECK:       cir.yield
 // CHECK:     }
 // CHECK:   }
@@ -56,8 +60,10 @@ cir.func dso_local @empty_try_block_with_catch_ist() {
     cir.try {
       cir.yield
     } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] {
+      %0 = cir.catch_param : !cir.ptr<!s32i>
       cir.yield
     } catch [type #cir.global_view<@_ZTIPKc> : !cir.ptr<!u8i>] {
+      %0 = cir.catch_param : !cir.ptr<!cir.ptr<!u8i>>
       cir.yield
     } unwind {
       cir.yield
@@ -71,8 +77,10 @@ cir.func dso_local @empty_try_block_with_catch_ist() {
 // CHECK:     cir.try {
 // CHECK:       cir.yield
 // CHECK:     } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] {
+// CHECK:       %[[CP1:.*]] = cir.catch_param : !cir.ptr<!s32i>
 // CHECK:       cir.yield
 // CHECK:     } catch [type #cir.global_view<@_ZTIPKc> : !cir.ptr<!u8i>] {
+// CHECK:       %[[CP2:.*]] = cir.catch_param : !cir.ptr<!cir.ptr<!u8i>>
 // CHECK:       cir.yield
 // CHECK:     } unwind {
 // CHECK:       cir.yield



More information about the cfe-commits mailing list