[Mlir-commits] [mlir] [MLIR][LLVM] Add Windows EH funclet ops to LLVM dialect (PR #233290)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sun Oct 18 13:58:08 PDT 2026


https://github.com/R-Goc created https://github.com/llvm/llvm-project/pull/233290

* Add cleanuppad, cleanupret, catchswitch, catchpad, catchret ops to LLVMIR dialect.
* Implement LLVM IR export translation for funclet operations.

Assisted-by: Gemini:gemini-3.8-flash-high


>From 5a6f87d031da523cf7419757d03c7de9cdb29e77 Mon Sep 17 00:00:00 2001
From: R-Goc <ryszardgoc at gmail.com>
Date: Fri, 16 Oct 2026 21:17:25 +0200
Subject: [PATCH] [MLIR][LLVM] Add Windows EH funclet ops to LLVM dialect

* Add cleanuppad, cleanupret, catchswitch, catchpad, catchret ops to LLVMIR dialect.
* Implement LLVM IR export translation for funclet operations.

Assisted-by: Gemini:gemini-3.8-flash-high
---
 mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td   | 136 +++++++++++++++++-
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    |  95 +++++++++++-
 .../LLVMIR/LLVMToLLVMIRTranslation.cpp        |  55 +++++++
 3 files changed, 279 insertions(+), 7 deletions(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
index 9f658bc7a4b817..f4fdd802aa171d 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMOps.td
@@ -760,7 +760,8 @@ def LLVM_InvokeOp
                          DeclareOpInterfaceMethods<CallOpInterface>,
                          DeclareOpInterfaceMethods<SymbolUserOpInterface>,
                          DeclareOpInterfaceMethods<WeightedBranchOpInterface>,
-                         Terminator]> {
+                         Terminator,
+                         TokenConsumerTrait]> {
   let arguments = (ins
                    OptionalAttr<TypeAttrOf<LLVM_FunctionType>>:$var_callee_type,
                    OptionalAttr<FlatSymbolRefAttr>:$callee,
@@ -790,7 +791,19 @@ def LLVM_InvokeOp
       "Block*":$unwind, "ValueRange":$unwindOps)>,
     OpBuilder<(ins "LLVMFunctionType":$calleeType, "FlatSymbolRefAttr":$callee,
       "ValueRange":$ops, "Block*":$normal, "ValueRange":$normalOps,
-      "Block*":$unwind, "ValueRange":$unwindOps)>];
+      "Block*":$unwind, "ValueRange":$unwindOps)>,
+    OpBuilder<(ins "TypeRange":$resultTypes, "FlatSymbolRefAttr":$callee,
+                   "ValueRange":$args, "Block *":$normalDest, "Block *":$unwindDest,
+                   "ArrayRef<ValueRange>":$op_bundle_operands,
+                   "ArrayAttr":$op_bundle_tags), [{
+      build($_builder, $_state, resultTypes, /*var_callee_type=*/nullptr,
+            callee, args, /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr,
+            /*normalDestOperands=*/ValueRange{},
+            /*unwindDestOperands=*/ValueRange{},
+            /*branch_weights=*/nullptr, /*CConv=*/nullptr,
+            /*default_func_attrs=*/nullptr, /*uniform_work_group_size=*/nullptr,
+            op_bundle_operands, op_bundle_tags, normalDest, unwindDest);
+    }]>];
   let hasCustomAssemblyFormat = 1;
   let hasVerifier = 1;
   let extraClassDeclaration = [{
@@ -812,6 +825,98 @@ def LLVM_LandingpadOp : LLVM_Op<"landingpad"> {
   let hasVerifier = 1;
 }
 
+def LLVM_CleanupPadOp : LLVM_Op<"cleanuppad", [TokenConsumerTrait]> {
+  let summary = "LLVM cleanuppad instruction";
+  let arguments = (ins Token:$parentPad, Variadic<LLVM_Type>:$args);
+  let results = (outs Token:$res);
+  let assemblyFormat = [{
+    `within` $parentPad (`[` $args^ `:` type($args) `]`)? attr-dict
+  }];
+}
+
+def LLVM_CleanupRetOp : LLVM_TerminatorOp<"cleanupret", [TokenConsumerTrait]> {
+  let summary = "LLVM cleanupret instruction";
+  let arguments = (ins Token:$cleanupPad);
+  let successors = (successor VariadicSuccessor<AnySuccessor>:$unwindDest);
+  let builders = [
+    OpBuilder<(ins "Value":$cleanupPad, CArg<"Block *", "nullptr">:$unwindDest), [{
+      build($_builder, $_state, cleanupPad,
+            unwindDest ? BlockRange(unwindDest) : BlockRange());
+    }]>
+  ];
+  let hasCustomAssemblyFormat = 1;
+  let extraClassDeclaration = [{
+    ::mlir::SuccessorRange getUnwindDest() const {
+      return {(*this)->successor_begin(), (*this)->successor_end()};
+    }
+    bool hasUnwindDest() const { return !getUnwindDest().empty(); }
+    Block *getUnwindDestBlock() const {
+      return hasUnwindDest() ? getUnwindDest().front() : nullptr;
+    }
+  }];
+}
+
+def LLVM_CatchSwitchOp : LLVM_TerminatorOp<"catchswitch", [
+    TokenConsumerTrait
+]> {
+  let summary = "LLVM catchswitch instruction";
+  let arguments = (ins Token:$parentPad);
+  let results = (outs Token:$res);
+  let successors = (successor VariadicSuccessor<AnySuccessor>:$successors);
+  let builders = [
+    OpBuilder<(ins "Value":$parentPad, "ArrayRef<Block *>":$handlers,
+                   CArg<"Block *", "nullptr">:$unwindDest), [{
+      SmallVector<Block *> succs(handlers.begin(), handlers.end());
+      if (unwindDest)
+        succs.push_back(unwindDest);
+      build($_builder, $_state, $_builder.getType<TokenType>(), parentPad, succs);
+      if (unwindDest)
+        $_state.addAttribute("has_unwind_dest", $_builder.getUnitAttr());
+    }]>
+  ];
+  let hasCustomAssemblyFormat = 1;
+  let extraClassDeclaration = [{
+    ::mlir::SuccessorRange getSuccessors() const {
+      return (*this)->getSuccessors();
+    }
+    bool hasUnwindDest() const { return (*this)->hasAttr("has_unwind_dest"); }
+    Block *getUnwindDest() const {
+      return hasUnwindDest() ? getSuccessors().back() : nullptr;
+    }
+    ::mlir::SuccessorRange getHandlers() const {
+      if (hasUnwindDest())
+        return getSuccessors().drop_back();
+      return getSuccessors();
+    }
+    unsigned getNumHandlers() const {
+      return getHandlers().size();
+    }
+  }];
+}
+
+def LLVM_CatchPadOp : LLVM_Op<"catchpad", [TokenConsumerTrait]> {
+  let summary = "LLVM catchpad instruction";
+  let arguments = (ins Token:$catchSwitch, Variadic<LLVM_Type>:$args);
+  let results = (outs Token:$res);
+  let assemblyFormat = [{
+    `within` $catchSwitch (`[` $args^ `:` type($args) `]`)? attr-dict
+  }];
+  let builders = [
+    OpBuilder<(ins "Value":$catchSwitch), [{
+      build($_builder, $_state, catchSwitch, ValueRange());
+    }]>
+  ];
+}
+
+def LLVM_CatchRetOp : LLVM_TerminatorOp<"catchret", [TokenConsumerTrait]> {
+  let summary = "LLVM catchret instruction";
+  let arguments = (ins Token:$catchPad);
+  let successors = (successor AnySuccessor:$dest);
+  let assemblyFormat = [{
+    `from` $catchPad `to` $dest attr-dict
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // VAArgOp
 //===----------------------------------------------------------------------===//
@@ -841,7 +946,8 @@ def LLVM_CallOp
           "call", [AttrSizedOperandSegments,
                    DeclareOpInterfaceMethods<FastmathFlagsInterface>,
                    DeclareOpInterfaceMethods<CallOpInterface>,
-                   DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
+                   DeclareOpInterfaceMethods<SymbolUserOpInterface>,
+                   TokenConsumerTrait]> {
   let summary = "Call to an LLVM function.";
   let description = [{
     In LLVM IR, functions may return either 0 or 1 value. LLVM IR dialect
@@ -930,7 +1036,29 @@ def LLVM_CallOp
     OpBuilder<(ins "LLVMFunctionType":$calleeType, "FlatSymbolRefAttr":$callee,
                    CArg<"ValueRange", "{}">:$args)>,
     OpBuilder<(ins "LLVMFunctionType":$calleeType, "StringRef":$callee,
-                   CArg<"ValueRange", "{}">:$args)>
+                   CArg<"ValueRange", "{}">:$args)>,
+    OpBuilder<(ins "TypeRange":$resultTypes, "FlatSymbolRefAttr":$callee,
+                   "ValueRange":$args, "ArrayRef<ValueRange>":$op_bundle_operands,
+                   "ArrayAttr":$op_bundle_tags), [{
+      build($_builder, $_state, resultTypes, /*var_callee_type=*/nullptr,
+            callee, args, /*fastmathFlags=*/nullptr, /*CConv=*/nullptr,
+            /*TailCallKind=*/nullptr, /*memory_effects=*/nullptr,
+            /*convergent=*/nullptr, /*no_unwind=*/nullptr, /*will_return=*/nullptr,
+            /*noreturn=*/nullptr, /*returns_twice=*/nullptr, /*hot=*/nullptr,
+            /*cold=*/nullptr, /*noduplicate=*/nullptr,
+            /*no_caller_saved_registers=*/nullptr, /*nocallback=*/nullptr,
+            /*modular_format=*/nullptr, /*nobuiltins=*/nullptr,
+            /*allocsize=*/nullptr, /*optsize=*/nullptr, /*minsize=*/nullptr,
+            /*builtin=*/nullptr, /*nobuiltin=*/nullptr,
+            /*save_reg_params=*/nullptr, /*zero_call_used_regs=*/nullptr,
+            /*trap_func_name=*/nullptr, /*default_func_attrs=*/nullptr,
+            /*uniform_work_group_size=*/nullptr, op_bundle_operands,
+            op_bundle_tags, /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr,
+            /*no_inline=*/nullptr, /*always_inline=*/nullptr,
+            /*inline_hint=*/nullptr, /*access_groups=*/nullptr,
+            /*alias_scopes=*/nullptr, /*noalias_scopes=*/nullptr,
+            /*tbaa=*/nullptr);
+    }]>
   ];
   let hasVerifier = 1;
   let hasCustomAssemblyFormat = 1;
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 1b0cc7bdb87990..7b9fed58836873 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -1713,10 +1713,11 @@ LogicalResult InvokeOp::verify() {
   if (unwindDest->empty())
     return emitError("must have at least one operation in unwind destination");
 
-  // In unwind destination, first operation must be LandingpadOp
-  if (!isa<LandingpadOp>(unwindDest->front()))
+  // In unwind destination, first operation must be LandingpadOp or funclet pad.
+  if (!isa<LandingpadOp, CleanupPadOp, CatchSwitchOp, NoneTokenOp>(
+          unwindDest->front()))
     return emitError("first operation in unwind destination should be a "
-                     "llvm.landingpad operation");
+                     "llvm.landingpad or funclet pad operation");
 
   if (failed(verifyOperandBundles(*this)))
     return failure();
@@ -1969,6 +1970,94 @@ ParseResult LandingpadOp::parse(OpAsmParser &parser, OperationState &result) {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// CleanupRetOp
+//===----------------------------------------------------------------------===//
+
+void CleanupRetOp::print(OpAsmPrinter &p) {
+  p << ' ' << getCleanupPad();
+  if (hasUnwindDest())
+    p << " unwind " << getUnwindDest().front();
+  p.printOptionalAttrDict((*this)->getAttrs());
+}
+
+ParseResult CleanupRetOp::parse(OpAsmParser &parser, OperationState &result) {
+  OpAsmParser::UnresolvedOperand cleanupPad;
+  if (parser.parseOperand(cleanupPad))
+    return failure();
+  Type tokenType = parser.getBuilder().getType<TokenType>();
+  if (parser.resolveOperand(cleanupPad, tokenType, result.operands))
+    return failure();
+  if (succeeded(parser.parseOptionalKeyword("unwind"))) {
+    Block *unwindDest = nullptr;
+    if (parser.parseSuccessor(unwindDest))
+      return failure();
+    result.addSuccessors(unwindDest);
+  }
+  if (parser.parseOptionalAttrDict(result.attributes))
+    return failure();
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// CatchSwitchOp
+//===----------------------------------------------------------------------===//
+
+void CatchSwitchOp::print(OpAsmPrinter &p) {
+  p << " within " << getParentPad() << " [";
+  llvm::interleaveComma(getHandlers(), p, [&](Block *b) { p << b; });
+  p << "] unwind ";
+  if (hasUnwindDest())
+    p << getUnwindDest();
+  else
+    p << "to caller";
+  p.printOptionalAttrDict((*this)->getAttrs(), {"has_unwind_dest"});
+}
+
+ParseResult CatchSwitchOp::parse(OpAsmParser &parser, OperationState &result) {
+  if (parser.parseKeyword("within"))
+    return failure();
+
+  OpAsmParser::UnresolvedOperand parentPad;
+  if (parser.parseOperand(parentPad))
+    return failure();
+  Type tokenType = parser.getBuilder().getType<TokenType>();
+  if (parser.resolveOperand(parentPad, tokenType, result.operands))
+    return failure();
+
+  SmallVector<Block *> handlers;
+  if (parser.parseCommaSeparatedList(OpAsmParser::Delimiter::Square, [&]() {
+        Block *handler = nullptr;
+        if (parser.parseSuccessor(handler))
+          return failure();
+        handlers.push_back(handler);
+        return success();
+      }))
+    return failure();
+  for (Block *handler : handlers)
+    result.addSuccessors(handler);
+
+  if (parser.parseKeyword("unwind"))
+    return failure();
+
+  if (succeeded(parser.parseOptionalKeyword("to"))) {
+    if (parser.parseKeyword("caller"))
+      return failure();
+  } else {
+    Block *unwindDest = nullptr;
+    if (parser.parseSuccessor(unwindDest))
+      return failure();
+    result.addSuccessors(unwindDest);
+    result.addAttribute("has_unwind_dest", parser.getBuilder().getUnitAttr());
+  }
+
+  if (parser.parseOptionalAttrDict(result.attributes))
+    return failure();
+
+  result.addTypes(tokenType);
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // ExtractValueOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
index 7833d8857699ce..cfda8c5865f43f 100644
--- a/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
+++ b/mlir/lib/Target/LLVMIR/Dialect/LLVMIR/LLVMToLLVMIRTranslation.cpp
@@ -763,6 +763,61 @@ convertOperationImpl(Operation &opInst, llvm::IRBuilderBase &builder,
     return success();
   }
 
+  if (auto cpOp = dyn_cast<LLVM::CleanupPadOp>(opInst)) {
+    llvm::Value *parentPad = moduleTranslation.lookupValue(cpOp.getParentPad());
+    SmallVector<llvm::Value *> args =
+        moduleTranslation.lookupValues(cpOp.getArgs());
+    llvm::CleanupPadInst *pad = builder.CreateCleanupPad(parentPad, args);
+    moduleTranslation.mapValue(cpOp.getResult(), pad);
+    return success();
+  }
+
+  if (auto crOp = dyn_cast<LLVM::CleanupRetOp>(opInst)) {
+    auto *cleanupPad = cast<llvm::CleanupPadInst>(
+        moduleTranslation.lookupValue(crOp.getCleanupPad()));
+    llvm::BasicBlock *unwindBB = nullptr;
+    if (!crOp.getUnwindDest().empty())
+      unwindBB = moduleTranslation.lookupBlock(crOp.getUnwindDest().front());
+    llvm::CleanupReturnInst *ret =
+        builder.CreateCleanupRet(cleanupPad, unwindBB);
+    if (unwindBB)
+      moduleTranslation.mapBranch(&opInst, ret);
+    return success();
+  }
+
+  if (auto csOp = dyn_cast<LLVM::CatchSwitchOp>(opInst)) {
+    llvm::Value *parentPad = moduleTranslation.lookupValue(csOp.getParentPad());
+    llvm::BasicBlock *unwindBB = nullptr;
+    if (csOp.hasUnwindDest())
+      unwindBB = moduleTranslation.lookupBlock(csOp.getUnwindDest());
+    llvm::CatchSwitchInst *cs =
+        builder.CreateCatchSwitch(parentPad, unwindBB, csOp.getNumHandlers());
+    for (Block *handler : csOp.getHandlers())
+      cs->addHandler(moduleTranslation.lookupBlock(handler));
+    moduleTranslation.mapValue(csOp.getResult(), cs);
+    moduleTranslation.mapBranch(&opInst, cs);
+    return success();
+  }
+
+  if (auto cpOp = dyn_cast<LLVM::CatchPadOp>(opInst)) {
+    llvm::Value *catchSwitch =
+        moduleTranslation.lookupValue(cpOp.getCatchSwitch());
+    SmallVector<llvm::Value *> args =
+        moduleTranslation.lookupValues(cpOp.getArgs());
+    llvm::CatchPadInst *pad = builder.CreateCatchPad(catchSwitch, args);
+    moduleTranslation.mapValue(cpOp.getResult(), pad);
+    return success();
+  }
+
+  if (auto crOp = dyn_cast<LLVM::CatchRetOp>(opInst)) {
+    auto *catchPad = cast<llvm::CatchPadInst>(
+        moduleTranslation.lookupValue(crOp.getCatchPad()));
+    llvm::BasicBlock *destBB = moduleTranslation.lookupBlock(crOp.getDest());
+    llvm::CatchReturnInst *ret = builder.CreateCatchRet(catchPad, destBB);
+    moduleTranslation.mapBranch(&opInst, ret);
+    return success();
+  }
+
   // Emit branches.  We need to look up the remapped blocks and ignore the
   // block arguments that were transformed into PHI nodes.
   if (auto brOp = dyn_cast<LLVM::BrOp>(opInst)) {



More information about the Mlir-commits mailing list