[clang] [CIR][EH] Preserved exception pointer address spaces in throw calls (PR #227595)

Kunal Dubey via cfe-commits cfe-commits at lists.llvm.org
Thu Oct 1 06:23:02 PDT 2026


https://github.com/xakep8 updated https://github.com/llvm/llvm-project/pull/227595

>From bcbf2e6c7cb9ff188db75e59dfb363e071eae49d Mon Sep 17 00:00:00 2001
From: Kunal Dubey <xakep8 at protonmail.com>
Date: Wed, 30 Sep 2026 12:42:49 +0530
Subject: [PATCH] [CIR][EH] Preserved exception pointer address spaces in throw
 calls

CIR previously stored the exception type information and destructor as
symbol names. When these were converted into pointers, they could be
placed in the wrong address space.

Passed them as typed pointer values intead and preserved their address
spaces when creating the __cxa_throw call. Also verified that all throw
calls in a module use compatible pointer types and added HIP regression
tests.
---
 .../clang/CIR/Dialect/IR/CIRDataLayout.h      |   9 ++
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  52 ++++----
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp |  30 +++--
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |   3 +-
 clang/lib/CIR/CodeGen/CIRGenModule.h          |   9 ++
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  65 +++++++++-
 .../Dialect/Transforms/CIRTransformUtils.cpp  |   4 +-
 .../CIR/Dialect/Transforms/EHABILowering.cpp  |  81 +++++-------
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  22 +---
 .../CodeGen/cleanup-scope-throw-caught.cpp    |   2 +-
 .../CodeGen/cleanup-throw-from-cleanup.cpp    |   4 +-
 .../test/CIR/CodeGen/rtti-member-pointer.cpp  |   2 +-
 clang/test/CIR/CodeGen/rtti-qualfn.cpp        |   2 +-
 clang/test/CIR/CodeGen/ternary-throw.cpp      |  38 +++---
 clang/test/CIR/CodeGen/throws.cpp             |  23 ++--
 clang/test/CIR/CodeGen/try-catch.cpp          |   2 +-
 clang/test/CIR/CodeGenHIP/throw-no-rtti.hip   |  83 ++++++++++++
 clang/test/CIR/IR/invalid-throw.cir           | 122 +++++++++++++++++-
 clang/test/CIR/IR/throw.cir                   |  12 ++
 19 files changed, 422 insertions(+), 143 deletions(-)
 create mode 100644 clang/test/CIR/CodeGenHIP/throw-no-rtti.hip

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h b/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
index 71214c798afbf..7b19ad1b12e40 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDataLayout.h
@@ -129,6 +129,15 @@ class CIRDataLayout {
       return {};
     return cir::TargetAddressSpaceAttr::get(ctx, allocaASAttr.getUInt());
   }
+
+  mlir::ptr::MemorySpaceAttrInterface
+  getGlobalAddrSpace(mlir::MLIRContext *ctx) const {
+    auto globalASAttr = mlir::dyn_cast_if_present<mlir::IntegerAttr>(
+        layout.getGlobalMemorySpace());
+    if (!globalASAttr || globalASAttr.getUInt() == 0)
+      return {};
+    return cir::TargetAddressSpaceAttr::get(ctx, globalASAttr.getUInt());
+  }
 };
 
 } // namespace cir
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index bc9af2210a048..7d6a3bec811a4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -8249,20 +8249,23 @@ def CIR_VAArgOp : CIR_Op<"va_arg"> {
 //===----------------------------------------------------------------------===//
 
 // Common base class shared by `cir.throw` and its EH counterpart
-// `cir.try_throw`. Both operations carry the same operands and attributes
-// (an optional exception pointer, an optional RTTI symbol and an optional
-// destructor symbol) and use the same verifier logic; subclasses contribute
-// any additional traits, successors and assembly format pieces.
+// `cir.try_throw`. A normal throw carries an exception pointer, an RTTI pointer
+// and a destructor pointer as SSA operands. Null RTTI and destructor pointers
+// are represented by typed null values. The zero-operand form represents a
+// rethrow. Since every normal throw lowers to the same `__cxa_throw` symbol,
+// the address spaces of these three operands must be consistent across the
+// module. Subclasses contribute any additional traits, successors and assembly
+// format pieces.
 class CIR_ThrowOpBase<string mnemonic, list<Trait> traits = []>
-    : CIR_Op<mnemonic, traits> {
+    : CIR_Op<mnemonic, !listconcat(traits, [AttrSizedOperandSegments])> {
   let arguments = (ins
     Optional<CIR_PointerType>:$exception_ptr,
-    OptionalAttr<FlatSymbolRefAttr>:$type_info,
-    OptionalAttr<FlatSymbolRefAttr>:$dtor
+    Optional<CIR_PointerType>:$type_info,
+    Optional<CIR_PointerType>:$dtor
   );
 
   let extraClassDeclaration = [{
-    bool rethrows() { return getNumOperands() == 0; }
+    bool rethrows() { return !getExceptionPtr(); }
   }];
 
   let hasVerifier = 1;
@@ -8278,12 +8281,12 @@ def CIR_ThrowOp : CIR_ThrowOpBase<"throw"> {
     This operation is equivalent to either __cxa_throw or __cxa_rethrow,
     depending on the arguments.
 
-    The absense of arguments for `cir.throw` means it rethrows.
+    The absence of arguments for `cir.throw` means it rethrows.
 
-    For the no-rethrow version, it must have at least two operands, the RTTI
-    information, a pointer to the exception object (likely allocated via
-    `cir.alloc_exception`) and finally an optional dtor, which might run as
-    part of this operation.
+    For the non-rethrow version, it has three operands: a pointer to the
+    exception object (likely allocated via `cir.alloc_exception`), the RTTI
+    pointer and the destructor pointer. The latter two may be typed null
+    pointers.
 
     Example:
 
@@ -8302,15 +8305,16 @@ def CIR_ThrowOp : CIR_ThrowOpBase<"throw"> {
       // Store string addr for "Division by zero condition!"
       cir.store %string_addr, %exception_addr : !cir.ptr<!s8i>,
         !cir.ptr<!cir.ptr<!s8i>>
-      cir.throw %exception_addr : !cir.ptr<!cir.ptr<!u8i>>,
-        @_ZTIPKc
+      %type_info = cir.const #cir.global_view<@_ZTIPKc> : !cir.ptr<!u8i>
+      %dtor = cir.const #cir.ptr<null> : !cir.ptr<!void>
+      cir.throw %exception_addr, %type_info, %dtor :
+        !cir.ptr<!cir.ptr<!u8i>>, !cir.ptr<!u8i>, !cir.ptr<!void>
     ```
   }];
 
   let assemblyFormat = [{
-    ($exception_ptr^ `:` type($exception_ptr))?
-    (`,` $type_info^)?
-    (`,` $dtor^)?
+    ($exception_ptr^ `,` $type_info `,` $dtor `:`
+      type($exception_ptr) `,` type($type_info) `,` type($dtor))?
     attr-dict
   }];
 }
@@ -8330,8 +8334,8 @@ def CIR_TryThrowOp : CIR_ThrowOpBase<"try_throw", [Terminator]> {
     analogous to how `cir.try_call` is the EH counterpart of `cir.call`.
 
     Like `cir.throw`, the absence of operands means rethrow. With operands,
-    it carries the same exception pointer, type info, and optional
-    destructor as `cir.throw`.
+    it carries the same exception pointer, type-info pointer, and destructor
+    pointer as `cir.throw`.
 
     This operation is produced by the FlattenCFG pass for `cir.throw`
     operations that appear inside a cleanup scope or try region. It is
@@ -8341,7 +8345,8 @@ def CIR_TryThrowOp : CIR_ThrowOpBase<"try_throw", [Terminator]> {
     Example:
 
     ```
-    cir.try_throw %exception_addr : !cir.ptr<!s32i>, @_ZTIi
+    cir.try_throw %exception_addr, %type_info, %dtor :
+        !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
         ^normalDest, ^unwindDest
     ^normalDest:
       cir.unreachable
@@ -8356,9 +8361,8 @@ def CIR_TryThrowOp : CIR_ThrowOpBase<"try_throw", [Terminator]> {
   );
 
   let assemblyFormat = [{
-    ($exception_ptr^ `:` type($exception_ptr))?
-    (`,` $type_info^)?
-    (`,` $dtor^)?
+    ($exception_ptr^ `,` $type_info `,` $dtor `:`
+      type($exception_ptr) `,` type($type_info) `,` type($dtor))?
     $normalDest `,` $unwindDest
     attr-dict
   }];
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index c7bf59871e691..43a56a6e40dc5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -1806,8 +1806,8 @@ mlir::Value CIRGenItaniumCXXABI::getCXXDestructorImplicitParam(
 // to the throw block and create a block for the remaining operations.
 static void insertThrowAndSplit(mlir::OpBuilder &builder, mlir::Location loc,
                                 mlir::Value exceptionPtr = {},
-                                mlir::FlatSymbolRefAttr typeInfo = {},
-                                mlir::FlatSymbolRefAttr dtor = {}) {
+                                mlir::Value typeInfo = {},
+                                mlir::Value dtor = {}) {
   mlir::Block *currentBlock = builder.getInsertionBlock();
   mlir::Region *region = currentBlock->getParent();
 
@@ -1864,11 +1864,16 @@ void CIRGenItaniumCXXABI::emitThrow(CIRGenFunction &cgf,
   CharUnits exnAlign = cgf.getContext().getExnObjectAlignment();
   cgf.emitAnyExprToExn(e->getSubExpr(), Address(exceptionPtr, exnAlign));
 
-  // Get the RTTI symbol address.
-  auto typeInfo = mlir::cast<cir::GlobalViewAttr>(
+  // Materialize the RTTI address as an SSA value. Device compilation may
+  // suppress RTTI emission, in which case this is a typed null pointer in the
+  // target's global address space instead of a view of an RTTI global.
+  auto typeInfoAttr = mlir::cast<mlir::TypedAttr>(
       cgm.getAddrOfRTTIDescriptor(subExprLoc, clangThrowType,
                                   /*forEH=*/true));
-  assert(!typeInfo.getIndices() && "expected no indirection");
+  if (auto globalView = mlir::dyn_cast<cir::GlobalViewAttr>(typeInfoAttr))
+    assert(!globalView.getIndices() && "expected no indirection");
+  mlir::Value typeInfo =
+      cir::ConstantOp::create(builder, subExprLoc, typeInfoAttr);
 
   // The address of the destructor.
   //
@@ -1878,21 +1883,26 @@ void CIRGenItaniumCXXABI::emitThrow(CIRGenFunction &cgf,
   // Lowering pass to skip passing the trivial function.
   //
   const auto *cxxrd = clangThrowType->getAsCXXRecordDecl();
-  mlir::FlatSymbolRefAttr dtor{};
+  cir::PointerType dtorTy = builder.getVoidPtrTy();
+  mlir::Value dtor = builder.getNullPtr(dtorTy, subExprLoc);
   if (cxxrd && !cxxrd->hasTrivialDestructor()) {
     // __cxa_throw is declared to take its destructor as void (*)(void *). We
     // must match that if function pointers can be authenticated with a
     // discriminator based on their type.
     assert(!cir::MissingFeatures::pointerAuthentication());
     CXXDestructorDecl *dtorD = cxxrd->getDestructor();
-    dtor = mlir::FlatSymbolRefAttr::get(
-        cgm.getAddrOfCXXStructor(GlobalDecl(dtorD, Dtor_Complete))
-            .getSymNameAttr());
+    cir::FuncOp dtorFunc =
+        cgm.getAddrOfCXXStructor(GlobalDecl(dtorD, Dtor_Complete));
+    cir::PointerType dtorFuncPtrTy =
+        builder.getPointerTo(dtorFunc.getFunctionType());
+    dtor = cir::GetGlobalOp::create(builder, subExprLoc, dtorFuncPtrTy,
+                                    dtorFunc.getSymName());
+    dtor = builder.createBitcast(dtor, dtorTy);
   }
 
   // Now throw the exception.
   mlir::Location loc = cgf.getLoc(e->getSourceRange());
-  insertThrowAndSplit(builder, loc, exceptionPtr, typeInfo.getSymbol(), dtor);
+  insertThrowAndSplit(builder, loc, exceptionPtr, typeInfo, dtor);
 }
 
 CIRGenCXXABI *clang::CIRGen::CreateCIRGenItaniumCXXABI(CIRGenModule &cgm) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index cc0f36a0e7992..82e22918ce3f8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -4080,7 +4080,8 @@ mlir::Attribute CIRGenModule::getAddrOfRTTIDescriptor(mlir::Location loc,
   // FIXME: should we even be calling this method if RTTI is disabled
   // and it's not for EH?
   if (!shouldEmitRTTI(forEh))
-    return builder.getConstNullPtrAttr(builder.getUInt8PtrTy());
+    return builder.getConstNullPtrAttr(forEh ? getGlobalsUInt8PtrTy()
+                                             : builder.getUInt8PtrTy());
 
   if (forEh && ty->isObjCObjectPointerType() &&
       langOpts.ObjCRuntime.isGNUFamily()) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h
index 3fb95f346536d..31d56304e3d4c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.h
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.h
@@ -600,6 +600,15 @@ class CIRGenModule : public CIRGenTypeCache {
              getTriple().isNVPTX());
   }
 
+  /// Return an i8 pointer in the target's address space for global variables.
+  /// RTTI descriptors use this type, including the null descriptor passed to
+  /// the exception runtime when device compilation suppresses RTTI emission.
+  cir::PointerType getGlobalsUInt8PtrTy() {
+    return builder.getPointerTo(
+        builder.getUInt8Ty(),
+        getDataLayout().getGlobalAddrSpace(&getMLIRContext()));
+  }
+
   /// Emit type info if type of an expression is a variably modified
   /// type. Also emit proper debug info for cast types.
   void emitExplicitCastExprType(const ExplicitCastExpr *e,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 4b9e0c7f0f544..30fd73df4cbca 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -34,6 +34,8 @@
 #include "llvm/ADT/TypeSwitch.h"
 #include "llvm/Support/LogicalResult.h"
 
+#include <array>
+
 using namespace mlir;
 using namespace cir;
 
@@ -4676,17 +4678,68 @@ ParseResult cir::InlineAsmOp::parse(OpAsmParser &parser,
 // ThrowOp / TryThrowOp
 //===----------------------------------------------------------------------===//
 
+using ThrowAddressSpaces = std::array<mlir::ptr::MemorySpaceAttrInterface, 3>;
+
+template <typename ThrowOpTy>
+static std::optional<ThrowAddressSpaces> getThrowAddressSpaces(ThrowOpTy op) {
+  if (!op.getExceptionPtr() || !op.getTypeInfo() || !op.getDtor())
+    return std::nullopt;
+
+  return ThrowAddressSpaces{
+      mlir::cast<cir::PointerType>(op.getExceptionPtr().getType())
+          .getAddrSpace(),
+      mlir::cast<cir::PointerType>(op.getTypeInfo().getType()).getAddrSpace(),
+      mlir::cast<cir::PointerType>(op.getDtor().getType()).getAddrSpace()};
+}
+
+static std::optional<ThrowAddressSpaces>
+getThrowAddressSpaces(mlir::Operation *op) {
+  if (auto throwOp = mlir::dyn_cast<cir::ThrowOp>(op))
+    return getThrowAddressSpaces(throwOp);
+  if (auto tryThrowOp = mlir::dyn_cast<cir::TryThrowOp>(op))
+    return getThrowAddressSpaces(tryThrowOp);
+  return std::nullopt;
+}
+
 template <typename ThrowOpTy>
 static mlir::LogicalResult verifyThrowOpImpl(ThrowOpTy op) {
-  if (op.rethrows())
+  bool hasExceptionPtr = static_cast<bool>(op.getExceptionPtr());
+  bool hasTypeInfo = static_cast<bool>(op.getTypeInfo());
+  bool hasDtor = static_cast<bool>(op.getDtor());
+
+  if (!hasExceptionPtr && !hasTypeInfo && !hasDtor)
     return mlir::success();
 
-  if (op.getNumOperands() != 0) {
-    if (op.getTypeInfo())
-      return mlir::success();
-    return op.emitOpError() << "'type_info' symbol attribute missing";
-  }
+  if (!hasExceptionPtr || !hasTypeInfo || !hasDtor)
+    return op.emitOpError()
+           << "must have either no operands for a rethrow or exception, "
+              "type_info, and destructor pointer operands for a throw";
+
+  auto module = op->template getParentOfType<mlir::ModuleOp>();
+  if (!module)
+    return op.emitOpError("expects an enclosing module");
+
+  mlir::Operation *firstThrow = nullptr;
+  std::optional<ThrowAddressSpaces> expectedAddressSpaces;
+  module.walk([&](mlir::Operation *candidate) {
+    expectedAddressSpaces = getThrowAddressSpaces(candidate);
+    if (!expectedAddressSpaces)
+      return mlir::WalkResult::advance();
+    firstThrow = candidate;
+    return mlir::WalkResult::interrupt();
+  });
+
+  assert(expectedAddressSpaces && firstThrow &&
+         "the operation being verified provides a throw signature");
+  if (*expectedAddressSpaces == *getThrowAddressSpaces(op))
+    return mlir::success();
 
+  mlir::InFlightDiagnostic diag = op.emitOpError(
+      "operand address spaces must match the first non-rethrow cir.throw or "
+      "cir.try_throw in the module");
+  diag.attachNote(firstThrow->getLoc())
+      << "the module's __cxa_throw address-space signature is established "
+         "here";
   return mlir::failure();
 }
 
diff --git a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
index 1c272527603fa..5132100eafede 100644
--- a/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CIRTransformUtils.cpp
@@ -124,8 +124,8 @@ mlir::Block *cir::replaceThrowWithTryThrow(cir::ThrowOp throwOp,
   // Build the try_throw to replace the original throw.
   rewriter.setInsertionPoint(throwOp);
   auto tryThrowOp = cir::TryThrowOp::create(
-      rewriter, loc, throwOp.getExceptionPtr(), throwOp.getTypeInfoAttr(),
-      throwOp.getDtorAttr(), normalDest, unwindDest);
+      rewriter, loc, throwOp.getExceptionPtr(), throwOp.getTypeInfo(),
+      throwOp.getDtor(), normalDest, unwindDest);
 
   // The shared inherent state is already set by TryThrowOp::create. Preserve
   // only auxiliary metadata here.
diff --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
index 77ce7434c8ed3..221168c5eb2d4 100644
--- a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -139,7 +139,9 @@ class ItaniumEHLowering : public EHABILowering {
 
   void ensureRuntimeDecls(mlir::Location loc);
   void ensureClangCallTerminate(mlir::Location loc);
-  void ensureCxaThrowDecl(mlir::Location loc);
+  void ensureCxaThrowDecl(mlir::Location loc, cir::PointerType exceptionPtrType,
+                          cir::PointerType typeInfoPtrType,
+                          cir::PointerType dtorPtrType);
   void ensureCxaRethrowDecl(mlir::Location loc);
   void ensureCxaCallUnexpectedDecl(mlir::Location loc);
   mlir::Block *buildTerminateBlock(cir::FuncOp funcOp, mlir::Location loc);
@@ -265,12 +267,20 @@ void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
 
 /// Ensure the __cxa_throw runtime function is declared in the module.
 ///
-///   void __cxa_throw(void *exception, void *type_info, void *dtor);
-void ItaniumEHLowering::ensureCxaThrowDecl(mlir::Location loc) {
+///   void __cxa_throw(void *exception, global void *type_info, void *dtor);
+///
+/// The type-info pointer may use a target-specific global address space, so
+/// derive the declaration from the typed throw operands instead of assuming
+/// that all three pointers use the default address space.
+void ItaniumEHLowering::ensureCxaThrowDecl(mlir::Location loc,
+                                           cir::PointerType exceptionPtrType,
+                                           cir::PointerType typeInfoPtrType,
+                                           cir::PointerType dtorPtrType) {
   if (cxaThrowFunc)
     return;
-  auto throwFuncTy = cir::FuncType::get({voidPtrType, voidPtrType, voidPtrType},
-                                        voidType, /*isVarArg=*/false);
+  auto throwFuncTy = cir::FuncType::get(
+      {exceptionPtrType, typeInfoPtrType, dtorPtrType}, voidType,
+      /*isVarArg=*/false);
   cxaThrowFunc =
       getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_throw", throwFuncTy);
 }
@@ -906,9 +916,9 @@ ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
 }
 
 /// Lower a cir.try_throw to a cir.try_call of __cxa_throw (or
-/// __cxa_rethrow for the no-operand rethrow form). Materializes the
-/// type_info and dtor pointers from their symbol attributes, bitcasting
-/// each to !cir.ptr<!void> as required by the runtime function signature.
+/// __cxa_rethrow for the no-operand rethrow form). Bitcast each typed pointer
+/// operand to a void pointer in the same address space to match the runtime
+/// function signature.
 mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
   mlir::Location loc = op.getLoc();
   mlir::Block *normalDest = op.getNormalDest();
@@ -924,45 +934,22 @@ mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
     return mlir::success();
   }
 
-  ensureCxaThrowDecl(loc);
-
-  // Bitcast the exception pointer to void* if necessary.
-  mlir::Value exnPtr = op.getExceptionPtr();
-  if (exnPtr.getType() != voidPtrType)
-    exnPtr = cir::CastOp::create(builder, loc, voidPtrType,
-                                 cir::CastKind::bitcast, exnPtr);
-
-  // Materialize the type_info pointer, looking up the typed symbol in the
-  // module so we get the correct pointer type for cir.get_global, then
-  // bitcasting to void* to match the runtime signature.
-  mlir::FlatSymbolRefAttr typeInfoAttr = op.getTypeInfoAttr();
-  auto typeInfoGlobal = mod.lookupSymbol<cir::GlobalOp>(typeInfoAttr);
-  if (!typeInfoGlobal)
-    return op.emitError("type_info symbol not found in module");
-  auto typeInfoPtrTy = cir::PointerType::get(typeInfoGlobal.getSymType());
-  mlir::Value typeInfo = cir::GetGlobalOp::create(builder, loc, typeInfoPtrTy,
-                                                  typeInfoAttr.getValue());
-  if (typeInfo.getType() != voidPtrType)
-    typeInfo = cir::CastOp::create(builder, loc, voidPtrType,
-                                   cir::CastKind::bitcast, typeInfo);
-
-  // Materialize the dtor pointer (or null if no dtor).
-  mlir::Value dtor;
-  if (mlir::FlatSymbolRefAttr dtorAttr = op.getDtorAttr()) {
-    auto dtorFunc = mod.lookupSymbol<cir::FuncOp>(dtorAttr);
-    if (!dtorFunc)
-      return op.emitError("dtor symbol not found in module");
-    auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
-    dtor =
-        cir::GetGlobalOp::create(builder, loc, dtorPtrTy, dtorAttr.getValue());
-    if (dtor.getType() != voidPtrType)
-      dtor = cir::CastOp::create(builder, loc, voidPtrType,
-                                 cir::CastKind::bitcast, dtor);
-  } else {
-    dtor = cir::ConstantOp::create(
-        builder, loc,
-        cir::ConstPtrAttr::get(voidPtrType, builder.getI64IntegerAttr(0)));
-  }
+  auto castToVoidPtr = [&](mlir::Value value) -> mlir::Value {
+    auto ptrType = mlir::cast<cir::PointerType>(value.getType());
+    auto targetType = cir::PointerType::get(voidType, ptrType.getAddrSpace());
+    if (value.getType() == targetType)
+      return value;
+    return cir::CastOp::create(builder, loc, targetType, cir::CastKind::bitcast,
+                               value);
+  };
+
+  mlir::Value exnPtr = castToVoidPtr(op.getExceptionPtr());
+  mlir::Value typeInfo = castToVoidPtr(op.getTypeInfo());
+  mlir::Value dtor = castToVoidPtr(op.getDtor());
+
+  ensureCxaThrowDecl(loc, mlir::cast<cir::PointerType>(exnPtr.getType()),
+                     mlir::cast<cir::PointerType>(typeInfo.getType()),
+                     mlir::cast<cir::PointerType>(dtor.getType()));
 
   cir::TryCallOp::create(
       builder, loc, mlir::FlatSymbolRefAttr::get(cxaThrowFunc), voidType,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index d3a1b3fb1a02c..e94541553bea1 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4691,29 +4691,19 @@ mlir::LogicalResult CIRToLLVMThrowOpLowering::matchAndRewrite(
     return mlir::success();
   }
 
-  auto llvmPtrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
+  mlir::Value exceptionPtr = adaptor.getExceptionPtr();
+  mlir::Value typeInfo = adaptor.getTypeInfo();
+  mlir::Value dtor = adaptor.getDtor();
   auto fnTy = mlir::LLVM::LLVMFunctionType::get(
-      voidTy, {llvmPtrTy, llvmPtrTy, llvmPtrTy});
+      voidTy, {exceptionPtr.getType(), typeInfo.getType(), dtor.getType()});
 
-  // Get or create `declare void @__cxa_throw(ptr, ptr, ptr)`
+  // Get or create the __cxa_throw declaration with the operand pointer types.
   const llvm::StringRef fnName = "__cxa_throw";
   createLLVMFuncOpIfNotExist(rewriter, symbolTables, op, fnName, fnTy);
 
-  mlir::Value typeInfo = mlir::LLVM::AddressOfOp::create(
-      rewriter, loc, mlir::LLVM::LLVMPointerType::get(rewriter.getContext()),
-      adaptor.getTypeInfoAttr());
-
-  mlir::Value dtor;
-  if (op.getDtor()) {
-    dtor = mlir::LLVM::AddressOfOp::create(rewriter, loc, llvmPtrTy,
-                                           adaptor.getDtorAttr());
-  } else {
-    dtor = mlir::LLVM::ZeroOp::create(rewriter, loc, llvmPtrTy);
-  }
-
   auto cxaThrowCall = mlir::LLVM::CallOp::create(
       rewriter, loc, mlir::TypeRange{}, fnName,
-      mlir::ValueRange{adaptor.getExceptionPtr(), typeInfo, dtor});
+      mlir::ValueRange{exceptionPtr, typeInfo, dtor});
 
   rewriter.replaceOp(op, cxaThrowCall);
   return mlir::success();
diff --git a/clang/test/CIR/CodeGen/cleanup-scope-throw-caught.cpp b/clang/test/CIR/CodeGen/cleanup-scope-throw-caught.cpp
index ff313033e9a85..c9fc0fcc2cdb2 100644
--- a/clang/test/CIR/CodeGen/cleanup-scope-throw-caught.cpp
+++ b/clang/test/CIR/CodeGen/cleanup-scope-throw-caught.cpp
@@ -31,7 +31,7 @@ int testCaughtThrowSharedDispatch() {
 // CIR:   cir.try {
 // CIR:     cir.call @_ZN1CC1Ev(%[[A]])
 // CIR:     cir.cleanup.scope {
-// CIR:       cir.throw %{{.*}} : !cir.ptr<!cir.ptr<!rec_C>>, @_ZTIP1C
+// CIR:       cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:       cir.unreachable
 // CIR:     } cleanup all {
 // CIR:       cir.call @_ZN1CD1Ev(%[[A]]) nothrow
diff --git a/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp b/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
index e64888453d31f..5d2796401f3fa 100644
--- a/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
+++ b/clang/test/CIR/CodeGen/cleanup-throw-from-cleanup.cpp
@@ -24,7 +24,7 @@ void testSwitchWithCleanup(int n) {
 // CIR:     %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR:     %[[VAL:.*]] = cir.const #cir.int<42> : !s32i
 // CIR:     cir.store{{.*}} %[[VAL]], %[[EXN]] : !s32i, !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXN]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR:     cir.yield
 // CIR:   } cleanup all {
@@ -47,7 +47,7 @@ void testSwitchWithCleanup(int n) {
 // CIR-FLAT:   %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR-FLAT:   %[[VAL:.*]] = cir.const #cir.int<42> : !s32i
 // CIR-FLAT:   cir.store{{.*}} %[[VAL]], %[[EXN]]
-// CIR-FLAT:   cir.try_throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi ^[[UNREACH:.+]], ^[[UNWIND:.+]]
+// CIR-FLAT:   cir.try_throw %[[EXN]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void> ^[[UNREACH:.+]], ^[[UNWIND:.+]]
 // CIR-FLAT: ^[[UNWIND]]:
 // CIR-FLAT:   %[[ET:.*]] = cir.eh.initiate cleanup : !cir.eh_token
 // CIR-FLAT:   cir.br ^[[CLEANUP:.+]](%[[ET]] : !cir.eh_token)
diff --git a/clang/test/CIR/CodeGen/rtti-member-pointer.cpp b/clang/test/CIR/CodeGen/rtti-member-pointer.cpp
index 93975ebd0814d..d5b901dcce4e4 100644
--- a/clang/test/CIR/CodeGen/rtti-member-pointer.cpp
+++ b/clang/test/CIR/CodeGen/rtti-member-pointer.cpp
@@ -21,7 +21,7 @@ void throw_data_member_ptr() {
 // CIR-DAG: cir.global {{.*}} @_ZTSM1Ai = #cir.const_array<"M1Ai" : !cir.array<!s8i x 4>, trailing_zeros> : !cir.array<!s8i x 5>
 // CIR-DAG: cir.global {{.*}} @_ZTIM1Ai = #cir.typeinfo<{#cir.global_view<@_ZTVN10__cxxabiv129__pointer_to_member_type_infoE, [2 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTSM1Ai> : !cir.ptr<!u8i>, #cir.int<0> : !u32i, #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>, #cir.global_view<@_ZTI1A> : !cir.ptr<!u8i>}>
 
-// CIR-DAG: cir.throw %{{.*}} : !cir.ptr<!s64i>, @_ZTIM1Ai
+// CIR-DAG: cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!s64i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 
 // LLVM-DAG: @_ZTSM1Ai = linkonce_odr constant [5 x i8] c"M1Ai\00", comdat
 // LLVM-DAG: @_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00", comdat
diff --git a/clang/test/CIR/CodeGen/rtti-qualfn.cpp b/clang/test/CIR/CodeGen/rtti-qualfn.cpp
index 5fbc782223a98..db4c71303f18d 100644
--- a/clang/test/CIR/CodeGen/rtti-qualfn.cpp
+++ b/clang/test/CIR/CodeGen/rtti-qualfn.cpp
@@ -21,7 +21,7 @@ void f() noexcept {
 // CIR-DAG: cir.global {{.*}} @_ZTSPDoFvvE = #cir.const_array<"PDoFvvE" : !cir.array<!s8i x 7>, trailing_zeros> : !cir.array<!s8i x 8>
 // CIR-DAG: cir.global {{.*}} @_ZTIPDoFvvE = #cir.typeinfo<{#cir.global_view<@_ZTVN10__cxxabiv119__pointer_type_infoE, [2 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTSPDoFvvE> : !cir.ptr<!u8i>, #cir.int<64> : !u32i, #cir.global_view<@_ZTIFvvE> : !cir.ptr<!u8i>}>
 
-// CIR: cir.throw %{{.*}} : !cir.ptr<!cir.ptr<!cir.func<()>>>, @_ZTIPDoFvvE
+// CIR: cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!cir.ptr<!cir.func<()>>>, !cir.ptr<!u8i>, !cir.ptr<!void>
 
 // LLVM-DAG: @_ZTSFvvE = linkonce_odr constant [5 x i8] c"FvvE\00", comdat
 // LLVM-DAG: @_ZTSPDoFvvE = linkonce_odr constant [8 x i8] c"PDoFvvE\00", comdat
diff --git a/clang/test/CIR/CodeGen/ternary-throw.cpp b/clang/test/CIR/CodeGen/ternary-throw.cpp
index ec617bea1c930..0fcf79a51a2a6 100644
--- a/clang/test/CIR/CodeGen/ternary-throw.cpp
+++ b/clang/test/CIR/CodeGen/ternary-throw.cpp
@@ -22,7 +22,7 @@ const int& test_cond_throw_false(bool flag) {
 // CIR:   %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR:   %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:   cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr<!s32i>
-// CIR:   cir.throw %[[EXCEPTION]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:   cir.throw %[[EXCEPTION]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:   cir.unreachable
 // CIR-NEXT: }) : (!cir.bool) -> !cir.ptr<!s32i>
 
@@ -83,7 +83,7 @@ const int& test_cond_throw_true(bool flag) {
 // CIR:   %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR:   %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:   cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr<!s32i>
-// CIR:   cir.throw %[[EXCEPTION]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:   cir.throw %[[EXCEPTION]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:   cir.unreachable
 // CIR-NEXT: }, false {
 // CIR:   cir.yield %[[A]] : !cir.ptr<!s32i>
@@ -208,7 +208,7 @@ const int &test_cond_const_true_throw_true() {
 // CIR:  %[[EXCEPTION:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR:  %[[CONST_0:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:  cir.store{{.*}} %[[CONST_0]], %[[EXCEPTION]] : !s32i, !cir.ptr<!s32i>
-// CIR:  cir.throw %[[EXCEPTION]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:  cir.throw %[[EXCEPTION]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:  cir.unreachable
 // CIR: ^[[NO_PRED_LABEL:.*]]:
 // CIR:   %[[CONST_NULL:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!s32i>
@@ -255,7 +255,7 @@ int test_agg_cond_throw_false(bool flag, struct s6 a1, struct s6 a2) {
 // CIR:   %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR:   %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:   cir.store{{.*}} %[[ZERO]], %[[EXC]] : !s32i, !cir.ptr<!s32i>
-// CIR:   cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:   cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:   cir.unreachable
 // CIR: }) : (!cir.bool) -> !cir.ptr<!rec_s6>
 // CIR: %[[F0:.*]] = cir.get_member %[[COND_RES]][0] {name = "f0"} : !cir.ptr<!rec_s6> -> !cir.ptr<!s32i>
@@ -318,7 +318,7 @@ int test_agg_cond_throw_true(bool flag, struct s6 a1, struct s6 a2) {
 // CIR:   %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR:   %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:   cir.store{{.*}} %[[ZERO]], %[[EXC]] : !s32i, !cir.ptr<!s32i>
-// CIR:   cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:   cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:   cir.unreachable
 // CIR: }, false {
 // CIR:   cir.yield %[[A1]] : !cir.ptr<!rec_s6>
@@ -416,7 +416,7 @@ const int test_agg_cond_const_true_throw_true(struct s6 a1, struct s6 a2) {
 // CIR: %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR: cir.store{{.*}} %[[ZERO]], %[[EXC]] : !s32i, !cir.ptr<!s32i>
-// CIR: cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR: cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 // CIR: ^[[NO_PRED:.*]]:
 // CIR: %[[NULL_REC:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_s6>
@@ -456,7 +456,7 @@ const int test_agg_cond_const_false_throw_false(struct s6 a1, struct s6 a2) {
 // CIR: %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR: cir.store{{.*}} %[[ZERO]], %[[EXC]] : !s32i, !cir.ptr<!s32i>
-// CIR: cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR: cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 // CIR: ^[[NO_PRED:.*]]:
 // CIR: %[[NULL_REC:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!rec_s6>
@@ -539,7 +539,7 @@ void test_agg_throw_true(bool flag) {
 // CIR:     %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR:     %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:     cir.store{{.*}} %[[ZERO]], %[[EXC]] : !s32i, !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   } else {
 // CIR:     %[[X:.*]] = cir.get_member %[[A]][0] {name = "x"} : !cir.ptr<!rec_Agg> -> !cir.ptr<!s32i>
@@ -608,7 +608,7 @@ void test_agg_throw_false(bool flag) {
 // CIR:     %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
 // CIR:     %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:     cir.store{{.*}} %[[ZERO]], %[[EXC]] : !s32i, !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }
 // CIR:   cir.return
@@ -659,7 +659,7 @@ int test_scalar_throw_true(bool flag, int x) {
 // CIR:   %[[COND:.*]] = cir.load{{.*}} : !cir.ptr<!cir.bool>, !cir.bool
 // CIR:   %{{.*}} = cir.ternary(%[[COND]], true {
 // CIR:     %[[EXC:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }, false {
 // CIR:     %[[X:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
@@ -693,7 +693,7 @@ int test_scalar_throw_false(bool flag, int x) {
 // CIR:     cir.yield %[[X]] : !s32i
 // CIR:   }, false {
 // CIR:     %[[EXC:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }) : (!cir.bool) -> !s32i
 
@@ -715,10 +715,10 @@ void test_both_throw(bool flag) {
 
 // CIR-LABEL: cir.func {{.*}} @_Z15test_both_throwb(
 // CIR:   cir.ternary(%{{.*}}, true {
-// CIR:     cir.throw %{{.*}} : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }, false {
-// CIR:     cir.throw %{{.*}} : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   })
 
@@ -757,7 +757,7 @@ int &test_ref_cond_throw(bool c, int &x) {
 // CIR:     %[[X_PTR:.*]] = cir.load %[[X_REF]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
 // CIR:     cir.yield %[[X_PTR]] : !cir.ptr<!s32i>
 // CIR-NEXT:   }, false {
-// CIR:     cir.throw {{.*}} @_ZTIi
+// CIR:     cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }) : (!cir.bool) -> !cir.ptr<!s32i>
 // CIR:   cir.store %[[RES]], %[[RET_ADDR]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
@@ -799,7 +799,7 @@ void test_assign_through_cond(bool c, int &x) {
 // CIR:     %[[X_PTR:.*]] = cir.load %[[X_REF]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
 // CIR:     cir.yield %[[X_PTR]] : !cir.ptr<!s32i>
 // CIR-NEXT:   }, false {
-// CIR:     cir.throw {{.*}} @_ZTIi
+// CIR:     cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }) : (!cir.bool) -> !cir.ptr<!s32i>
 // CIR:   cir.store{{.*}} %[[FIVE]], %[[RES]] : !s32i, !cir.ptr<!s32i>
@@ -841,7 +841,7 @@ int &test_member_cond_throw(bool c, struct s6 *p) {
 // CIR:     %[[F0:.*]] = cir.get_member %[[P]][0] {name = "f0"} : !cir.ptr<!rec_s6> -> !cir.ptr<!s32i>
 // CIR:     cir.yield %[[F0]] : !cir.ptr<!s32i>
 // CIR-NEXT:   }, false {
-// CIR:     cir.throw {{.*}} @_ZTIi
+// CIR:     cir.throw %{{.*}}, %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   }) : (!cir.bool) -> !cir.ptr<!s32i>
 // CIR:   cir.store %[[RES]], %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
@@ -941,7 +941,7 @@ void test_agg_assign_throw(bool c, Agg &a) {
 // CIR:   %[[C_VAL:.*]] = cir.load{{.*}} %[[C]] : !cir.ptr<!cir.bool>, !cir.bool
 // CIR:   cir.if %[[C_VAL]] {
 // CIR:     %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   } else {
 // CIR:     cir.get_member %[[TMP]][0] {name = "x"}
@@ -988,7 +988,7 @@ void test_agg_nested_throw(bool c1, bool c2) {
 // CIR:     %[[C2_VAL:.*]] = cir.load{{.*}} : !cir.ptr<!cir.bool>, !cir.bool
 // CIR:     cir.if %[[C2_VAL]] {
 // CIR:       %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
-// CIR:       cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:       cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:       cir.unreachable
 // CIR-NEXT:     } else {
 // CIR:       cir.get_member %[[A]][0] {name = "x"}
@@ -1038,7 +1038,7 @@ void test_agg_arg_throw(bool c) {
 // CIR:   %[[TMP:.*]] = cir.alloca "agg.tmp0" {{.*}} : !cir.ptr<!rec_Agg>
 // CIR:   cir.if %{{.*}} {
 // CIR:     %[[EXC:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr<!s32i>
-// CIR:     cir.throw %[[EXC]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:     cir.throw %[[EXC]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:     cir.unreachable
 // CIR-NEXT:   } else {
 // CIR:     cir.get_member %[[TMP]][0] {name = "x"}
diff --git a/clang/test/CIR/CodeGen/throws.cpp b/clang/test/CIR/CodeGen/throws.cpp
index 5a2d5ccf62e0a..bff6904ea8f27 100644
--- a/clang/test/CIR/CodeGen/throws.cpp
+++ b/clang/test/CIR/CodeGen/throws.cpp
@@ -91,7 +91,9 @@ void throw_scalar() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR: %[[EXCEPTION_VALUE:.*]] = cir.const #cir.int<1> : !s32i
 // CIR: cir.store{{.*}} %[[EXCEPTION_VALUE]], %[[EXCEPTION_ADDR]] : !s32i, !cir.ptr<!s32i>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR: %[[TYPE_INFO:.*]] = cir.const #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>
+// CIR: %[[DTOR:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %[[TYPE_INFO]], %[[DTOR]] : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[EXCEPTION_ADDR:.*]] = call ptr @__cxa_allocate_exception(i64 4)
@@ -109,7 +111,7 @@ void paren_expr() { (throw 0, 1 + 2); }
 // CIR:   %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR:   %[[EXCEPTION_VALUE:.*]] = cir.const #cir.int<0> : !s32i
 // CIR:   cir.store{{.*}} %[[EXCEPTION_VALUE]], %[[EXCEPTION_ADDR]] : !s32i, !cir.ptr<!s32i>
-// CIR:   cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:   cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:   cir.unreachable
 // CIR: ^bb1:
 // CIR:   cir.return
@@ -129,7 +131,7 @@ void throw_complex_expr() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 8 -> !cir.ptr<!cir.complex<!cir.float>>
 // CIR: %[[EXCEPTION_VALUE:.*]] = cir.const #cir.const_complex<#cir.fp<1.100000e+00> : !cir.float, #cir.fp<2.200000e+00> : !cir.float> : !cir.complex<!cir.float>
 // CIR: cir.store{{.*}} %[[EXCEPTION_VALUE]], %[[EXCEPTION_ADDR]] : !cir.complex<!cir.float>, !cir.ptr<!cir.complex<!cir.float>>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!cir.complex<!cir.float>>, @_ZTICf
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!cir.complex<!cir.float>>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[EXCEPTION_ADDR:.*]] = call ptr @__cxa_allocate_exception(i64 8)
@@ -153,7 +155,7 @@ void throw_vector_type() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 16 -> !cir.ptr<!cir.vector<4 x !s32i>>
 // CIR: %[[TMP_A:.*]] = cir.load{{.*}} %[[A_ADDR]] : !cir.ptr<!cir.vector<4 x !s32i>>, !cir.vector<4 x !s32i>
 // CIR: cir.store{{.*}} %[[TMP_A]], %[[EXCEPTION_ADDR]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!cir.vector<4 x !s32i>>, @_ZTIDv4_i
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!cir.vector<4 x !s32i>>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[A_ADDR:.*]] = alloca <4 x i32>, align 16
@@ -179,7 +181,7 @@ void throw_ext_vector_type() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 16 -> !cir.ptr<!cir.vector<4 x !s32i>>
 // CIR: %[[TMP_A:.*]] = cir.load{{.*}} %[[A_ADDR]] : !cir.ptr<!cir.vector<4 x !s32i>>, !cir.vector<4 x !s32i>
 // CIR: cir.store{{.*}} %[[TMP_A]], %[[EXCEPTION_ADDR]] : !cir.vector<4 x !s32i>, !cir.ptr<!cir.vector<4 x !s32i>>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!cir.vector<4 x !s32i>>, @_ZTIDv4_i
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!cir.vector<4 x !s32i>>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[A_ADDR:.*]] = alloca <4 x i32>, align 16
@@ -206,7 +208,7 @@ void throw_enum_expr() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 4 -> !cir.ptr<!u32i>
 // CIR: %[[EXCEPTION_VALUE:.*]] = cir.const #cir.int<0> : !u32i
 // CIR: cir.store{{.*}} %[[EXCEPTION_VALUE]], %[[EXCEPTION_ADDR]] : !u32i, !cir.ptr<!u32i>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!u32i>, @_ZTIZ15throw_enum_exprvE4Test
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!u32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[EXCEPTION_ADDR:.*]] = call ptr @__cxa_allocate_exception(i64 4)
@@ -230,7 +232,7 @@ void throw_enum_class_expr() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR: %[[EXCEPTION_VALUE:.*]] = cir.const #cir.int<0> : !s32i
 // CIR: cir.store{{.*}} %[[EXCEPTION_VALUE]], %[[EXCEPTION_ADDR]] : !s32i, !cir.ptr<!s32i>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!s32i>, @_ZTIZ21throw_enum_class_exprvE4Test
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[EXCEPTION_ADDR:.*]] = call ptr @__cxa_allocate_exception(i64 4)
@@ -255,7 +257,7 @@ void throw_pointer_type() {
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 8 -> !cir.ptr<!cir.ptr<!s32i>>
 // CIR: %[[TMP_PTR:.*]] = cir.load{{.*}} %[[PTR_ADDR]] : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!s32i>
 // CIR: cir.store{{.*}} %[[TMP_PTR]], %[[EXCEPTION_ADDR]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!cir.ptr<!s32i>>, @_ZTIPi
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %{{.*}}, %{{.*}} : !cir.ptr<!cir.ptr<!s32i>>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[PTR_ADDR:.*]] = alloca ptr,{{.*}} align 8
@@ -280,7 +282,10 @@ struct ThrowNonTrivialDtor {
 void throw_class_with_nontrivial_dtor() { throw ThrowNonTrivialDtor(); }
 
 // CIR: %[[EXCEPTION_ADDR:.*]] = cir.alloc.exception 1 -> !cir.ptr<!rec_ThrowNonTrivialDtor>
-// CIR: cir.throw %[[EXCEPTION_ADDR]] : !cir.ptr<!rec_ThrowNonTrivialDtor>, @_ZTI19ThrowNonTrivialDtor, @_ZN19ThrowNonTrivialDtorD1Ev
+// CIR: %[[TYPE_INFO:.*]] = cir.const #cir.global_view<@_ZTI19ThrowNonTrivialDtor> : !cir.ptr<!u8i>
+// CIR: %[[DTOR_ADDR:.*]] = cir.get_global @_ZN19ThrowNonTrivialDtorD1Ev
+// CIR: %[[DTOR:.*]] = cir.cast bitcast %[[DTOR_ADDR]] : {{.*}} -> !cir.ptr<!void>
+// CIR: cir.throw %[[EXCEPTION_ADDR]], %[[TYPE_INFO]], %[[DTOR]] : !cir.ptr<!rec_ThrowNonTrivialDtor>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR: cir.unreachable
 
 // LLVM: %[[EXCEPTION_ADDR:.*]] = call ptr @__cxa_allocate_exception(i64 1)
diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp
index 2b4108ca0d270..febb382cd2170 100644
--- a/clang/test/CIR/CodeGen/try-catch.cpp
+++ b/clang/test/CIR/CodeGen/try-catch.cpp
@@ -1780,7 +1780,7 @@ void direct_inside_try_catch_with_exception_type() {
 // CIR:       %[[EXN:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
 // CIR:       %[[FORTYTWO:.*]] = cir.const #cir.int<42> : !s32i
 // CIR:       cir.store{{.*}} %[[FORTYTWO]], %[[EXN]]
-// CIR:       cir.throw %[[EXN]] : !cir.ptr<!s32i>, @_ZTIi
+// CIR:       cir.throw %[[EXN]], %{{.*}}, %{{.*}} : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
 // CIR:       cir.unreachable
 // CIR:     } catch [type #cir.global_view<@_ZTIi> : !cir.ptr<!u8i>] (%[[TOKEN:.*]]: !cir.eh_token {{.*}}) {
 // CIR:       %[[CATCH_TOKEN:.*]], %[[EXN_PTR:.*]] = cir.begin_catch %{{.*}} : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
diff --git a/clang/test/CIR/CodeGenHIP/throw-no-rtti.hip b/clang/test/CIR/CodeGenHIP/throw-no-rtti.hip
new file mode 100644
index 0000000000000..7bc3a1fafff7b
--- /dev/null
+++ b/clang/test/CIR/CodeGenHIP/throw-no-rtti.hip
@@ -0,0 +1,83 @@
+// RUN: %clang_cc1 -std=c++17 -triple amdgcn-amd-amdhsa -x hip \
+// RUN:   -fcuda-is-device --hipstdpar -fcxx-exceptions -fexceptions \
+// RUN:   -fclangir -emit-cir %s -o - | FileCheck %s --check-prefix=CIR
+// RUN: %clang_cc1 -std=c++17 -triple amdgcn-amd-amdhsa -x hip \
+// RUN:   -fcuda-is-device --hipstdpar -fcxx-exceptions -fexceptions \
+// RUN:   -fclangir -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++17 -triple amdgcn-amd-amdhsa -x hip \
+// RUN:   -fcuda-is-device --hipstdpar -fcxx-exceptions -fexceptions \
+// RUN:   -emit-llvm %s -o - | FileCheck %s --check-prefix=OGCG
+
+struct X {
+  ~X();
+};
+
+struct Guard {
+  ~Guard();
+};
+
+inline void throw_int() {
+  throw 1;
+}
+
+inline void throw_object() {
+  throw X{};
+}
+
+inline void throw_with_cleanup() {
+  Guard guard;
+  throw 2;
+}
+
+inline void throw_object_with_cleanup() {
+  Guard guard;
+  throw X{};
+}
+
+int main() {
+  throw_int();
+  throw_object();
+  throw_with_cleanup();
+  throw_object_with_cleanup();
+}
+
+// Device-side HIP compilation disables RTTI. The Itanium throw ABI still
+// requires a type-info argument, so use a null pointer in the target's global
+// address space instead of trying to create a reference to a missing RTTI
+// global.
+
+// CIR: module
+// CIR-NOT: cir.global {{.*}} @_ZTI
+
+// CIR-LABEL: cir.func{{.*}} @_Z9throw_intv()
+// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception 4 -> !cir.ptr<!s32i>
+// CIR: %[[TYPE_INFO:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!u8i, target_address_space(1)>
+// CIR: %[[DTOR:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!void>
+// CIR: cir.throw %[[EXCEPTION]], %[[TYPE_INFO]], %[[DTOR]] : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+
+// CIR-LABEL: cir.func{{.*}} @_Z12throw_objectv()
+// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception 1 -> !cir.ptr<!rec_X>
+// CIR: %[[TYPE_INFO:.*]] = cir.const #cir.ptr<null> : !cir.ptr<!u8i, target_address_space(1)>
+// CIR: %[[DTOR_ADDR:.*]] = cir.get_global @_ZN1XD1Ev
+// CIR: %[[DTOR:.*]] = cir.cast bitcast %[[DTOR_ADDR]] : {{.*}} -> !cir.ptr<!void>
+// CIR: cir.throw %[[EXCEPTION]], %[[TYPE_INFO]], %[[DTOR]] : !cir.ptr<!rec_X>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+
+// LLVM-LABEL: define{{.*}} void @_Z9throw_intv()
+// LLVM: call void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr null)
+// LLVM-LABEL: define{{.*}} void @_Z12throw_objectv()
+// LLVM: call void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr @_ZN1XD1Ev)
+// LLVM-LABEL: define{{.*}} void @_Z18throw_with_cleanupv()
+// LLVM: invoke void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr null)
+// LLVM-LABEL: define{{.*}} void @_Z25throw_object_with_cleanupv()
+// LLVM: invoke void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr @_ZN1XD1Ev)
+// LLVM: declare void @__cxa_throw(ptr, ptr addrspace(1), ptr)
+
+// OGCG-LABEL: define{{.*}} void @_Z9throw_intv()
+// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr null)
+// OGCG-LABEL: define{{.*}} void @_Z12throw_objectv()
+// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr @_ZN1XD1Ev)
+// OGCG-LABEL: define{{.*}} void @_Z18throw_with_cleanupv()
+// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr null)
+// OGCG-LABEL: define{{.*}} void @_Z25throw_object_with_cleanupv()
+// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr addrspace(1) null, ptr @_ZN1XD1Ev)
+// OGCG: declare void @__cxa_throw(ptr, ptr addrspace(1), ptr)
diff --git a/clang/test/CIR/IR/invalid-throw.cir b/clang/test/CIR/IR/invalid-throw.cir
index 114767a9ad715..50b8759cfba87 100644
--- a/clang/test/CIR/IR/invalid-throw.cir
+++ b/clang/test/CIR/IR/invalid-throw.cir
@@ -4,13 +4,129 @@
 
 module {
 
-cir.func dso_local @throw_without_type_info() {
+cir.func dso_local @throw_with_incomplete_operands() {
     %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
-    // expected-error @below {{'cir.throw' op 'type_info' symbol attribute missing}}
-    cir.throw %0 : !cir.ptr<!s32i>
+    // expected-error @below {{'cir.throw' op must have either no operands for a rethrow or exception, type_info, and destructor pointer operands for a throw}}
+    "cir.throw"(%0) <{operandSegmentSizes = array<i32: 1, 0, 0>}> : (!cir.ptr<!s32i>) -> ()
     cir.unreachable
   ^bb1:
     cir.return
 }
 
 }
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+module {
+
+cir.func @throw_with_as1(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void>) {
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+cir.func @throw_with_as2(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(2)>, %dtor: !cir.ptr<!void>) {
+  // expected-error @below {{'cir.throw' op operand address spaces must match the first non-rethrow cir.throw or cir.try_throw in the module}}
+  // expected-note @-6 {{the module's __cxa_throw address-space signature is established here}}
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(2)>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+module {
+
+cir.func @throw_with_default_spaces(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void>) {
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+cir.func @throw_with_exception_as2(%exception: !cir.ptr<!s32i, target_address_space(2)>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void>) {
+  // expected-error @below {{'cir.throw' op operand address spaces must match the first non-rethrow cir.throw or cir.try_throw in the module}}
+  // expected-note @-6 {{the module's __cxa_throw address-space signature is established here}}
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i, target_address_space(2)>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+module {
+
+cir.func @throw_with_default_spaces(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void>) {
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+cir.func @throw_with_dtor_as2(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void, target_address_space(2)>) {
+  // expected-error @below {{'cir.throw' op operand address spaces must match the first non-rethrow cir.throw or cir.try_throw in the module}}
+  // expected-note @-6 {{the module's __cxa_throw address-space signature is established here}}
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void, target_address_space(2)>
+  cir.unreachable
+}
+
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+module {
+
+cir.func @throw_with_as1(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void>) {
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+cir.func @try_throw_with_as2(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(2)>, %dtor: !cir.ptr<!void>) {
+  // expected-error @below {{'cir.try_throw' op operand address spaces must match the first non-rethrow cir.throw or cir.try_throw in the module}}
+  // expected-note @-6 {{the module's __cxa_throw address-space signature is established here}}
+  cir.try_throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(2)>, !cir.ptr<!void> ^normal, ^unwind
+^normal:
+  cir.unreachable
+^unwind:
+  cir.unreachable
+}
+
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+module {
+
+// Different pointee types are valid when the address-space signature is the
+// same.
+cir.func @try_throw_one(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i, target_address_space(1)>, %dtor: !cir.ptr<!void>) {
+  cir.try_throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i, target_address_space(1)>, !cir.ptr<!void> ^normal, ^unwind
+^normal:
+  cir.unreachable
+^unwind:
+  cir.unreachable
+}
+
+cir.func @throw_two(%exception: !cir.ptr<!u8i>, %type_info: !cir.ptr<!void, target_address_space(1)>, %dtor: !cir.ptr<!s32i>) {
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!u8i>, !cir.ptr<!void, target_address_space(1)>, !cir.ptr<!s32i>
+  cir.unreachable
+}
+
+}
diff --git a/clang/test/CIR/IR/throw.cir b/clang/test/CIR/IR/throw.cir
index d47d17efe3383..494f1a13e546c 100644
--- a/clang/test/CIR/IR/throw.cir
+++ b/clang/test/CIR/IR/throw.cir
@@ -1,6 +1,8 @@
 // RUN: cir-opt %s --verify-roundtrip | FileCheck %s
 
 !s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
 
 module  {
 
@@ -14,6 +16,16 @@ cir.func @throw_with_no_return() {
 // CHECK:  cir.unreachable
 // CHECK: }
 
+cir.func @throw_with_operands(%exception: !cir.ptr<!s32i>, %type_info: !cir.ptr<!u8i>, %dtor: !cir.ptr<!void>) {
+  cir.throw %exception, %type_info, %dtor : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
+  cir.unreachable
+}
+
+// CHECK: cir.func @throw_with_operands(%[[EXCEPTION:.*]]: !cir.ptr<!s32i>, %[[TYPE_INFO:.*]]: !cir.ptr<!u8i>, %[[DTOR:.*]]: !cir.ptr<!void>) {
+// CHECK:   cir.throw %[[EXCEPTION]], %[[TYPE_INFO]], %[[DTOR]] : !cir.ptr<!s32i>, !cir.ptr<!u8i>, !cir.ptr<!void>
+// CHECK:   cir.unreachable
+// CHECK: }
+
 cir.func @throw_with_no_return_2(%arg0: !s32i, %arg1: !s32i) -> !s32i {
   %0 = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
   %1 = cir.alloca "b" align(4) init : !cir.ptr<!s32i>



More information about the cfe-commits mailing list