Author: Andy Kaylor Date: 2026-03-03T20:09:49Z New Revision: 375d65ee8de7dd65e19faab567db2799a52dfe52
URL: https://github.com/llvm/llvm-project/commit/375d65ee8de7dd65e19faab567db2799a52dfe52 DIFF: https://github.com/llvm/llvm-project/commit/375d65ee8de7dd65e19faab567db2799a52dfe52.diff LOG: [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. Added: clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir Modified: clang/include/clang/CIR/Dialect/IR/CIROps.td clang/include/clang/CIR/Dialect/Passes.h clang/include/clang/CIR/Dialect/Passes.td clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/Dialect/Transforms/CMakeLists.txt clang/lib/CIR/Lowering/CIRPasses.cpp clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp clang/test/CIR/CodeGen/try-catch-tmp.cpp clang/test/CIR/CodeGen/try-catch.cpp clang/tools/cir-opt/cir-opt.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index a5a5197cd3ea6..064be6f54def2 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -3486,7 +3486,6 @@ def CIR_TryCallOp : CIR_CallOpBase<"try_call",[ ); let skipDefaultBuilders = 1; - let hasLLVMLowering = false; let builders = [ OpBuilder<(ins "mlir::SymbolRefAttr":$callee, diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h index 7b57b00f7da8c..d441dfcbc6c14 100644 --- a/clang/include/clang/CIR/Dialect/Passes.h +++ b/clang/include/clang/CIR/Dialect/Passes.h @@ -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(); diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td index 0d60d5e33dd4b..32cd182aacec7 100644 --- a/clang/include/clang/CIR/Dialect/Passes.td +++ b/clang/include/clang/CIR/Dialect/Passes.td @@ -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"; diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 02d22bb628900..ab05d2191d9b0 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -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; } diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt index 3fe6c5f9ecb55..411bbc386b9d1 100644 --- a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt +++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_library(MLIRCIRTransforms CIRCanonicalize.cpp CIRSimplify.cpp CXXABILowering.cpp + EHABILowering.cpp TargetLowering.cpp FlattenCFG.cpp HoistAllocas.cpp diff --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp new file mode 100644 index 0000000000000..d5d10b1b251be --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp @@ -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>(); +} diff --git a/clang/lib/CIR/Lowering/CIRPasses.cpp b/clang/lib/CIR/Lowering/CIRPasses.cpp index c0dcc4ba5e90a..7b93356a34c38 100644 --- a/clang/lib/CIR/Lowering/CIRPasses.cpp +++ b/clang/lib/CIR/Lowering/CIRPasses.cpp @@ -49,6 +49,7 @@ namespace mlir { void populateCIRPreLoweringPasses(OpPassManager &pm) { pm.addPass(createHoistAllocasPass()); pm.addPass(createCIRFlattenCFGPass()); + pm.addPass(createCIREHABILoweringPass()); pm.addPass(createGotoSolverPass()); } diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 03085ad29ab78..4f71c588b5018 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -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 { diff --git a/clang/test/CIR/CodeGen/try-catch-tmp.cpp b/clang/test/CIR/CodeGen/try-catch-tmp.cpp index 5cb60aaf6b2cc..f7951e229c06f 100644 --- a/clang/test/CIR/CodeGen/try-catch-tmp.cpp +++ b/clang/test/CIR/CodeGen/try-catch-tmp.cpp @@ -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 diff --git a/clang/test/CIR/CodeGen/try-catch.cpp b/clang/test/CIR/CodeGen/try-catch.cpp index 81ac15eeb7016..27e3d8ef41115 100644 --- a/clang/test/CIR/CodeGen/try-catch.cpp +++ b/clang/test/CIR/CodeGen/try-catch.cpp @@ -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 (...) {} } diff --git a/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir b/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir new file mode 100644 index 0000000000000..42e2722a43455 --- /dev/null +++ b/clang/test/CIR/Transforms/eh-abi-lowering-itanium.cir @@ -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 diff --git a/clang/tools/cir-opt/cir-opt.cpp b/clang/tools/cir-opt/cir-opt.cpp index edadfeec09a2a..83832a100debb 100644 --- a/clang/tools/cir-opt/cir-opt.cpp +++ b/clang/tools/cir-opt/cir-opt.cpp @@ -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(); }); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
