[Mlir-commits] [llvm] [mlir] msvc abi/mlir funclet ops (PR #233257)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sun Oct 18 09:14:34 PDT 2026


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

- **[LLVMABI] Implement Windows x86_64 TargetInfo and SRetAfterThis support**
- **[MLIR][LLVM] Add Windows EH funclet ops to LLVM dialect**


>From ab83ea96f7b1520f797292bffa47b9cdbe5d762c Mon Sep 17 00:00:00 2001
From: R-Goc <ryszardgoc at gmail.com>
Date: Fri, 16 Oct 2026 21:11:08 +0200
Subject: [PATCH 1/2] [LLVMABI] Implement Windows x86_64 TargetInfo and
 SRetAfterThis support

* Add SRetAfterThis classification to ArgInfo.
* Add IsInstanceMethod property to FunctionInfo.
* Implement WinX86_64TargetInfo for Windows x86_64 argument/return classification.
* Add unit tests in llvm/unittests/ABI/WinX86_64TargetInfoTest.cpp.

Assisted-by: Gemini:gemini-3.8-flash-high
---
 llvm/include/llvm/ABI/FunctionInfo.h          |  27 +-
 llvm/include/llvm/ABI/TargetInfo.h            |   4 +
 llvm/include/llvm/ABI/Types.h                 |  14 +
 llvm/lib/ABI/Targets/X86.cpp                  |  81 ++++++
 llvm/unittests/ABI/CMakeLists.txt             |   1 +
 llvm/unittests/ABI/FunctionInfoTest.cpp       |   5 +
 llvm/unittests/ABI/TypesTest.cpp              |  14 +
 .../unittests/ABI/WinX86_64TargetInfoTest.cpp | 250 ++++++++++++++++++
 8 files changed, 391 insertions(+), 5 deletions(-)
 create mode 100644 llvm/unittests/ABI/WinX86_64TargetInfoTest.cpp

diff --git a/llvm/include/llvm/ABI/FunctionInfo.h b/llvm/include/llvm/ABI/FunctionInfo.h
index 9edd588c480018..065d46ea638d62 100644
--- a/llvm/include/llvm/ABI/FunctionInfo.h
+++ b/llvm/include/llvm/ABI/FunctionInfo.h
@@ -83,11 +83,12 @@ class ArgInfo {
   bool CanBeFlattened : 1;
   unsigned NeededIntRegs : 3;
   unsigned NeededSseRegs : 3;
+  bool SRetAfterThis : 1;
 
   ArgInfo(Kind K = Direct)
       : TheKind(K), SignExt(false), ZeroExt(false), IndirectByVal(false),
         IndirectRealign(false), CanBeFlattened(false), NeededIntRegs(0),
-        NeededSseRegs(0) {}
+        NeededSseRegs(0), SRetAfterThis(false) {}
 
 public:
   /// \param T The type to coerce to. If null, the argument's original type is
@@ -111,7 +112,7 @@ class ArgInfo {
     return AI;
   }
 
-  static ArgInfo getExtend(const Type *T) {
+  static ArgInfo getExtend(const Type *T, bool Signed) {
     assert(T && "Type cannot be null");
     assert(T->isInteger() && "Unexpected type - only integers can be extended");
 
@@ -120,8 +121,7 @@ class ArgInfo {
     AI.Alignment = std::nullopt;
     AI.DirectAttr.Offset = 0;
 
-    const IntegerType *IntTy = cast<IntegerType>(T);
-    if (IntTy->isSigned())
+    if (Signed)
       AI.setSignExt();
     else
       AI.setZeroExt();
@@ -129,6 +129,13 @@ class ArgInfo {
     return AI;
   }
 
+  static ArgInfo getExtend(const Type *T) {
+    assert(T && "Type cannot be null");
+    assert(T->isInteger() && "Unexpected type - only integers can be extended");
+    const IntegerType *IntTy = cast<IntegerType>(T);
+    return getExtend(T, IntTy->isSigned());
+  }
+
   /// Realign: the caller couldn't guarantee sufficient alignment - the callee
   /// must copy the argument to a properly aligned temporary before use.
   static ArgInfo getIndirect(Align Align, bool ByVal, unsigned AddrSpace = 0,
@@ -268,6 +275,9 @@ class ArgInfo {
     return !SignExt && !ZeroExt;
   }
 
+  bool isSRetAfterThis() const { return SRetAfterThis; }
+  void setSRetAfterThis(bool Value) { SRetAfterThis = Value; }
+
   const Type *getCoerceToType() const {
     assert((isDirect() || isExtend() || isCoerceAndExpand()) &&
            "Invalid Kind!");
@@ -297,6 +307,8 @@ struct ArgEntry {
 
   ArgEntry(const Type *T) : ABIType(T), Info(ArgInfo::getDirect()) {}
   ArgEntry(const Type *T, ArgInfo A) : ABIType(T), Info(A) {}
+
+  const Type *getType() const { return ABIType; }
 };
 
 /// Whether a signature accepts arguments beyond its declared parameters, and
@@ -338,11 +350,13 @@ class FunctionInfo final : private TrailingObjects<FunctionInfo, ArgEntry> {
   unsigned NumArgs;
   CallingConv::ID CC = CallingConv::C;
   RequiredArgs Required;
+  bool IsInstanceMethod : 1;
 
   FunctionInfo(CallingConv::ID CC, const Type *RetTy, unsigned NumArguments,
                RequiredArgs Required)
       : ReturnType(RetTy), ReturnInfo(ArgInfo::getDirect()),
-        NumArgs(NumArguments), CC(CC), Required(Required) {}
+        NumArgs(NumArguments), CC(CC), Required(Required),
+        IsInstanceMethod(false) {}
 
   friend class TrailingObjects;
 
@@ -369,6 +383,9 @@ class FunctionInfo final : private TrailingObjects<FunctionInfo, ArgEntry> {
 
   CallingConv::ID getCallingConvention() const { return CC; }
 
+  bool isInstanceMethod() const { return IsInstanceMethod; }
+  void setIsInstanceMethod(bool Value) { IsInstanceMethod = Value; }
+
   bool isVariadic() const { return Required.allowsOptionalArgs(); }
 
   unsigned getNumRequiredArgs() const {
diff --git a/llvm/include/llvm/ABI/TargetInfo.h b/llvm/include/llvm/ABI/TargetInfo.h
index 859eca06ba785a..4d85ed85ddbb5a 100644
--- a/llvm/include/llvm/ABI/TargetInfo.h
+++ b/llvm/include/llvm/ABI/TargetInfo.h
@@ -188,6 +188,10 @@ LLVM_ABI std::unique_ptr<TargetInfo>
 createX86_64TargetInfo(TypeBuilder &TB, X86AVXABILevel AVXLevel,
                        bool Has64BitPointers, const X86ABICompatInfo &Compat);
 
+LLVM_ABI std::unique_ptr<TargetInfo>
+createWinX86_64TargetInfo(TypeBuilder &TB, X86AVXABILevel AVXLevel,
+                          const ABICompatInfo &Compat);
+
 enum class AArch64ABIKind {
   AAPCS = 0,
   DarwinPCS,
diff --git a/llvm/include/llvm/ABI/Types.h b/llvm/include/llvm/ABI/Types.h
index 746e107b3a32ca..3c95d1f6950e6c 100644
--- a/llvm/include/llvm/ABI/Types.h
+++ b/llvm/include/llvm/ABI/Types.h
@@ -17,13 +17,19 @@
 #include "llvm/ADT/APFloat.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/BitmaskEnum.h"
+#include "llvm/ADT/bit.h"
 #include "llvm/Support/Alignment.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/TypeSize.h"
 
 namespace llvm {
+
+/// Returns the largest power of two less than or equal to the given value.
+inline uint64_t PowerOf2Floor(uint64_t A) { return llvm::bit_floor(A); }
+
 namespace abi {
 
 enum class TypeKind {
@@ -482,6 +488,14 @@ class TypeBuilder {
         IntegerType(BitWidth, Align, Signed, IsBitInt);
   }
 
+  const IntegerType *getIntegerType(TypeSize Size, bool Signed = false) {
+    uint64_t BitWidth = Size.getFixedValue();
+    return getIntegerType(
+        BitWidth,
+        Align(llvm::PowerOf2Floor(std::max<uint64_t>(1, BitWidth / 8))),
+        Signed);
+  }
+
   const FloatType *getFloatType(const fltSemantics &Semantics, Align Align) {
     return new (Allocator.Allocate<FloatType>()) FloatType(Semantics, Align);
   }
diff --git a/llvm/lib/ABI/Targets/X86.cpp b/llvm/lib/ABI/Targets/X86.cpp
index d57c5a0cf516f4..4c8571ac37e6f9 100644
--- a/llvm/lib/ABI/Targets/X86.cpp
+++ b/llvm/lib/ABI/Targets/X86.cpp
@@ -1386,5 +1386,86 @@ createX86_64TargetInfo(TypeBuilder &TB, X86AVXABILevel AVXLevel,
                                             Compat);
 }
 
+namespace {
+
+class WinX86_64TargetInfo : public TargetInfo {
+  [[maybe_unused]] X86AVXABILevel AVXLevel;
+  ABICompatInfo CompatInfo;
+
+  ArgInfo classify(const Type *Ty, bool IsReturn, bool IsInstanceMethod) const {
+    if (Ty->isVoid())
+      return ArgInfo::getIgnore();
+
+    if (IsReturn && IsInstanceMethod && isAggregateTypeForABI(Ty)) {
+      ArgInfo AI =
+          getNaturalAlignIndirect(Ty, getAllocaAddrSpace(), /*ByVal=*/false);
+      AI.setSRetAfterThis(true);
+      return AI;
+    }
+
+    if (const auto *RT = dyn_cast<RecordType>(Ty)) {
+      if (!RT->canPassInRegisters() || RT->hasFlexibleArrayMember())
+        return getNaturalAlignIndirect(Ty, getAllocaAddrSpace(), /*ByVal=*/false);
+
+      uint64_t Width = RT->getSizeInBits().getFixedValue();
+      if (Width > 64 || !llvm::isPowerOf2_64(Width))
+        return getNaturalAlignIndirect(Ty, getAllocaAddrSpace(), /*ByVal=*/false);
+
+      const Type *CoerceTy =
+          TB.getIntegerType(TypeSize::getFixed(Width), /*IsSigned=*/false);
+      return ArgInfo::getDirect(CoerceTy);
+    }
+
+    if (const auto *IT = dyn_cast<IntegerType>(Ty)) {
+      // MSVC WinX86_64 ABI only extends 1-bit booleans; other small integers are passed directly.
+      if (IT->getSizeInBits().getFixedValue() == 1)
+        return ArgInfo::getExtend(Ty, IT->isSigned());
+      if (IT->getSizeInBits().getFixedValue() > 64)
+        return getNaturalAlignIndirect(Ty, getAllocaAddrSpace(), /*ByVal=*/false);
+      return ArgInfo::getDirect();
+    }
+
+    if (Ty->isFloat() || Ty->isPointer() || Ty->isVector())
+      return ArgInfo::getDirect();
+
+    if (isAggregateTypeForABI(Ty)) {
+      uint64_t Width = Ty->getSizeInBits().getFixedValue();
+      if (Width > 64 || !llvm::isPowerOf2_64(Width))
+        return getNaturalAlignIndirect(Ty, getAllocaAddrSpace(), /*ByVal=*/false);
+      const Type *CoerceTy =
+          TB.getIntegerType(TypeSize::getFixed(Width), /*IsSigned=*/false);
+      return ArgInfo::getDirect(CoerceTy);
+    }
+
+    return ArgInfo::getDirect();
+  }
+
+public:
+  WinX86_64TargetInfo(TypeBuilder &TB, X86AVXABILevel AVXLevel,
+                      const ABICompatInfo &Compat)
+      : TargetInfo(TB), AVXLevel(AVXLevel), CompatInfo(Compat) {}
+
+  const ABICompatInfo &getABICompatInfo() const override {
+    return CompatInfo;
+  }
+
+  void computeInfo(FunctionInfo &FI) const override {
+    FI.getReturnInfo() = classify(FI.getReturnType(), /*IsReturn=*/true,
+                                  FI.isInstanceMethod());
+    for (auto &Arg : FI.arguments())
+      Arg.Info = classify(Arg.getType(), /*IsReturn=*/false,
+                          /*IsInstanceMethod=*/false);
+  }
+};
+
+
+} // namespace
+
+std::unique_ptr<TargetInfo>
+createWinX86_64TargetInfo(TypeBuilder &TB, X86AVXABILevel AVXLevel,
+                          const ABICompatInfo &Compat) {
+  return std::make_unique<WinX86_64TargetInfo>(TB, AVXLevel, Compat);
+}
+
 } // namespace abi
 } // namespace llvm
diff --git a/llvm/unittests/ABI/CMakeLists.txt b/llvm/unittests/ABI/CMakeLists.txt
index eb50b88b87d2df..efa5b339603fe6 100644
--- a/llvm/unittests/ABI/CMakeLists.txt
+++ b/llvm/unittests/ABI/CMakeLists.txt
@@ -11,5 +11,6 @@ add_llvm_unittest(ABITests
   TargetInfoTest.cpp
   AMDGPUTargetInfoTest.cpp
   X86TargetInfoTest.cpp
+  WinX86_64TargetInfoTest.cpp
   TypesTest.cpp
   )
diff --git a/llvm/unittests/ABI/FunctionInfoTest.cpp b/llvm/unittests/ABI/FunctionInfoTest.cpp
index 0a855edab9bb40..7db77800fa3739 100644
--- a/llvm/unittests/ABI/FunctionInfoTest.cpp
+++ b/llvm/unittests/ABI/FunctionInfoTest.cpp
@@ -132,6 +132,11 @@ TEST_F(FunctionInfoTest, IndirectAliasedSurvivesFunctionInfo) {
   EXPECT_EQ(Info.getIndirectAlign(), llvm::Align(8));
 }
 
+TEST_F(FunctionInfoTest, ArgEntryGetType) {
+  ArgEntry Entry(I32);
+  EXPECT_EQ(Entry.getType(), I32);
+}
+
 #if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
 TEST_F(FunctionInfoTest, CanBeFlattenedIsDirectOnly) {
   EXPECT_DEATH((void)ArgInfo::getIgnore().getCanBeFlattened(), "Invalid Kind");
diff --git a/llvm/unittests/ABI/TypesTest.cpp b/llvm/unittests/ABI/TypesTest.cpp
index 588d1f914db12b..f37592f01a3a92 100644
--- a/llvm/unittests/ABI/TypesTest.cpp
+++ b/llvm/unittests/ABI/TypesTest.cpp
@@ -214,6 +214,20 @@ TEST_F(ABITypesTest, ScalarABISize) {
   EXPECT_EQ(TB.getComplexType(F32, Align(4))->getABISizeInBits(), 64u);
 }
 
+TEST_F(ABITypesTest, IntegerTypeFromTypeSize) {
+  // Power-of-2 byte sizes.
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(8))->getAlignment(), Align(1));
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(16))->getAlignment(), Align(2));
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(32))->getAlignment(), Align(4));
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(64))->getAlignment(), Align(8));
+
+  // Non-byte and non-power-of-2 byte sizes should not assert on alignment.
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(1))->getAlignment(), Align(1));
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(24))->getAlignment(), Align(2));
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(40))->getAlignment(), Align(4));
+  EXPECT_EQ(TB.getIntegerType(TypeSize::getFixed(48))->getAlignment(), Align(4));
+}
+
 // A bool element takes one bit and a one-bit _BitInt element a whole byte.  The
 // total is rounded up to a power of two of at least 8 bits.
 TEST_F(ABITypesTest, VectorABISize) {
diff --git a/llvm/unittests/ABI/WinX86_64TargetInfoTest.cpp b/llvm/unittests/ABI/WinX86_64TargetInfoTest.cpp
new file mode 100644
index 00000000000000..84e416887452f4
--- /dev/null
+++ b/llvm/unittests/ABI/WinX86_64TargetInfoTest.cpp
@@ -0,0 +1,250 @@
+//===- WinX86_64TargetInfoTest.cpp - Windows x86-64 ABI unit tests --------===//
+//
+// 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 "llvm/ABI/FunctionInfo.h"
+#include "llvm/ABI/TargetInfo.h"
+#include "llvm/ABI/Types.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/IR/CallingConv.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Allocator.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using namespace llvm;
+
+using ABIType = llvm::abi::Type;
+using llvm::abi::ABICompatInfo;
+using llvm::abi::ArgInfo;
+using llvm::abi::createWinX86_64TargetInfo;
+using llvm::abi::FieldInfo;
+using llvm::abi::FunctionInfo;
+using llvm::abi::RecordFlags;
+using llvm::abi::StructPacking;
+using llvm::abi::TargetInfo;
+using llvm::abi::TypeBuilder;
+using llvm::abi::X86AVXABILevel;
+
+class WinX86_64TargetInfoTest : public ::testing::Test {
+protected:
+  llvm::BumpPtrAllocator Alloc;
+  TypeBuilder TB;
+  const ABIType *Void;
+  const ABIType *I1;
+  const ABIType *I8;
+  const ABIType *I16;
+  const ABIType *I32;
+  const ABIType *I64;
+  const ABIType *F32;
+  const ABIType *F64;
+
+  WinX86_64TargetInfoTest()
+      : TB(Alloc),
+        Void(TB.getVoidType()),
+        I1(TB.getIntegerType(1, llvm::Align(1), /*Signed=*/false)),
+        I8(TB.getIntegerType(8, llvm::Align(1), /*Signed=*/true)),
+        I16(TB.getIntegerType(16, llvm::Align(2), /*Signed=*/true)),
+        I32(TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true)),
+        I64(TB.getIntegerType(64, llvm::Align(8), /*Signed=*/true)),
+        F32(TB.getFloatType(llvm::APFloat::IEEEsingle(), llvm::Align(4))),
+        F64(TB.getFloatType(llvm::APFloat::IEEEdouble(), llvm::Align(8))) {}
+
+  std::unique_ptr<TargetInfo> target() const {
+    return createWinX86_64TargetInfo(const_cast<TypeBuilder &>(TB),
+                                     X86AVXABILevel::None, ABICompatInfo());
+  }
+
+  const ABIType *structOfBytes(uint64_t NumBytes,
+                               RecordFlags Flags = RecordFlags::CanPassInRegisters) {
+    llvm::SmallVector<FieldInfo, 8> Fields;
+    for (uint64_t I = 0; I < NumBytes; ++I)
+      Fields.emplace_back(I8, I * 8);
+    return TB.getRecordType(Fields, llvm::TypeSize::getFixed(NumBytes * 8),
+                            llvm::Align(1), llvm::Align(1),
+                            StructPacking::Default, {}, {}, Flags);
+  }
+
+  const ArgInfo &classifyReturn(const ABIType *RetTy,
+                                std::unique_ptr<FunctionInfo> &FI,
+                                std::unique_ptr<TargetInfo> &TI,
+                                bool IsInstanceMethod = false) {
+    TI = target();
+    FI = FunctionInfo::create(llvm::CallingConv::C, RetTy, {});
+    FI->setIsInstanceMethod(IsInstanceMethod);
+    TI->computeInfo(*FI);
+    return FI->getReturnInfo();
+  }
+};
+
+static void expectDirectInteger(const ArgInfo &Info, unsigned Bits) {
+  ASSERT_TRUE(Info.isDirect());
+  const ABIType *Coerce = Info.getCoerceToType();
+  ASSERT_NE(Coerce, nullptr);
+  const auto *IT = llvm::dyn_cast<llvm::abi::IntegerType>(Coerce);
+  ASSERT_NE(IT, nullptr);
+  EXPECT_EQ(IT->getSizeInBits().getFixedValue(), Bits);
+}
+
+TEST_F(WinX86_64TargetInfoTest, ScalarReturns) {
+  std::unique_ptr<FunctionInfo> FI;
+  std::unique_ptr<TargetInfo> TI;
+
+  // Void -> ignore.
+  EXPECT_TRUE(classifyReturn(Void, FI, TI).isIgnore());
+
+  // i1 -> extend (zero extend).
+  const ArgInfo &I1Info = classifyReturn(I1, FI, TI);
+  EXPECT_TRUE(I1Info.isExtend());
+  EXPECT_TRUE(I1Info.isZeroExt());
+
+  // i8 -> direct.
+  const ArgInfo &I8Info = classifyReturn(I8, FI, TI);
+  EXPECT_TRUE(I8Info.isDirect());
+  EXPECT_EQ(I8Info.getCoerceToType(), nullptr);
+
+  // i16 -> direct.
+  const ArgInfo &I16Info = classifyReturn(I16, FI, TI);
+  EXPECT_TRUE(I16Info.isDirect());
+  EXPECT_EQ(I16Info.getCoerceToType(), nullptr);
+
+  // i32 -> direct.
+  const ArgInfo &I32Info = classifyReturn(I32, FI, TI);
+  EXPECT_TRUE(I32Info.isDirect());
+  EXPECT_EQ(I32Info.getCoerceToType(), nullptr);
+
+  // i64 -> direct.
+  const ArgInfo &I64Info = classifyReturn(I64, FI, TI);
+  EXPECT_TRUE(I64Info.isDirect());
+  EXPECT_EQ(I64Info.getCoerceToType(), nullptr);
+
+  // Float -> direct.
+  const ArgInfo &F32Info = classifyReturn(F32, FI, TI);
+  EXPECT_TRUE(F32Info.isDirect());
+  EXPECT_EQ(F32Info.getCoerceToType(), nullptr);
+
+  // Double -> direct.
+  const ArgInfo &F64Info = classifyReturn(F64, FI, TI);
+  EXPECT_TRUE(F64Info.isDirect());
+  EXPECT_EQ(F64Info.getCoerceToType(), nullptr);
+}
+
+TEST_F(WinX86_64TargetInfoTest, DirectStructReturns) {
+  std::unique_ptr<FunctionInfo> FI;
+  std::unique_ptr<TargetInfo> TI;
+
+  // 1-byte struct -> direct, coerced to i8.
+  const ABIType *S1 = structOfBytes(1);
+  expectDirectInteger(classifyReturn(S1, FI, TI), 8);
+
+  // 2-byte struct -> direct, coerced to i16.
+  const ABIType *S2 = structOfBytes(2);
+  expectDirectInteger(classifyReturn(S2, FI, TI), 16);
+
+  // 4-byte struct -> direct, coerced to i32.
+  const ABIType *S4 = structOfBytes(4);
+  expectDirectInteger(classifyReturn(S4, FI, TI), 32);
+
+  // 8-byte struct -> direct, coerced to i64.
+  const ABIType *S8 = structOfBytes(8);
+  expectDirectInteger(classifyReturn(S8, FI, TI), 64);
+}
+
+TEST_F(WinX86_64TargetInfoTest, IndirectStructReturns) {
+  std::unique_ptr<FunctionInfo> FI;
+  std::unique_ptr<TargetInfo> TI;
+
+  for (uint64_t Bytes : {3u, 5u, 12u, 16u}) {
+    const ABIType *S = structOfBytes(Bytes);
+    const ArgInfo &Info = classifyReturn(S, FI, TI);
+    EXPECT_TRUE(Info.isIndirect());
+    EXPECT_FALSE(Info.getIndirectByVal());
+    EXPECT_FALSE(Info.isSRetAfterThis());
+  }
+}
+
+TEST_F(WinX86_64TargetInfoTest, NonTrivialStructReturn) {
+  std::unique_ptr<FunctionInfo> FI;
+  std::unique_ptr<TargetInfo> TI;
+
+  // 4-byte struct with RecordFlags::None (cannot pass in registers).
+  const ABIType *S = structOfBytes(4, RecordFlags::None);
+  const ArgInfo &Info = classifyReturn(S, FI, TI);
+  EXPECT_TRUE(Info.isIndirect());
+  EXPECT_FALSE(Info.getIndirectByVal());
+}
+
+TEST_F(WinX86_64TargetInfoTest, InstanceMethodStructReturn) {
+  std::unique_ptr<FunctionInfo> FI;
+  std::unique_ptr<TargetInfo> TI;
+
+  // Even a 4-byte struct returned from an instance method is indirect with SRetAfterThis = true.
+  const ABIType *S4 = structOfBytes(4);
+  const ArgInfo &Info =
+      classifyReturn(S4, FI, TI, /*IsInstanceMethod=*/true);
+  EXPECT_TRUE(Info.isIndirect());
+  EXPECT_FALSE(Info.getIndirectByVal());
+  EXPECT_TRUE(Info.isSRetAfterThis());
+}
+
+TEST_F(WinX86_64TargetInfoTest, ScalarAndStructArguments) {
+  std::unique_ptr<FunctionInfo> FI;
+  std::unique_ptr<TargetInfo> TI = target();
+
+  const ABIType *S4 = structOfBytes(4);
+  const ABIType *S16 = structOfBytes(16);
+
+  FI = FunctionInfo::create(llvm::CallingConv::C, Void,
+                            {I1, I8, I16, I32, I64, S4, S16});
+  TI->computeInfo(*FI);
+
+  // Arg 0: i1 -> extend (zero extend).
+  EXPECT_EQ(FI->getArgInfo(0).getType(), I1);
+  const ArgInfo &Arg0 = FI->getArgInfo(0).Info;
+  EXPECT_TRUE(Arg0.isExtend());
+  EXPECT_TRUE(Arg0.isZeroExt());
+
+  // Arg 1: i8 -> direct.
+  EXPECT_EQ(FI->getArgInfo(1).getType(), I8);
+  const ArgInfo &Arg1 = FI->getArgInfo(1).Info;
+  EXPECT_TRUE(Arg1.isDirect());
+  EXPECT_EQ(Arg1.getCoerceToType(), nullptr);
+
+  // Arg 2: i16 -> direct.
+  EXPECT_EQ(FI->getArgInfo(2).getType(), I16);
+  const ArgInfo &Arg2 = FI->getArgInfo(2).Info;
+  EXPECT_TRUE(Arg2.isDirect());
+  EXPECT_EQ(Arg2.getCoerceToType(), nullptr);
+
+  // Arg 3: i32 -> direct.
+  EXPECT_EQ(FI->getArgInfo(3).getType(), I32);
+  const ArgInfo &Arg3 = FI->getArgInfo(3).Info;
+  EXPECT_TRUE(Arg3.isDirect());
+  EXPECT_EQ(Arg3.getCoerceToType(), nullptr);
+
+  // Arg 4: i64 -> direct.
+  EXPECT_EQ(FI->getArgInfo(4).getType(), I64);
+  const ArgInfo &Arg4 = FI->getArgInfo(4).Info;
+  EXPECT_TRUE(Arg4.isDirect());
+  EXPECT_EQ(Arg4.getCoerceToType(), nullptr);
+
+  // Arg 5: 4-byte struct -> direct, coerced to i32.
+  EXPECT_EQ(FI->getArgInfo(5).getType(), S4);
+  const ArgInfo &Arg5 = FI->getArgInfo(5).Info;
+  expectDirectInteger(Arg5, 32);
+
+  // Arg 6: 16-byte struct -> indirect, ByVal = false.
+  EXPECT_EQ(FI->getArgInfo(6).getType(), S16);
+  const ArgInfo &Arg6 = FI->getArgInfo(6).Info;
+  EXPECT_TRUE(Arg6.isIndirect());
+  EXPECT_FALSE(Arg6.getIndirectByVal());
+}
+
+} // namespace

>From 80a02559367a8d3b349f0a073b032d028f8479a8 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 2/2] [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..4883f363638ad1 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