[CIR] Implement EH lowering to Itanium form and LLVM IR (#184386)

This introduces a new pass to lower from a flattened, target-independent
form of CIR to a form that uses Itanium-specific representation for
exception handling. It also includes a small amount of code needed to
lower the Itanium form to LLVM IR.

Substantial amounts of this PR were created using agentic AI tools, but
I have carefully reviewed the code, comments, and tests and made changes
as needed.
This commit is contained in:
Andy Kaylor 2026-03-03 12:09:49 -08:00 committed by GitHub
parent 5586d93a87
commit 375d65ee8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1381 additions and 16 deletions

View File

@ -3486,7 +3486,6 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[
);
let skipDefaultBuilders = 1;
let hasLLVMLowering = false;
let builders = [
OpBuilder<(ins "mlir::SymbolRefAttr":$callee,

View File

@ -24,6 +24,7 @@ namespace mlir {
std::unique_ptr<Pass> createCIRCanonicalizePass();
std::unique_ptr<Pass> createCIRFlattenCFGPass();
std::unique_ptr<Pass> createCIRSimplifyPass();
std::unique_ptr<Pass> createCIREHABILoweringPass();
std::unique_ptr<Pass> createCXXABILoweringPass();
std::unique_ptr<Pass> createTargetLoweringPass();
std::unique_ptr<Pass> createHoistAllocasPass();

View File

@ -150,6 +150,29 @@ def TargetLowering : Pass<"cir-target-lowering", "mlir::ModuleOp"> {
let dependentDialects = ["cir::CIRDialect"];
}
def CIREHABILowering : Pass<"cir-eh-abi-lowering", "mlir::ModuleOp"> {
let summary = "Lower flattened CIR EH operations to target-specific ABI form";
let description = [{
This pass lowers the ABI-agnostic exception handling operations produced by
CFG flattening into an ABI-specific form. Currently only the Itanium C++
ABI is implemented.
For the Itanium ABI, the pass performs the following transformations:
- Replaces `cir.eh.initiate` with `cir.eh.inflight_exception`
- Replaces `cir.eh.dispatch` with `cir.eh.typeid` + comparison chains
- Removes `cir.begin_cleanup` and `cir.end_cleanup` operations
- Replaces `cir.begin_catch` with a call to `__cxa_begin_catch`
- Replaces `cir.end_catch` with a call to `__cxa_end_catch`
- Replaces `cir.resume` with `cir.resume.flat`
- Sets the personality function attribute on functions that require EH
If a non-Itanium ABI is specified, the pass emits a diagnostic indicating
that the target is not yet implemented.
}];
let constructor = "mlir::createCIREHABILoweringPass()";
let dependentDialects = ["cir::CIRDialect"];
}
def LoweringPrepare : Pass<"cir-lowering-prepare"> {
let summary = "Lower to more fine-grained CIR operations before lowering to "
"other dialects";

View File

@ -114,8 +114,6 @@ struct MissingFeatures {
static bool opCallExtParameterInfo() { return false; }
static bool opCallCIRGenFuncInfoParamInfo() { return false; }
static bool opCallCIRGenFuncInfoExtParamInfo() { return false; }
static bool opCallLandingPad() { return false; }
static bool opCallContinueBlock() { return false; }
static bool opCallChain() { return false; }
static bool opCallExceptionAttr() { return false; }

View File

@ -4,6 +4,7 @@ add_clang_library(MLIRCIRTransforms
CIRCanonicalize.cpp
CIRSimplify.cpp
CXXABILowering.cpp
EHABILowering.cpp
TargetLowering.cpp
FlattenCFG.cpp
HoistAllocas.cpp

View File

@ -0,0 +1,502 @@
//===- EHABILowering.cpp - Lower flattened CIR EH ops to ABI-specific form ===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file implements a pass that lowers ABI-agnostic flattened CIR exception
// handling operations into an ABI-specific form. Currently only the Itanium
// C++ ABI is supported.
//
// The Itanium ABI lowering performs these transformations:
// - cir.eh.initiate → cir.eh.inflight_exception (landing pad)
// - cir.eh.dispatch → cir.eh.typeid + cir.cmp + cir.brcond chains
// - cir.begin_cleanup → (removed)
// - cir.end_cleanup → (removed)
// - cir.begin_catch → call to __cxa_begin_catch
// - cir.end_catch → call to __cxa_end_catch
// - cir.resume → cir.resume.flat
// - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
// - personality function set on functions requiring EH
//
//===----------------------------------------------------------------------===//
#include "PassDetail.h"
#include "mlir/IR/Builders.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/Dialect/Passes.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/TargetParser/Triple.h"
using namespace mlir;
using namespace cir;
namespace mlir {
#define GEN_PASS_DEF_CIREHABILOWERING
#include "clang/CIR/Dialect/Passes.h.inc"
} // namespace mlir
namespace {
//===----------------------------------------------------------------------===//
// Shared utilities
//===----------------------------------------------------------------------===//
/// Ensure a function with the given name and type exists in the module. If it
/// does not exist, create a private external declaration.
static cir::FuncOp getOrCreateRuntimeFuncDecl(mlir::ModuleOp mod,
mlir::Location loc,
StringRef name,
cir::FuncType funcTy) {
if (auto existing = mod.lookupSymbol<cir::FuncOp>(name))
return existing;
mlir::OpBuilder builder(mod.getContext());
builder.setInsertionPointToEnd(mod.getBody());
auto funcOp = cir::FuncOp::create(builder, loc, name, funcTy);
funcOp.setLinkage(cir::GlobalLinkageKind::ExternalLinkage);
funcOp.setPrivate();
return funcOp;
}
//===----------------------------------------------------------------------===//
// EH ABI Lowering Base Class
//===----------------------------------------------------------------------===//
/// Abstract base class for exception-handling ABI lowering.
/// Each supported ABI (Itanium, Microsoft, etc.) provides a concrete subclass.
class EHABILowering {
public:
explicit EHABILowering(mlir::ModuleOp mod)
: mod(mod), ctx(mod.getContext()), builder(ctx) {}
virtual ~EHABILowering() = default;
/// Lower all EH operations in the module to an ABI-specific form.
virtual mlir::LogicalResult run() = 0;
protected:
mlir::ModuleOp mod;
mlir::MLIRContext *ctx;
mlir::OpBuilder builder;
};
//===----------------------------------------------------------------------===//
// Itanium EH ABI Lowering
//===----------------------------------------------------------------------===//
/// Lowers flattened CIR EH operations to the Itanium C++ ABI form.
///
/// The entry point is run(), which iterates over all functions and
/// calls lowerFunc() for each. lowerFunc() drives all lowering from
/// cir.eh.initiate operations: every other EH op (begin/end_cleanup,
/// eh.dispatch, begin/end_catch, resume) is reachable by tracing the
/// eh_token produced by the initiate through its users.
class ItaniumEHLowering : public EHABILowering {
public:
using EHABILowering::EHABILowering;
mlir::LogicalResult run() override;
private:
/// Maps a !cir.eh_token value to its Itanium ABI replacement pair:
/// an exception pointer (!cir.ptr<!void>) and a type id (!u32i).
using EhTokenMap = DenseMap<mlir::Value, std::pair<mlir::Value, mlir::Value>>;
cir::VoidType voidType;
cir::PointerType voidPtrType;
cir::PointerType u8PtrType;
cir::IntType u32Type;
// Cached runtime function declarations, initialized when needed by
// ensureRuntimeDecls().
cir::FuncOp personalityFunc;
cir::FuncOp beginCatchFunc;
cir::FuncOp endCatchFunc;
constexpr const static ::llvm::StringLiteral kGxxPersonality =
"__gxx_personality_v0";
void ensureRuntimeDecls(mlir::Location loc);
mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
void lowerEhInitiate(cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
SmallVectorImpl<mlir::Operation *> &deadOps);
void lowerDispatch(cir::EhDispatchOp dispatch, mlir::Value exnPtr,
mlir::Value typeId,
SmallVectorImpl<mlir::Operation *> &deadOps);
};
/// Lower all EH operations in the module to the Itanium-specific form.
mlir::LogicalResult ItaniumEHLowering::run() {
// Pre-compute the common types used throughout all function lowerings.
// TODO(cir): Move these to the base class if they are also needed for MSVC.
voidType = cir::VoidType::get(ctx);
voidPtrType = cir::PointerType::get(voidType);
auto u8Type = cir::IntType::get(ctx, 8, /*isSigned=*/false);
u8PtrType = cir::PointerType::get(u8Type);
u32Type = cir::IntType::get(ctx, 32, /*isSigned=*/false);
for (cir::FuncOp funcOp : mod.getOps<cir::FuncOp>()) {
if (mlir::failed(lowerFunc(funcOp)))
return mlir::failure();
}
return mlir::success();
}
/// Ensure the necessary Itanium runtime function declarations exist in the
/// module.
void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location loc) {
// TODO(cir): Handle other personality functions. This probably isn't needed
// here if we fix codegen to always set the personality function.
if (!personalityFunc) {
auto s32Type = cir::IntType::get(ctx, 32, /*isSigned=*/true);
auto personalityFuncTy = cir::FuncType::get({}, s32Type, /*isVarArg=*/true);
personalityFunc = getOrCreateRuntimeFuncDecl(mod, loc, kGxxPersonality,
personalityFuncTy);
}
if (!beginCatchFunc) {
auto beginCatchFuncTy =
cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
beginCatchFunc = getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_begin_catch",
beginCatchFuncTy);
}
if (!endCatchFunc) {
auto endCatchFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
endCatchFunc =
getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_end_catch", endCatchFuncTy);
}
}
/// Lower all EH operations in a single function.
mlir::LogicalResult ItaniumEHLowering::lowerFunc(cir::FuncOp funcOp) {
if (funcOp.isDeclaration())
return mlir::success();
// All EH lowering follows from cir.eh.initiate operations. The token each
// initiate produces connects it to every other EH op in the function
// (begin/end_cleanup, eh.dispatch, begin/end_catch, resume) through the
// token graph. A single walk to collect initiates is therefore sufficient.
SmallVector<cir::EhInitiateOp> initiateOps;
funcOp.walk([&](cir::EhInitiateOp op) { initiateOps.push_back(op); });
if (initiateOps.empty())
return mlir::success();
ensureRuntimeDecls(funcOp.getLoc());
// Set the personality function if it is not already set.
// TODO(cir): The personality function should already have been set by this
// point. If we've seen a try operation, it will have been set by
// emitCXXTryStmt. If we only have cleanups, it may not have been set. We
// need to fix that in CodeGen. This is a placeholder until that is done.
if (!funcOp.getPersonality())
funcOp.setPersonality(kGxxPersonality);
// Lower each initiate and all EH ops connected to it. The token map is
// shared across all initiate operations. Multiple initiates may flow into the
// same dispatch block, and the map ensures the arguments are registered
// only once. Dispatch ops are scheduled for deferred removal so that sibling
// initiates can still read catch types from a shared dispatch.
EhTokenMap ehTokenMap;
SmallVector<mlir::Operation *> deadOps;
for (cir::EhInitiateOp initiateOp : initiateOps)
lowerEhInitiate(initiateOp, ehTokenMap, deadOps);
// Erase operations that were deferred during per-initiate processing
// (dispatch ops whose catch types were read by multiple initiates).
for (mlir::Operation *op : deadOps)
op->erase();
// Remove the !cir.eh_token block arguments that were replaced by (ptr, u32)
// pairs. Iterate in reverse to preserve argument indices during removal.
for (mlir::Block &block : funcOp.getBody()) {
for (int i = block.getNumArguments() - 1; i >= 0; --i) {
if (mlir::isa<cir::EhTokenType>(block.getArgument(i).getType()))
block.eraseArgument(i);
}
}
return mlir::success();
}
/// Lower all EH operations connected to a single cir.eh.initiate.
///
/// The cir.eh.initiate is the root of a token graph. The token it produces
/// flows through branch edges to consuming operations:
///
/// cir.eh.initiate → (via cir.br) → cir.begin_cleanup
/// → cir.end_cleanup (via cleanup_token)
/// → (via cir.br) → cir.eh.dispatch
/// → (successors) →
/// cir.begin_catch
/// → cir.end_catch
/// (via catch_token)
/// → cir.resume
///
/// A single traversal of the token graph discovers and processes every
/// connected op inline. The inflight_exception is created up-front without
/// a catch_type_list; when the dispatch is encountered during traversal,
/// the catch types are read and set on the inflight op.
///
/// Dispatch ops are not erased during per-initiate processing because they may
/// be used by other initiate ops that haven't yet been lowered. Instead they
/// are added to \p deadOps and erased by the caller after all initiates have
/// been lowered.
///
/// \p ehTokenMap is shared across all initiates in the function so that block
/// arguments reachable from multiple sibling initiates are registered once.
void ItaniumEHLowering::lowerEhInitiate(
cir::EhInitiateOp initiateOp, EhTokenMap &ehTokenMap,
SmallVectorImpl<mlir::Operation *> &deadOps) {
mlir::Value rootToken = initiateOp.getEhToken();
// Create the inflight_exception without a catch_type_list. The catch types
// will be set once we encounter the dispatch during the traversal below.
builder.setInsertionPoint(initiateOp);
auto inflightOp = cir::EhInflightOp::create(
builder, initiateOp.getLoc(), /*cleanup=*/initiateOp.getCleanup(),
/*catch_type_list=*/mlir::ArrayAttr{});
ehTokenMap[rootToken] = {inflightOp.getExceptionPtr(),
inflightOp.getTypeId()};
// Single traversal of the token graph. For each token value (the root token
// or a block argument that carries it), we snapshot its users, register
// (ptr, u32) replacement arguments on successor blocks, then process every
// user inline. This avoids collecting ops into separate vectors.
SmallVector<mlir::Value> worklist;
SmallPtrSet<mlir::Value, 8> visited;
worklist.push_back(rootToken);
while (!worklist.empty()) {
mlir::Value current = worklist.pop_back_val();
if (!visited.insert(current).second)
continue;
// Snapshot users before modifying any of them (erasing ops during
// iteration would invalidate the use-list iterator).
SmallVector<mlir::Operation *> users;
for (mlir::OpOperand &use : current.getUses())
users.push_back(use.getOwner());
// Register replacement block arguments on successor blocks (extending the
// worklist), then lower the op itself.
for (mlir::Operation *user : users) {
// Trace into successor blocks to register (ptr, u32) replacement
// arguments for any !cir.eh_token block arguments found there. Even
// if a block arg was already registered by a sibling initiate, it is
// still added to the worklist so that the traversal can reach the
// shared dispatch to read catch types.
for (unsigned s = 0; s < user->getNumSuccessors(); ++s) {
mlir::Block *succ = user->getSuccessor(s);
for (mlir::BlockArgument arg : succ->getArguments()) {
if (!mlir::isa<cir::EhTokenType>(arg.getType()))
continue;
if (!ehTokenMap.count(arg)) {
mlir::Value ptrArg = succ->addArgument(voidPtrType, arg.getLoc());
mlir::Value u32Arg = succ->addArgument(u32Type, arg.getLoc());
ehTokenMap[arg] = {ptrArg, u32Arg};
}
worklist.push_back(arg);
}
}
if (auto op = mlir::dyn_cast<cir::BeginCleanupOp>(user)) {
// begin_cleanup / end_cleanup are no-ops for Itanium. Erase the
// end_cleanup first (drops the cleanup_token use) then the begin.
for (auto &tokenUsers :
llvm::make_early_inc_range(op.getCleanupToken().getUses())) {
if (auto endOp =
mlir::dyn_cast<cir::EndCleanupOp>(tokenUsers.getOwner()))
endOp.erase();
}
op.erase();
} else if (auto op = mlir::dyn_cast<cir::BeginCatchOp>(user)) {
// Replace end_catch → __cxa_end_catch (drops the catch_token use),
// then replace begin_catch → __cxa_begin_catch.
for (auto &tokenUsers :
llvm::make_early_inc_range(op.getCatchToken().getUses())) {
if (auto endOp =
mlir::dyn_cast<cir::EndCatchOp>(tokenUsers.getOwner())) {
builder.setInsertionPoint(endOp);
cir::CallOp::create(builder, endOp.getLoc(),
mlir::FlatSymbolRefAttr::get(endCatchFunc),
voidType, mlir::ValueRange{});
endOp.erase();
}
}
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
builder.setInsertionPoint(op);
auto callOp = cir::CallOp::create(
builder, op.getLoc(), mlir::FlatSymbolRefAttr::get(beginCatchFunc),
u8PtrType, mlir::ValueRange{exnPtr});
mlir::Value castResult = callOp.getResult();
mlir::Type expectedPtrType = op.getExnPtr().getType();
if (castResult.getType() != expectedPtrType)
castResult =
cir::CastOp::create(builder, op.getLoc(), expectedPtrType,
cir::CastKind::bitcast, callOp.getResult());
op.getExnPtr().replaceAllUsesWith(castResult);
op.erase();
} else if (auto op = mlir::dyn_cast<cir::EhDispatchOp>(user)) {
// Read catch types from the dispatch and set them on the inflight op.
mlir::ArrayAttr catchTypes = op.getCatchTypesAttr();
if (catchTypes && catchTypes.size() > 0) {
SmallVector<mlir::Attribute> typeSymbols;
for (mlir::Attribute attr : catchTypes)
typeSymbols.push_back(
mlir::cast<cir::GlobalViewAttr>(attr).getSymbol());
inflightOp.setCatchTypeListAttr(builder.getArrayAttr(typeSymbols));
}
// Only lower the dispatch once. A sibling initiate sharing the same
// dispatch will still read its catch types (above), but the comparison
// chain and branch replacement are only created the first time.
if (!llvm::is_contained(deadOps, op.getOperation())) {
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
lowerDispatch(op, exnPtr, typeId, deadOps);
}
} else if (auto op = mlir::dyn_cast<cir::ResumeOp>(user)) {
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
builder.setInsertionPoint(op);
cir::ResumeFlatOp::create(builder, op.getLoc(), exnPtr, typeId);
op.erase();
} else if (auto op = mlir::dyn_cast<cir::BrOp>(user)) {
// Replace eh_token operands with the (ptr, u32) pair.
SmallVector<mlir::Value> newOperands;
bool changed = false;
for (mlir::Value operand : op.getDestOperands()) {
auto it = ehTokenMap.find(operand);
if (it != ehTokenMap.end()) {
newOperands.push_back(it->second.first);
newOperands.push_back(it->second.second);
changed = true;
} else {
newOperands.push_back(operand);
}
}
if (changed) {
builder.setInsertionPoint(op);
cir::BrOp::create(builder, op.getLoc(), op.getDest(), newOperands);
op.erase();
}
}
}
}
initiateOp.erase();
}
/// Lower a cir.eh.dispatch by creating a comparison chain in new blocks.
/// The dispatch itself is replaced with a branch to the first comparison
/// block and added to deadOps for deferred removal.
void ItaniumEHLowering::lowerDispatch(
cir::EhDispatchOp dispatch, mlir::Value exnPtr, mlir::Value typeId,
SmallVectorImpl<mlir::Operation *> &deadOps) {
mlir::Location dispLoc = dispatch.getLoc();
mlir::Block *defaultDest = dispatch.getDefaultDestination();
mlir::ArrayAttr catchTypes = dispatch.getCatchTypesAttr();
mlir::SuccessorRange catchDests = dispatch.getCatchDestinations();
mlir::Block *dispatchBlock = dispatch->getBlock();
// Build the comparison chain in new blocks inserted after the dispatch's
// block. The dispatch itself is replaced with a branch to the first
// comparison block and scheduled for deferred removal.
if (!catchTypes || catchTypes.empty()) {
// No typed catches: replace dispatch with a direct branch.
builder.setInsertionPoint(dispatch);
cir::BrOp::create(builder, dispLoc, defaultDest,
mlir::ValueRange{exnPtr, typeId});
} else {
unsigned numCatches = catchTypes.size();
// Create and populate comparison blocks in reverse order so that each
// block's false destination (the next comparison block, or defaultDest
// for the last one) is already available. Each createBlock inserts
// before the previous one, so the blocks end up in forward order.
mlir::Block *insertBefore = dispatchBlock->getNextNode();
mlir::Block *falseDest = defaultDest;
mlir::Block *firstCmpBlock = nullptr;
for (int i = numCatches - 1; i >= 0; --i) {
auto *cmpBlock = builder.createBlock(insertBefore, {voidPtrType, u32Type},
{dispLoc, dispLoc});
mlir::Value cmpExnPtr = cmpBlock->getArgument(0);
mlir::Value cmpTypeId = cmpBlock->getArgument(1);
auto globalView = mlir::cast<cir::GlobalViewAttr>(catchTypes[i]);
auto ehTypeIdOp =
cir::EhTypeIdOp::create(builder, dispLoc, globalView.getSymbol());
auto cmpOp = cir::CmpOp::create(builder, dispLoc, cir::CmpOpKind::eq,
cmpTypeId, ehTypeIdOp.getTypeId());
cir::BrCondOp::create(builder, dispLoc, cmpOp, catchDests[i], falseDest,
mlir::ValueRange{cmpExnPtr, cmpTypeId},
mlir::ValueRange{cmpExnPtr, cmpTypeId});
insertBefore = cmpBlock;
falseDest = cmpBlock;
firstCmpBlock = cmpBlock;
}
// Replace the dispatch with a branch to the first comparison block.
builder.setInsertionPoint(dispatch);
cir::BrOp::create(builder, dispLoc, firstCmpBlock,
mlir::ValueRange{exnPtr, typeId});
}
// Schedule the dispatch for deferred removal. We cannot erase it now because
// a sibling initiate that shares this dispatch may still need to read its
// catch types.
deadOps.push_back(dispatch);
}
//===----------------------------------------------------------------------===//
// The Pass
//===----------------------------------------------------------------------===//
struct CIREHABILoweringPass
: public impl::CIREHABILoweringBase<CIREHABILoweringPass> {
CIREHABILoweringPass() = default;
void runOnOperation() override;
};
void CIREHABILoweringPass::runOnOperation() {
auto mod = mlir::cast<mlir::ModuleOp>(getOperation());
// The target triple is attached to the module as the "cir.triple" attribute.
// If it is absent (e.g. a CIR module parsed from text without a triple) we
// cannot determine the ABI and must skip the pass.
auto tripleAttr = mlir::dyn_cast_if_present<mlir::StringAttr>(
mod->getAttr(cir::CIRDialect::getTripleAttrName()));
if (!tripleAttr) {
mod.emitError("Module has no target triple");
return;
}
// Select the ABI-specific lowering handler from the triple. The Microsoft
// C++ ABI targets a Windows MSVC environment; everything else uses Itanium.
// Extend this when Microsoft ABI lowering is added.
llvm::Triple triple(tripleAttr.getValue());
std::unique_ptr<EHABILowering> lowering;
if (triple.isWindowsMSVCEnvironment()) {
mod.emitError(
"EH ABI lowering is not yet implemented for the Microsoft ABI");
return signalPassFailure();
} else {
lowering = std::make_unique<ItaniumEHLowering>(mod);
}
if (mlir::failed(lowering->run()))
return signalPassFailure();
}
} // namespace
std::unique_ptr<Pass> mlir::createCIREHABILoweringPass() {
return std::make_unique<CIREHABILoweringPass>();
}

View File

@ -49,6 +49,7 @@ namespace mlir {
void populateCIRPreLoweringPasses(OpPassManager &pm) {
pm.addPass(createHoistAllocasPass());
pm.addPass(createCIRFlattenCFGPass());
pm.addPass(createCIREHABILoweringPass());
pm.addPass(createGotoSolverPass());
}

View File

@ -1837,7 +1837,9 @@ static mlir::LogicalResult
rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
mlir::ConversionPatternRewriter &rewriter,
const mlir::TypeConverter *converter,
mlir::FlatSymbolRefAttr calleeAttr) {
mlir::FlatSymbolRefAttr calleeAttr,
mlir::Block *continueBlock = nullptr,
mlir::Block *landingPadBlock = nullptr) {
llvm::SmallVector<mlir::Type, 8> llvmResults;
mlir::ValueTypeRange<mlir::ResultRange> cirResults = op->getResultTypes();
auto call = cast<cir::CIRCallOpInterface>(op);
@ -1908,18 +1910,23 @@ rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
converter->convertType(calleeFuncTy));
}
assert(!cir::MissingFeatures::opCallLandingPad());
assert(!cir::MissingFeatures::opCallContinueBlock());
assert(!cir::MissingFeatures::opCallCallConv());
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
op, llvmFnTy, calleeAttr, callOperands);
newOp->setAttrs(attributes);
if (memoryEffects)
newOp.setMemoryEffectsAttr(memoryEffects);
newOp.setNoUnwind(noUnwind);
newOp.setWillReturn(willReturn);
newOp.setNoreturn(noReturn);
if (landingPadBlock) {
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::InvokeOp>(
op, llvmFnTy, calleeAttr, callOperands, continueBlock,
mlir::ValueRange{}, landingPadBlock, mlir::ValueRange{});
newOp->setAttrs(attributes);
} else {
auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
op, llvmFnTy, calleeAttr, callOperands);
newOp->setAttrs(attributes);
if (memoryEffects)
newOp.setMemoryEffectsAttr(memoryEffects);
newOp.setNoUnwind(noUnwind);
newOp.setWillReturn(willReturn);
newOp.setNoreturn(noReturn);
}
return mlir::success();
}
@ -1931,6 +1938,15 @@ mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite(
getTypeConverter(), op.getCalleeAttr());
}
mlir::LogicalResult CIRToLLVMTryCallOpLowering::matchAndRewrite(
cir::TryCallOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
assert(!cir::MissingFeatures::opCallCallConv());
return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter,
getTypeConverter(), op.getCalleeAttr(),
op.getNormalDest(), op.getUnwindDest());
}
mlir::LogicalResult CIRToLLVMReturnAddrOpLowering::matchAndRewrite(
cir::ReturnAddrOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {

View File

@ -1,8 +1,12 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
// TODO(cir): Merge this with try-catch.cpp.
int division();
void call_function_inside_try_catch_all() {
@ -29,6 +33,43 @@ void call_function_inside_try_catch_all() {
// CIR: }
// CIR: }
// LLVM: define {{.*}} void @_Z34call_function_inside_try_catch_allv() {{.*}} personality ptr @__gxx_personality_v0
// LLVM: br label %[[TRY_SCOPE:.*]]
// LLVM: [[TRY_SCOPE]]:
// LLVM: br label %[[TRY_BEGIN:.*]]
// LLVM: [[TRY_BEGIN]]:
// LLVM: %[[CALL:.*]] = invoke i32 @_Z8divisionv()
// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
// LLVM: [[INVOKE_CONT]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[LANDING_PAD]]:
// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
// LLVM: catch ptr null
// LLVM: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
// LLVM: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
// LLVM: br label %[[CATCH:.*]]
// LLVM: [[CATCH]]:
// LLVM: %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: br label %[[BEGIN_CATCH:.*]]
// LLVM: [[BEGIN_CATCH]]:
// LLVM: %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI1]])
// LLVM: br label %[[CATCH_BODY:.*]]
// LLVM: [[CATCH_BODY]]:
// LLVM: br label %[[END_CATCH:.*]]
// LLVM: [[END_CATCH]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH:.*]]
// LLVM: [[END_DISPATCH]]:
// LLVM: br label %[[END_TRY:.*]]
// LLVM: [[END_TRY]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[TRY_CONT]]:
// LLVM: br label %[[DONE:.*]]
// LLVM: [[DONE]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z34call_function_inside_try_catch_allv() #0 personality ptr @__gxx_personality_v0
// OGCG: %[[EXN_OBJ_ADDR:.*]] = alloca ptr, align 8
@ -82,6 +123,58 @@ void call_function_inside_try_catch_with_exception_type() {
// CIR: }
// CIR: }
// LLVM: define {{.*}} void @_Z50call_function_inside_try_catch_with_exception_typev() {{.*}} personality ptr @__gxx_personality_v0
// LLVM: br label %[[TRY_SCOPE:.*]]
// LLVM: [[TRY_SCOPE]]:
// LLVM: br label %[[TRY_BEGIN:.*]]
// LLVM: [[TRY_BEGIN]]:
// LLVM: %[[CALL:.*]] = invoke i32 @_Z8divisionv()
// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
// LLVM: [[INVOKE_CONT]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[LANDING_PAD]]:
// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
// LLVM: catch ptr @_ZTIi
// LLVM: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
// LLVM: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
// LLVM: br label %[[CATCH:.*]]
// LLVM: [[CATCH]]:
// LLVM: %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: br label %[[DISPATCH:.*]]
// LLVM: [[DISPATCH]]:
// LLVM: %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
// LLVM: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], %[[EH_TYPE_ID]]
// LLVM: br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label %[[RESUME:.*]]
// LLVM: [[BEGIN_CATCH]]:
// LLVM: %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
// LLVM: br label %[[CATCH_BODY:.*]]
// LLVM: [[CATCH_BODY]]:
// LLVM: %[[LOAD:.*]] = load i32, ptr %[[TOKEN]], align 4
// LLVM: store i32 %[[LOAD]], ptr {{.*}}, align 4
// LLVM: br label %[[END_CATCH:.*]]
// LLVM: [[END_CATCH]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH:.*]]
// LLVM: [[END_DISPATCH]]:
// LLVM: br label %[[END_TRY:.*]]
// LLVM: [[END_TRY]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[RESUME]]:
// LLVM: %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr %[[EXN_OBJ_PHI3]], 0
// LLVM: %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } %[[TMP_EXCEPTION_INFO]], i32 %[[EH_SELECTOR_PHI3]], 1
// LLVM: resume { ptr, i32 } %[[EXCEPTION_INFO]]
// LLVM: [[TRY_CONT]]:
// LLVM: br label %[[DONE:.*]]
// LLVM: [[DONE]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z50call_function_inside_try_catch_with_exception_typev() #0 personality ptr @__gxx_personality_v0
// OGCG: %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
// OGCG: %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@ -148,6 +241,58 @@ void call_function_inside_try_catch_with_complex_exception_type() {
// CIR: }
// CIR: }
// LLVM: define {{.*}} void @_Z58call_function_inside_try_catch_with_complex_exception_typev() {{.*}} personality ptr @__gxx_personality_v0
// LLVM: br label %[[TRY_SCOPE:.*]]
// LLVM: [[TRY_SCOPE]]:
// LLVM: br label %[[TRY_BEGIN:.*]]
// LLVM: [[TRY_BEGIN]]:
// LLVM: %[[CALL:.*]] = invoke i32 @_Z8divisionv()
// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
// LLVM: [[INVOKE_CONT]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[LANDING_PAD]]:
// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
// LLVM: catch ptr @_ZTICi
// LLVM: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
// LLVM: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
// LLVM: br label %[[CATCH:.*]]
// LLVM: [[CATCH]]:
// LLVM: %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: br label %[[DISPATCH:.*]]
// LLVM: [[DISPATCH]]:
// LLVM: %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTICi)
// LLVM: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], %[[EH_TYPE_ID]]
// LLVM: br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label %[[RESUME:.*]]
// LLVM: [[BEGIN_CATCH]]:
// LLVM: %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
// LLVM: br label %[[CATCH_BODY:.*]]
// LLVM: [[CATCH_BODY]]:
// LLVM: %[[LOAD:.*]] = load { i32, i32 }, ptr %[[TOKEN]], align 4
// LLVM: store { i32, i32 } %[[LOAD]], ptr {{.*}}, align 4
// LLVM: br label %[[END_CATCH:.*]]
// LLVM: [[END_CATCH]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH:.*]]
// LLVM: [[END_DISPATCH]]:
// LLVM: br label %[[END_TRY:.*]]
// LLVM: [[END_TRY]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[RESUME]]:
// LLVM: %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr %[[EXN_OBJ_PHI3]], 0
// LLVM: %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } %[[TMP_EXCEPTION_INFO]], i32 %[[EH_SELECTOR_PHI3]], 1
// LLVM: resume { ptr, i32 } %[[EXCEPTION_INFO]]
// LLVM: [[TRY_CONT]]:
// LLVM: br label %[[DONE:.*]]
// LLVM: [[DONE]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z58call_function_inside_try_catch_with_complex_exception_typev() #0 personality ptr @__gxx_personality_v0
// OGCG: %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
// OGCG: %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@ -219,6 +364,57 @@ void call_function_inside_try_catch_with_array_exception_type() {
// CIR: }
// CIR: }
// LLVM: define {{.*}} void @_Z56call_function_inside_try_catch_with_array_exception_typev() {{.*}} personality ptr @__gxx_personality_v0
// LLVM: br label %[[TRY_SCOPE:.*]]
// LLVM: [[TRY_SCOPE]]:
// LLVM: br label %[[TRY_BEGIN:.*]]
// LLVM: [[TRY_BEGIN]]:
// LLVM: %[[CALL:.*]] = invoke i32 @_Z8divisionv()
// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
// LLVM: [[INVOKE_CONT]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[LANDING_PAD]]:
// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
// LLVM: catch ptr @_ZTIPi
// LLVM: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
// LLVM: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
// LLVM: br label %[[CATCH:.*]]
// LLVM: [[CATCH]]:
// LLVM: %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: br label %[[DISPATCH:.*]]
// LLVM: [[DISPATCH]]:
// LLVM: %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIPi)
// LLVM: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], %[[EH_TYPE_ID]]
// LLVM: br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label %[[RESUME:.*]]
// LLVM: [[BEGIN_CATCH]]:
// LLVM: %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
// LLVM: br label %[[CATCH_BODY:.*]]
// LLVM: [[CATCH_BODY]]:
// LLVM: store ptr %[[TOKEN]], ptr {{.*}}, align 8
// LLVM: br label %[[END_CATCH:.*]]
// LLVM: [[END_CATCH]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH:.*]]
// LLVM: [[END_DISPATCH]]:
// LLVM: br label %[[END_TRY:.*]]
// LLVM: [[END_TRY]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[RESUME]]:
// LLVM: %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TMP_EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } poison, ptr %[[EXN_OBJ_PHI3]], 0
// LLVM: %[[EXCEPTION_INFO:.*]] = insertvalue { ptr, i32 } %[[TMP_EXCEPTION_INFO]], i32 %[[EH_SELECTOR_PHI3]], 1
// LLVM: resume { ptr, i32 } %[[EXCEPTION_INFO]]
// LLVM: [[TRY_CONT]]:
// LLVM: br label %[[DONE:.*]]
// LLVM: [[DONE]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z56call_function_inside_try_catch_with_array_exception_typev() #0 personality ptr @__gxx_personality_v0
// OGCG: %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
// OGCG: %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@ -292,6 +488,66 @@ void call_function_inside_try_catch_with_exception_type_and_catch_all() {
// CIR: }
// CIR: }
// LLVM: define {{.*}} void @_Z64call_function_inside_try_catch_with_exception_type_and_catch_allv() {{.*}} personality ptr @__gxx_personality_v0
// LLVM: br label %[[TRY_SCOPE:.*]]
// LLVM: [[TRY_SCOPE]]:
// LLVM: br label %[[TRY_BEGIN:.*]]
// LLVM: [[TRY_BEGIN]]:
// LLVM: %[[CALL:.*]] = invoke i32 @_Z8divisionv()
// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
// LLVM: [[INVOKE_CONT]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[LANDING_PAD]]:
// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
// LLVM: catch ptr @_ZTIi
// LLVM: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
// LLVM: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
// LLVM: br label %[[CATCH:.*]]
// LLVM: [[CATCH]]:
// LLVM: %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: br label %[[DISPATCH:.*]]
// LLVM: [[DISPATCH]]:
// LLVM: %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_TYPE_ID:.*]] = call i32 @llvm.eh.typeid.for.p0(ptr @_ZTIi)
// LLVM: %[[TYPE_ID_EQ:.*]] = icmp eq i32 %[[EH_SELECTOR_PHI1]], %[[EH_TYPE_ID]]
// LLVM: br i1 %[[TYPE_ID_EQ]], label %[[BEGIN_CATCH:.*]], label %[[CATCH_ALL:.*]]
// LLVM: [[BEGIN_CATCH]]:
// LLVM: %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
// LLVM: br label %[[CATCH_BODY:.*]]
// LLVM: [[CATCH_BODY]]:
// LLVM: %[[LOAD:.*]] = load i32, ptr %[[TOKEN]], align 4
// LLVM: store i32 %[[LOAD]], ptr {{.*}}, align 4
// LLVM: br label %[[END_CATCH:.*]]
// LLVM: [[END_CATCH]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH:.*]]
// LLVM: [[END_DISPATCH]]:
// LLVM: br label %[[END_TRY:.*]]
// LLVM: [[END_TRY]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[CATCH_ALL]]:
// LLVM: %[[EXN_OBJ_PHI3:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI3:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[DISPATCH:.*]] ]
// LLVM: %[[TOKEN2:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI3]])
// LLVM: br label %[[CATCH_ALL_BODY:.*]]
// LLVM: [[CATCH_ALL_BODY]]:
// LLVM: br label %[[END_CATCH2:.*]]
// LLVM: [[END_CATCH2]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH2:.*]]
// LLVM: [[END_DISPATCH2]]:
// LLVM: br label %[[END_TRY2:.*]]
// LLVM: [[END_TRY2]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[TRY_CONT]]:
// LLVM: br label %[[DONE:.*]]
// LLVM: [[DONE]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z64call_function_inside_try_catch_with_exception_type_and_catch_allv() #0 personality ptr @__gxx_personality_v0
// OGCG: %[[EXCEPTION_ADDR:.*]] = alloca ptr, align 8
// OGCG: %[[EH_TYPE_ID_ADDR:.*]] = alloca i32, align 4
@ -364,6 +620,56 @@ void cleanup_inside_try_body() {
// CIR: }
// CIR: }
// LLVM: define {{.*}} void @_Z23cleanup_inside_try_bodyv() {{.*}} personality ptr @__gxx_personality_v0
// LLVM: br label %[[TRY_SCOPE:.*]]
// LLVM: [[TRY_SCOPE]]:
// LLVM: br label %[[TRY_BEGIN:.*]]
// LLVM: [[TRY_BEGIN]]:
// LLVM: br label %[[CLEANUP_SCOPE:.*]]
// LLVM: [[CLEANUP_SCOPE]]:
// LLVM: %[[CALL:.*]] = invoke i32 @_Z8divisionv()
// LLVM: to label %[[INVOKE_CONT:.*]] unwind label %[[LANDING_PAD:.*]]
// LLVM: [[INVOKE_CONT]]:
// LLVM: br label %[[CLEANUP:.*]]
// LLVM: [[CLEANUP]]:
// LLVM: call void @_ZN1SD1Ev(ptr {{.*}})
// LLVM: br label %[[END_CLEANUP:.*]]
// LLVM: [[END_CLEANUP]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[LANDING_PAD]]:
// LLVM: %[[LP:.*]] = landingpad { ptr, i32 }
// LLVM: cleanup
// LLVM: %[[EXN_OBJ:.*]] = extractvalue { ptr, i32 } %[[LP]], 0
// LLVM: %[[EH_SELECTOR_VAL:.*]] = extractvalue { ptr, i32 } %[[LP]], 1
// LLVM: br label %[[CLEANUP_LANDING:.*]]
// LLVM: [[CLEANUP_LANDING]]:
// LLVM: %[[EXN_OBJ_PHI:.*]] = phi ptr [ %[[EXN_OBJ:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI:.*]] = phi i32 [ %[[EH_SELECTOR_VAL:.*]], %[[LANDING_PAD:.*]] ]
// LLVM: call void @_ZN1SD1Ev(ptr {{.*}})
// LLVM: br label %[[CATCH:.*]]
// LLVM: [[CATCH]]:
// LLVM: %[[EXN_OBJ_PHI1:.*]] = phi ptr [ %[[EXN_OBJ_PHI:.*]], %[[CLEANUP_LANDING:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI1:.*]] = phi i32 [ %[[EH_SELECTOR_PHI:.*]], %[[CLEANUP_LANDING:.*]] ]
// LLVM: br label %[[BEGIN_CATCH:.*]]
// LLVM: [[BEGIN_CATCH]]:
// LLVM: %[[EXN_OBJ_PHI2:.*]] = phi ptr [ %[[EXN_OBJ_PHI1:.*]], %[[CATCH:.*]] ]
// LLVM: %[[EH_SELECTOR_PHI2:.*]] = phi i32 [ %[[EH_SELECTOR_PHI1:.*]], %[[CATCH:.*]] ]
// LLVM: %[[TOKEN:.*]] = call ptr @__cxa_begin_catch(ptr %[[EXN_OBJ_PHI2]])
// LLVM: br label %[[CATCH_BODY:.*]]
// LLVM: [[CATCH_BODY]]:
// LLVM: br label %[[END_CATCH:.*]]
// LLVM: [[END_CATCH]]:
// LLVM: call void @__cxa_end_catch()
// LLVM: br label %[[END_DISPATCH:.*]]
// LLVM: [[END_DISPATCH]]:
// LLVM: br label %[[END_TRY:.*]]
// LLVM: [[END_TRY]]:
// LLVM: br label %[[TRY_CONT:.*]]
// LLVM: [[TRY_CONT]]:
// LLVM: br label %[[DONE:.*]]
// LLVM: [[DONE]]:
// LLVM: ret void
// OGCG: define {{.*}} void @_Z23cleanup_inside_try_bodyv() {{.*}} personality ptr @__gxx_personality_v0 {
// OGCG: %[[S:.*]] = alloca %struct.S
// OGCG: %[[EXN_SLOT:.*]] = alloca ptr

View File

@ -1,10 +1,10 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
// TODO(cir): Reenable lowering to LLVM after new try-catch lowering is implemented.
void empty_try_block_with_catch_all() {
try {} catch (...) {}
}

View File

@ -0,0 +1,514 @@
// RUN: cir-opt %s -cir-eh-abi-lowering -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s
// Test Itanium ABI lowering of flattened CIR exception handling operations.
// The input is the form produced by cir-flatten-cfg, and the output should
// have all EH-specific tokens replaced with concrete values and ABI calls.
!s32i = !cir.int<s, 32>
!u32i = !cir.int<u, 32>
!u8i = !cir.int<u, 8>
!void = !cir.void
!rec_SomeClass = !cir.record<struct "SomeClass" {!s32i}>
!rec_exception = !cir.record<struct "std::exception" {!s32i}>
module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
// Test: EH cleanup only (no catch handlers).
// cir.eh.initiate cleanup → cir.eh.inflight_exception cleanup
// cir.begin_cleanup / cir.end_cleanup → removed
// cir.resume → cir.resume.flat
cir.func @test_cleanup_only() {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
cir.call @ctor(%0) : (!cir.ptr<!rec_SomeClass>) -> ()
cir.try_call @doSomething(%0) ^bb1, ^bb2 : (!cir.ptr<!rec_SomeClass>) -> ()
^bb1:
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.br ^bb5
^bb2:
%1 = cir.eh.initiate cleanup : !cir.eh_token
cir.br ^bb3(%1 : !cir.eh_token)
^bb3(%eh_token : !cir.eh_token):
%2 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.end_cleanup %2 : !cir.cleanup_token
cir.resume %eh_token : !cir.eh_token
^bb5:
cir.return
}
// CHECK-LABEL: cir.func @test_cleanup_only()
// CHECK-SAME: personality(@__gxx_personality_v0)
// CHECK: cir.try_call @doSomething(%{{.*}}) ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// Normal path unchanged.
// CHECK: ^[[NORMAL]]:
// CHECK: cir.call @dtor
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
//
// Unwind: eh.initiate replaced with eh.inflight_exception.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception cleanup
// CHECK: cir.br ^[[CLEANUP:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i)
//
// Cleanup: begin_cleanup/end_cleanup removed, resume becomes resume.flat.
// CHECK: ^[[CLEANUP]](%[[C_EXN:.*]]: !cir.ptr<!void>, %[[C_TID:.*]]: !u32i):
// CHECK-NOT: cir.begin_cleanup
// CHECK: cir.call @dtor
// CHECK-NOT: cir.end_cleanup
// CHECK: cir.resume.flat %[[C_EXN]], %[[C_TID]]
//
// CHECK: ^[[RETURN]]:
// CHECK: cir.return
// Test: Try-catch with catch-all handler.
// cir.eh.dispatch [catch_all] → cir.br to catch-all block
// cir.begin_catch → __cxa_begin_catch + bitcast
// cir.end_catch → __cxa_end_catch
cir.func @test_catch_all() {
cir.try_call @mayThrow() ^bb1, ^bb2 : () -> ()
^bb1:
cir.br ^bb5
^bb2:
%1 = cir.eh.initiate : !cir.eh_token
cir.br ^bb3(%1 : !cir.eh_token)
^bb3(%eh_token : !cir.eh_token):
cir.eh.dispatch %eh_token : !cir.eh_token [
catch_all : ^bb4
]
^bb4(%eh_token.1 : !cir.eh_token):
%ct, %exn = cir.begin_catch %eh_token.1 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
cir.end_catch %ct : !cir.catch_token
cir.br ^bb5
^bb5:
cir.return
}
// CHECK-LABEL: cir.func @test_catch_all()
// CHECK-SAME: personality(@__gxx_personality_v0)
// CHECK: cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// CHECK: ^[[NORMAL]]:
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
//
// Landing pad with catch_all (no cleanup, no typed catches).
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception
// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i)
//
// Dispatch replaced with direct branch to catch-all handler.
// CHECK: ^[[DISPATCH]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: !u32i):
// CHECK: cir.br ^[[CATCH_ALL:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i)
//
// Catch-all: begin_catch → __cxa_begin_catch, end_catch → __cxa_end_catch.
// CHECK: ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: %{{.*}} = cir.call @__cxa_begin_catch(%[[CA_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// CHECK: ^[[RETURN]]:
// CHECK: cir.return
// Test: Try-catch with typed handler and unwind.
// cir.eh.dispatch with typed catch → typeid + cmp + brcond chain
// unwind handler → cir.resume.flat
cir.func @test_typed_catch_with_unwind() {
cir.try_call @mayThrow() ^bb1, ^bb2 : () -> ()
^bb1:
cir.br ^bb6
^bb2:
%1 = cir.eh.initiate : !cir.eh_token
cir.br ^bb3(%1 : !cir.eh_token)
^bb3(%eh_token : !cir.eh_token):
cir.eh.dispatch %eh_token : !cir.eh_token [
catch(#cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb4,
unwind : ^bb5
]
^bb4(%eh_token.1 : !cir.eh_token):
%ct, %exn = cir.begin_catch %eh_token.1 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
cir.end_catch %ct : !cir.catch_token
cir.br ^bb6
^bb5(%eh_token.2 : !cir.eh_token):
cir.resume %eh_token.2 : !cir.eh_token
^bb6:
cir.return
}
// CHECK-LABEL: cir.func @test_typed_catch_with_unwind()
// CHECK-SAME: personality(@__gxx_personality_v0)
// CHECK: cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// CHECK: ^[[NORMAL]]:
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
//
// Landing pad with typed catch list.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception [@_ZTISt9exception]
// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i)
//
// Dispatch: branch to comparison chain.
// CHECK: ^[[DISPATCH]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.br ^[[CMP_BLOCK:bb[0-9]+]]
//
// Comparison: typeid comparison + brcond.
// CHECK: ^[[CMP_BLOCK]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: !u32i):
// CHECK: %[[TYPE_ID:.*]] = cir.eh.typeid @_ZTISt9exception
// CHECK: %[[CMP:.*]] = cir.cmp(eq, %[[D_TID]], %[[TYPE_ID]])
// CHECK: cir.brcond %[[CMP]] ^[[CATCH:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i), ^[[UNWIND_HANDLER:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i)
//
// Typed catch handler.
// CHECK: ^[[CATCH]](%[[C_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: %{{.*}} = cir.call @__cxa_begin_catch(%[[C_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// Unwind handler: resume.flat.
// CHECK: ^[[UNWIND_HANDLER]](%[[UW_EXN:.*]]: !cir.ptr<!void>, %[[UW_TID:.*]]: !u32i):
// CHECK: cir.resume.flat %[[UW_EXN]], %[[UW_TID]]
//
// CHECK: ^[[RETURN]]:
// CHECK: cir.return
// Test: Try-catch with multiple typed handlers and catch-all.
// Dispatch → chain of typeid comparisons, catch-all as final default.
cir.func @test_multiple_typed_and_catch_all() {
cir.try_call @mayThrow() ^bb1, ^bb2 : () -> ()
^bb1:
cir.br ^bb7
^bb2:
%1 = cir.eh.initiate : !cir.eh_token
cir.br ^bb3(%1 : !cir.eh_token)
^bb3(%eh_token : !cir.eh_token):
cir.eh.dispatch %eh_token : !cir.eh_token [
catch(#cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb4,
catch(#cir.global_view<@_ZTIi> : !cir.ptr<!u8i>) : ^bb5,
catch_all : ^bb6
]
^bb4(%eh_token.1 : !cir.eh_token):
%ct1, %exn1 = cir.begin_catch %eh_token.1 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
cir.end_catch %ct1 : !cir.catch_token
cir.br ^bb7
^bb5(%eh_token.2 : !cir.eh_token):
%ct2, %exn2 = cir.begin_catch %eh_token.2 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!s32i>)
cir.end_catch %ct2 : !cir.catch_token
cir.br ^bb7
^bb6(%eh_token.3 : !cir.eh_token):
%ct3, %exn3 = cir.begin_catch %eh_token.3 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
cir.end_catch %ct3 : !cir.catch_token
cir.br ^bb7
^bb7:
cir.return
}
// CHECK-LABEL: cir.func @test_multiple_typed_and_catch_all()
// CHECK-SAME: personality(@__gxx_personality_v0)
// CHECK: cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// CHECK: ^[[NORMAL]]:
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
//
// Landing pad with typed catches AND catch-all.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception [@_ZTISt9exception, @_ZTIi]
// CHECK: cir.br ^[[CMP1_BLOCK:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i)
//
// Dispatch: branch to comparison chain.
// CHECK: ^[[CMP1_BLOCK]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.br ^[[CMP1:bb[0-9]+]]
//
// First type comparison (exception type).
// CHECK: ^[[CMP1]](%[[C1_EXN:.*]]: !cir.ptr<!void>, %[[C1_TID:.*]]: !u32i):
// CHECK: %[[TID1:.*]] = cir.eh.typeid @_ZTISt9exception
// CHECK: %[[CMP1V:.*]] = cir.cmp(eq, %[[C1_TID]], %[[TID1]])
// CHECK: cir.brcond %[[CMP1V]] ^[[CATCH_EXN:bb[0-9]+]](%[[C1_EXN]], %[[C1_TID]] : !cir.ptr<!void>, !u32i), ^[[CMP2_BLOCK:bb[0-9]+]](%[[C1_EXN]], %[[C1_TID]] : !cir.ptr<!void>, !u32i)
//
// Second type comparison (int type).
// CHECK: ^[[CMP2_BLOCK]](%[[C2_EXN:.*]]: !cir.ptr<!void>, %[[C2_TID:.*]]: !u32i):
// CHECK: %[[TID2:.*]] = cir.eh.typeid @_ZTIi
// CHECK: %[[CMP2:.*]] = cir.cmp(eq, %[[C2_TID]], %[[TID2]])
// CHECK: cir.brcond %[[CMP2]] ^[[CATCH_INT:bb[0-9]+]](%[[C2_EXN]], %[[C2_TID]] : !cir.ptr<!void>, !u32i), ^[[CATCH_ALL:bb[0-9]+]](%[[C2_EXN]], %[[C2_TID]] : !cir.ptr<!void>, !u32i)
//
// Exception catch handler.
// CHECK: ^[[CATCH_EXN]](%[[CE_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[CE_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// Int catch handler.
// CHECK: ^[[CATCH_INT]](%[[CI_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[CI_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// Catch-all handler.
// CHECK: ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[CA_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// CHECK: ^[[RETURN]]:
// CHECK: cir.return
// Test: Try-catch with cleanup + catch handler.
// Two landing pads: one without cleanup (ctor), one with cleanup (doSomething).
// cleanup ops are removed, cleanup code stays.
cir.func @test_catch_with_cleanup() {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
cir.try_call @ctor(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> ()
^bb1:
cir.try_call @doSomething(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> ()
^bb2:
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.br ^bb8
^bb3:
%1 = cir.eh.initiate : !cir.eh_token
cir.br ^bb6(%1 : !cir.eh_token)
^bb4:
%2 = cir.eh.initiate cleanup : !cir.eh_token
cir.br ^bb5(%2 : !cir.eh_token)
^bb5(%eh_token : !cir.eh_token):
%3 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.end_cleanup %3 : !cir.cleanup_token
cir.br ^bb6(%eh_token : !cir.eh_token)
^bb6(%eh_token.1 : !cir.eh_token):
cir.eh.dispatch %eh_token.1 : !cir.eh_token [
catch_all : ^bb7
]
^bb7(%eh_token.2 : !cir.eh_token):
%ct, %exn = cir.begin_catch %eh_token.2 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
cir.end_catch %ct : !cir.catch_token
cir.br ^bb8
^bb8:
cir.return
}
// CHECK-LABEL: cir.func @test_catch_with_cleanup()
// CHECK-SAME: personality(@__gxx_personality_v0)
//
// Entry: ctor call.
// CHECK: cir.try_call @ctor(%{{.*}}) ^[[AFTER_CTOR:bb[0-9]+]], ^[[CTOR_UNWIND:bb[0-9]+]]
//
// After ctor: doSomething call.
// CHECK: ^[[AFTER_CTOR]]:
// CHECK: cir.try_call @doSomething(%{{.*}}) ^[[NORMAL_CLEANUP:bb[0-9]+]], ^[[DO_UNWIND:bb[0-9]+]]
//
// Normal cleanup.
// CHECK: ^[[NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
//
// Ctor unwind: landing pad without cleanup.
// CHECK: ^[[CTOR_UNWIND]]:
// CHECK: %[[E1:.*]], %[[T1:.*]] = cir.eh.inflight_exception
// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[E1]], %[[T1]] : !cir.ptr<!void>, !u32i)
//
// doSomething unwind: landing pad with cleanup.
// CHECK: ^[[DO_UNWIND]]:
// CHECK: %[[E2:.*]], %[[T2:.*]] = cir.eh.inflight_exception cleanup
// CHECK: cir.br ^[[CLEANUP:bb[0-9]+]](%[[E2]], %[[T2]] : !cir.ptr<!void>, !u32i)
//
// EH cleanup: begin_cleanup and end_cleanup are REMOVED, dtor call stays.
// CHECK: ^[[CLEANUP]](%[[CL_EXN:.*]]: !cir.ptr<!void>, %[[CL_TID:.*]]: !u32i):
// CHECK-NOT: cir.begin_cleanup
// CHECK: cir.call @dtor
// CHECK-NOT: cir.end_cleanup
// CHECK: cir.br ^[[DISPATCH]](%[[CL_EXN]], %[[CL_TID]] : !cir.ptr<!void>, !u32i)
//
// Dispatch: catch-all handler (direct branch).
// CHECK: ^[[DISPATCH]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: !u32i):
// CHECK: cir.br ^[[CATCH_ALL:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i)
//
// Catch-all handler.
// CHECK: ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[CA_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// CHECK: ^[[RETURN]]:
// CHECK: cir.return
// Test: Try-catch with cleanup + typed catch handler.
// Like test_catch_with_cleanup but with a typed catch instead of catch-all.
// Having a typed catch in the dispatch causes both eh.initiate sites to
// include @_ZTISt9exception in their catch_type_list.
cir.func @test_typed_catch_with_cleanup() {
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["c", init] {alignment = 4 : i64}
cir.try_call @ctor(%0) ^bb1, ^bb3 : (!cir.ptr<!rec_SomeClass>) -> ()
^bb1:
cir.try_call @doSomething(%0) ^bb2, ^bb4 : (!cir.ptr<!rec_SomeClass>) -> ()
^bb2:
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.br ^bb9
^bb3:
%1 = cir.eh.initiate : !cir.eh_token
cir.br ^bb6(%1 : !cir.eh_token)
^bb4:
%2 = cir.eh.initiate cleanup : !cir.eh_token
cir.br ^bb5(%2 : !cir.eh_token)
^bb5(%eh_token : !cir.eh_token):
%3 = cir.begin_cleanup %eh_token : !cir.eh_token -> !cir.cleanup_token
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.end_cleanup %3 : !cir.cleanup_token
cir.br ^bb6(%eh_token : !cir.eh_token)
^bb6(%eh_token.1 : !cir.eh_token):
cir.eh.dispatch %eh_token.1 : !cir.eh_token [
catch(#cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb7,
unwind : ^bb8
]
^bb7(%eh_token.2 : !cir.eh_token):
%ct, %exn = cir.begin_catch %eh_token.2 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
cir.end_catch %ct : !cir.catch_token
cir.br ^bb9
^bb8(%eh_token.3 : !cir.eh_token):
cir.resume %eh_token.3 : !cir.eh_token
^bb9:
cir.return
}
// CHECK-LABEL: cir.func @test_typed_catch_with_cleanup()
// CHECK-SAME: personality(@__gxx_personality_v0)
//
// Entry: ctor call.
// CHECK: cir.try_call @ctor(%{{.*}}) ^[[AFTER_CTOR:bb[0-9]+]], ^[[CTOR_UNWIND:bb[0-9]+]]
//
// After ctor: doSomething call.
// CHECK: ^[[AFTER_CTOR]]:
// CHECK: cir.try_call @doSomething(%{{.*}}) ^[[NORMAL_CLEANUP:bb[0-9]+]], ^[[DO_UNWIND:bb[0-9]+]]
//
// Normal cleanup.
// CHECK: ^[[NORMAL_CLEANUP]]:
// CHECK: cir.call @dtor
// CHECK: cir.br ^[[RETURN:bb[0-9]+]]
//
// Ctor unwind: landing pad without cleanup; typed catch list from shared dispatch.
// CHECK: ^[[CTOR_UNWIND]]:
// CHECK: %[[E1:.*]], %[[T1:.*]] = cir.eh.inflight_exception [@_ZTISt9exception]
// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[E1]], %[[T1]] : !cir.ptr<!void>, !u32i)
//
// doSomething unwind: landing pad with cleanup and same typed catch list.
// CHECK: ^[[DO_UNWIND]]:
// CHECK: %[[E2:.*]], %[[T2:.*]] = cir.eh.inflight_exception cleanup [@_ZTISt9exception]
// CHECK: cir.br ^[[CLEANUP:bb[0-9]+]](%[[E2]], %[[T2]] : !cir.ptr<!void>, !u32i)
//
// EH cleanup: begin_cleanup and end_cleanup are REMOVED, dtor call stays.
// CHECK: ^[[CLEANUP]](%[[CL_EXN:.*]]: !cir.ptr<!void>, %[[CL_TID:.*]]: !u32i):
// CHECK-NOT: cir.begin_cleanup
// CHECK: cir.call @dtor
// CHECK-NOT: cir.end_cleanup
// CHECK: cir.br ^[[DISPATCH]](%[[CL_EXN]], %[[CL_TID]] : !cir.ptr<!void>, !u32i)
//
// Dispatch: branch to comparison chain.
// CHECK: ^[[DISPATCH]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.br ^[[CMP_BLOCK:bb[0-9]+]]
//
// Comparison: typeid comparison for std::exception, with unwind fallthrough.
// CHECK: ^[[CMP_BLOCK]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: !u32i):
// CHECK: %[[TYPE_ID:.*]] = cir.eh.typeid @_ZTISt9exception
// CHECK: %[[CMP:.*]] = cir.cmp(eq, %[[D_TID]], %[[TYPE_ID]])
// CHECK: cir.brcond %[[CMP]] ^[[CATCH:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i), ^[[UNWIND_HANDLER:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i)
//
// Typed catch handler.
// CHECK: ^[[CATCH]](%[[C_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[C_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// CHECK: cir.br ^[[RETURN]]
//
// Unwind handler: resume.flat.
// CHECK: ^[[UNWIND_HANDLER]](%[[UW_EXN:.*]]: !cir.ptr<!void>, %[[UW_TID:.*]]: !u32i):
// CHECK: cir.resume.flat %[[UW_EXN]], %[[UW_TID]]
//
// CHECK: ^[[RETURN]]:
// CHECK: cir.return
// Test: try-catch with a local variable that has a non-trivial destructor.
// The cleanup path calls ~S() then falls through to the catch-all dispatch.
// begin_cleanup/end_cleanup are removed; typed catch + catch-all dispatch is lowered.
cir.func no_inline dso_local @test_catch_with_cleanup_no_ctor() personality(@__gxx_personality_v0) {
cir.br ^bb1
^bb1: // pred: ^bb0
%0 = cir.alloca !rec_SomeClass, !cir.ptr<!rec_SomeClass>, ["s"] {alignment = 1 : i64}
cir.br ^bb2
^bb2: // pred: ^bb1
cir.try_call @mayThrow() ^bb3, ^bb4 : () -> ()
^bb3: // pred: ^bb2
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.br ^bb9
^bb4: // pred: ^bb2
%1 = cir.eh.initiate cleanup : !cir.eh_token
cir.br ^bb5(%1 : !cir.eh_token)
^bb5(%2: !cir.eh_token): // pred: ^bb4
%3 = cir.begin_cleanup %2 : !cir.eh_token -> !cir.cleanup_token
cir.call @dtor(%0) nothrow : (!cir.ptr<!rec_SomeClass>) -> ()
cir.end_cleanup %3 : !cir.cleanup_token
cir.br ^bb6(%2 : !cir.eh_token)
^bb6(%4: !cir.eh_token): // pred: ^bb5
cir.eh.dispatch %4 : !cir.eh_token [
catch(#cir.global_view<@_ZTISt9exception> : !cir.ptr<!u8i>) : ^bb7,
catch_all : ^bb8
]
^bb7(%6: !cir.eh_token): // pred: ^bb6
%catch_token2, %exn_ptr2 = cir.begin_catch %6 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!cir.ptr<!rec_exception>>)
cir.end_catch %catch_token2 : !cir.catch_token
cir.br ^bb9
^bb8(%5: !cir.eh_token): // pred: ^bb6
%catch_token, %exn_ptr = cir.begin_catch %5 : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>)
cir.end_catch %catch_token : !cir.catch_token
cir.br ^bb9
^bb9: // preds: ^bb3, ^bb7, ^bb8
cir.return
}
// CHECK-LABEL: cir.func {{.*}} @test_catch_with_cleanup_no_ctor()
// CHECK-SAME: personality(@__gxx_personality_v0)
// CHECK: cir.try_call @mayThrow() ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]]
//
// Normal path: destructor called on success.
// CHECK: ^[[NORMAL]]:
// CHECK: cir.call @dtor
//
// Unwind: initiate cleanup → inflight_exception cleanup with std::exception type.
// CHECK: ^[[UNWIND]]:
// CHECK: %[[EXN:.*]], %[[TID:.*]] = cir.eh.inflight_exception cleanup [@_ZTISt9exception]
// CHECK: cir.br ^[[CLEANUP:bb[0-9]+]](%[[EXN]], %[[TID]] : !cir.ptr<!void>, !u32i)
//
// Cleanup: begin_cleanup/end_cleanup removed, destructor call stays.
// CHECK: ^[[CLEANUP]](%[[CL_EXN:.*]]: !cir.ptr<!void>, %[[CL_TID:.*]]: !u32i):
// CHECK-NOT: cir.begin_cleanup
// CHECK: cir.call @dtor
// CHECK-NOT: cir.end_cleanup
// CHECK: cir.br ^[[DISPATCH:bb[0-9]+]](%[[CL_EXN]], %[[CL_TID]] : !cir.ptr<!void>, !u32i)
//
// Dispatch: branch to comparison chain.
// CHECK: ^[[DISPATCH]](%{{.*}}: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.br ^[[CMP_BLOCK:bb[0-9]+]]
//
// Comparison: typeid comparison for std::exception, with catch-all fallthrough.
// CHECK: ^[[CMP_BLOCK]](%[[D_EXN:.*]]: !cir.ptr<!void>, %[[D_TID:.*]]: !u32i):
// CHECK: %[[TYPE_ID:.*]] = cir.eh.typeid @_ZTISt9exception
// CHECK: %[[CMP:.*]] = cir.cmp(eq, %[[D_TID]], %[[TYPE_ID]])
// CHECK: cir.brcond %[[CMP]] ^[[TYPED:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i), ^[[CATCH_ALL:bb[0-9]+]](%[[D_EXN]], %[[D_TID]] : !cir.ptr<!void>, !u32i)
//
// Typed catch for std::exception (^bb7 in source).
// CHECK: ^[[TYPED]](%[[T_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[T_EXN]])
// CHECK: cir.call @__cxa_end_catch()
//
// Catch-all handler (^bb8 in source).
// CHECK: ^[[CATCH_ALL]](%[[CA_EXN:.*]]: !cir.ptr<!void>, %{{.*}}: !u32i):
// CHECK: cir.call @__cxa_begin_catch(%[[CA_EXN]])
// CHECK: cir.call @__cxa_end_catch()
// Verify that runtime function declarations are added.
// CHECK: cir.func private @__gxx_personality_v0(...)
// CHECK: cir.func private @__cxa_begin_catch(!cir.ptr<!void>)
// CHECK: cir.func private @__cxa_end_catch()
cir.func private @mayThrow()
cir.func private @ctor(!cir.ptr<!rec_SomeClass>)
cir.func private @dtor(!cir.ptr<!rec_SomeClass>) attributes {nothrow}
cir.func private @doSomething(!cir.ptr<!rec_SomeClass>)
cir.global "private" constant external @_ZTISt9exception : !cir.ptr<!u8i>
cir.global "private" constant external @_ZTIi : !cir.ptr<!u8i>
} // end module

View File

@ -55,6 +55,10 @@ int main(int argc, char **argv) {
return mlir::createCIRFlattenCFGPass();
});
::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return mlir::createCIREHABILoweringPass();
});
::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return mlir::createHoistAllocasPass();
});