Files
TheRock/compiler/amd-llvm/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

959 lines
41 KiB
C++

//===- 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.eh.terminate → call to __clang_call_terminate + unreachable
// - cir.resume → cir.resume.flat
// - !cir.eh_token values → (!cir.ptr<!void>, !u32i) value pairs
// - cir.construct_catch_param → __cxa_get_exception_ptr + inlined
// catch-copy thunk body
// - personality function set on functions requiring EH
//
//===----------------------------------------------------------------------===//
#include "PassDetail.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/IRMapping.h"
#include "mlir/IR/PatternMatch.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 "clang/CIR/Dialect/Transforms/CIRTransformUtils.h"
#include "clang/CIR/MissingFeatures.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;
cir::FuncOp getExceptionPtrFunc;
cir::FuncOp clangCallTerminateFunc;
cir::FuncOp cxaThrowFunc;
cir::FuncOp cxaRethrowFunc;
DenseMap<mlir::StringAttr, cir::FuncOp> catchCopyThunks;
constexpr const static ::llvm::StringLiteral kGxxPersonality =
"__gxx_personality_v0";
void ensureRuntimeDecls(mlir::Location loc);
void ensureClangCallTerminate(mlir::Location loc);
void ensureCxaThrowDecl(mlir::Location loc);
void ensureCxaRethrowDecl(mlir::Location loc);
mlir::Block *buildTerminateBlock(cir::FuncOp funcOp, mlir::Location loc);
mlir::FailureOr<cir::FuncOp>
resolveCatchCopyThunk(cir::ConstructCatchParamOp op);
mlir::LogicalResult lowerFunc(cir::FuncOp funcOp);
mlir::LogicalResult
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);
mlir::LogicalResult lowerConstructCatchParam(cir::ConstructCatchParamOp op,
mlir::Value exnPtr);
void lowerInitCatchParam(cir::InitCatchParamOp op);
mlir::LogicalResult lowerTryThrow(cir::TryThrowOp op);
};
/// 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);
}
if (!getExceptionPtrFunc) {
auto getExceptionPtrFuncTy =
cir::FuncType::get({voidPtrType}, u8PtrType, /*isVarArg=*/false);
getExceptionPtrFunc = getOrCreateRuntimeFuncDecl(
mod, loc, "__cxa_get_exception_ptr", getExceptionPtrFuncTy);
}
}
/// Ensure the __clang_call_terminate function exists in the module. This
/// function is defined with a body that calls __cxa_begin_catch followed by
/// std::terminate, matching the behavior of Clang's LLVM IR codegen.
///
/// void __clang_call_terminate(void *exn) nounwind noreturn {
/// __cxa_begin_catch(exn);
/// std::terminate();
/// unreachable;
/// }
void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
if (clangCallTerminateFunc)
return;
ensureRuntimeDecls(loc);
if (auto existing = mod.lookupSymbol<cir::FuncOp>("__clang_call_terminate")) {
clangCallTerminateFunc = existing;
return;
}
auto funcTy = cir::FuncType::get({voidPtrType}, voidType, /*isVarArg=*/false);
builder.setInsertionPointToEnd(mod.getBody());
auto funcOp =
cir::FuncOp::create(builder, loc, "__clang_call_terminate", funcTy);
funcOp.setLinkage(cir::GlobalLinkageKind::LinkOnceODRLinkage);
funcOp.setGlobalVisibility(cir::VisibilityKind::Hidden);
mlir::Block *entryBlock = funcOp.addEntryBlock();
builder.setInsertionPointToStart(entryBlock);
mlir::Value exnArg = entryBlock->getArgument(0);
auto catchCall = cir::CallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(beginCatchFunc), u8PtrType,
mlir::ValueRange{exnArg});
catchCall.setNothrowAttr(builder.getUnitAttr());
auto terminateFuncDecl = getOrCreateRuntimeFuncDecl(
mod, loc, "_ZSt9terminatev",
cir::FuncType::get({}, voidType, /*isVarArg=*/false));
terminateFuncDecl->setAttr(cir::CIRDialect::getNoReturnAttrName(),
builder.getUnitAttr());
auto terminateCall = cir::CallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(terminateFuncDecl), voidType,
mlir::ValueRange{});
terminateCall.setNothrowAttr(builder.getUnitAttr());
terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
builder.getUnitAttr());
cir::UnreachableOp::create(builder, loc);
funcOp->setAttr(cir::CIRDialect::getNoReturnAttrName(),
builder.getUnitAttr());
clangCallTerminateFunc = funcOp;
}
/// Ensure the __cxa_throw runtime function is declared in the module.
///
/// void __cxa_throw(void *exception, void *type_info, void *dtor);
void ItaniumEHLowering::ensureCxaThrowDecl(mlir::Location loc) {
if (cxaThrowFunc)
return;
auto throwFuncTy = cir::FuncType::get({voidPtrType, voidPtrType, voidPtrType},
voidType, /*isVarArg=*/false);
cxaThrowFunc =
getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_throw", throwFuncTy);
}
/// Ensure the __cxa_rethrow runtime function is declared in the module.
///
/// void __cxa_rethrow();
void ItaniumEHLowering::ensureCxaRethrowDecl(mlir::Location loc) {
if (cxaRethrowFunc)
return;
auto rethrowFuncTy = cir::FuncType::get({}, voidType, /*isVarArg=*/false);
cxaRethrowFunc =
getOrCreateRuntimeFuncDecl(mod, loc, "__cxa_rethrow", rethrowFuncTy);
}
/// Create a terminate landing pad block at the end of the specified function.
mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
mlir::Location loc) {
assert(clangCallTerminateFunc &&
"ensureClangCallTerminate must run before buildTerminateBlock");
mlir::Region &body = funcOp.getRegion();
mlir::Block *terminateBlock = builder.createBlock(&body, body.end());
auto inflight = cir::EhInflightOp::create(
builder, loc, /*cleanup=*/false, /*catch_all=*/true,
/*catch_type_list=*/mlir::ArrayAttr{});
auto terminateCall = cir::CallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc),
voidType, mlir::ValueRange{inflight.getExceptionPtr()});
terminateCall.setNothrowAttr(builder.getUnitAttr());
terminateCall->setAttr(cir::CIRDialect::getNoReturnAttrName(),
builder.getUnitAttr());
cir::UnreachableOp::create(builder, loc);
return terminateBlock;
}
/// 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)
if (mlir::failed(lowerEhInitiate(initiateOp, ehTokenMap, deadOps)))
return mlir::failure();
// 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);
}
}
// Lower any cir.init_catch_param ops in this function. These materialize
// the catch parameter local from the (already lowered) begin_catch result,
// and are independent of the eh_token graph traversal above.
SmallVector<cir::InitCatchParamOp> initCatchOps;
funcOp.walk([&](cir::InitCatchParamOp op) { initCatchOps.push_back(op); });
for (cir::InitCatchParamOp op : initCatchOps)
lowerInitCatchParam(op);
// Lower any cir.try_throw ops in this function to cir.try_call of
// __cxa_throw / __cxa_rethrow. These are produced by FlattenCFG when a
// cir.throw appears inside a cleanup scope or try region.
SmallVector<cir::TryThrowOp> tryThrowOps;
funcOp.walk([&](cir::TryThrowOp op) { tryThrowOps.push_back(op); });
for (cir::TryThrowOp op : tryThrowOps)
if (mlir::failed(lowerTryThrow(op)))
return mlir::failure();
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.
mlir::LogicalResult 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_all=*/false,
/*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::ConstructCatchParamOp>(user)) {
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
if (mlir::failed(lowerConstructCatchParam(op, exnPtr)))
return mlir::failure();
} 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));
}
if (op.getDefaultIsCatchAll())
inflightOp.setCatchAllAttr(builder.getUnitAttr());
// 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::EhTerminateOp>(user)) {
auto [exnPtr, typeId] = ehTokenMap.lookup(op.getEhToken());
ensureClangCallTerminate(op.getLoc());
builder.setInsertionPoint(op);
auto call = cir::CallOp::create(
builder, op.getLoc(),
mlir::FlatSymbolRefAttr::get(clangCallTerminateFunc), voidType,
mlir::ValueRange{exnPtr});
call.setNothrowAttr(builder.getUnitAttr());
call->setAttr(cir::CIRDialect::getNoReturnAttrName(),
builder.getUnitAttr());
cir::UnreachableOp::create(builder, op.getLoc());
op.erase();
} 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();
return mlir::success();
}
/// 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);
}
mlir::FailureOr<cir::FuncOp>
ItaniumEHLowering::resolveCatchCopyThunk(cir::ConstructCatchParamOp op) {
mlir::FlatSymbolRefAttr thunkRef = op.getCopyFnAttr();
mlir::StringAttr thunkName = thunkRef.getAttr();
auto cached = catchCopyThunks.find(thunkName);
if (cached != catchCopyThunks.end())
return cached->second;
cir::FuncOp thunk = mod.lookupSymbol<cir::FuncOp>(thunkRef);
if (!thunk)
return op.emitError("could not resolve catch-copy thunk symbol");
assert(thunk->hasAttr(cir::CIRDialect::getCatchCopyThunkAttrName()) &&
"verifier should have rejected non-thunk catch-copy reference");
if (thunk.isDeclaration())
return op.emitError("catch-copy thunk has no body to inline");
mlir::Region &thunkRegion = thunk.getRegion();
if (!llvm::hasSingleElement(thunkRegion))
return op.emitError("multi-block catch-copy thunks are NYI");
mlir::Block &thunkEntry = thunkRegion.front();
assert(thunkEntry.getNumArguments() == 2 &&
"catch-copy thunk must have exactly two parameters");
if (!mlir::isa<cir::ReturnOp>(thunkEntry.getTerminator()))
return op.emitError("catch-copy thunk must end in cir.return");
catchCopyThunks[thunkName] = thunk;
return thunk;
}
/// Lower a cir.construct_catch_param into the Itanium-specific sequence
/// that runs before `__cxa_begin_catch` to bind the catch parameter to the
/// in-flight exception.
mlir::LogicalResult
ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
mlir::Value exnPtr) {
mlir::Location loc = op.getLoc();
mlir::Value paramAddr = op.getParamAddr();
cir::PointerType paramAddrType =
mlir::cast<cir::PointerType>(paramAddr.getType());
if (op.getKind() == cir::InitCatchKind::Reference) {
assert(!MissingFeatures::sizeOfUnwindException());
constexpr unsigned headerSize = 32;
builder.setInsertionPoint(op);
auto index = cir::ConstantOp::create(
builder, loc, cir::IntAttr::get(u32Type, headerSize));
assert((exnPtr.getType() == voidPtrType || exnPtr.getType() == u8PtrType) &&
"lowerConstructCatchParam exn ptr not void* or i8*");
auto exnObj =
cir::PtrStrideOp::create(builder, loc, exnPtr.getType(), exnPtr, index);
mlir::Value casted =
cir::CastOp::create(builder, loc, paramAddrType.getPointee(),
cir::CastKind::bitcast, exnObj);
cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {});
op.erase();
return success();
}
if (op.getKind() != cir::InitCatchKind::NonTrivialCopy)
return op.emitError(
"ConstructCatchParam: only non_trivial_copy is supported");
ensureRuntimeDecls(loc);
ensureClangCallTerminate(loc);
// Call __cxa_get_exception_ptr to get the in-flight exception.
builder.setInsertionPoint(op);
cir::CallOp getExnCall = cir::CallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(getExceptionPtrFunc),
u8PtrType, mlir::ValueRange{exnPtr});
getExnCall.setNothrowAttr(builder.getUnitAttr());
mlir::Value adjusted =
cir::CastOp::create(builder, loc, paramAddrType, cir::CastKind::bitcast,
getExnCall.getResult());
// Get the thunk function definition.
mlir::FailureOr<cir::FuncOp> thunkOr = resolveCatchCopyThunk(op);
if (mlir::failed(thunkOr))
return mlir::failure();
cir::FuncOp thunk = *thunkOr;
// This is also verified by resolveCatchCopyThunk, but the loop below is
// where the constraint is required so let's assert it again here.
assert(llvm::hasSingleElement(thunk.getRegion()) &&
"multi-block catch-copy thunks are NYI");
// Clone the thunk function to perform the copy.
mlir::Block &thunkEntry = thunk.getRegion().front();
mlir::IRMapping mapping;
mapping.map(thunkEntry.getArgument(0), paramAddr);
mapping.map(thunkEntry.getArgument(1), adjusted);
llvm::SmallVector<cir::CallOp> throwingCalls;
for (mlir::Operation &thunkOp : thunkEntry.without_terminator()) {
mlir::Operation *cloned = builder.clone(thunkOp, mapping);
if (cir::CallOp callOp = mlir::dyn_cast<cir::CallOp>(cloned))
if (!callOp.getNothrow())
throwingCalls.push_back(callOp);
}
op.erase();
if (throwingCalls.empty())
return mlir::success();
// All calls in the copy (which is usually just a single call) need to
// unwind to a terminate block if it throws an exception.
mlir::IRRewriter rewriter(builder);
mlir::Block *terminateBlock = nullptr;
for (cir::CallOp call : throwingCalls) {
if (!terminateBlock)
terminateBlock = buildTerminateBlock(call->getParentOfType<cir::FuncOp>(),
call.getLoc());
cir::replaceCallWithTryCall(call, terminateBlock, call.getLoc(), rewriter);
}
return mlir::success();
}
/// Lower a cir.try_throw to a cir.try_call of __cxa_throw (or
/// __cxa_rethrow for the no-operand rethrow form). Materializes the
/// type_info and dtor pointers from their symbol attributes, bitcasting
/// each to !cir.ptr<!void> as required by the runtime function signature.
mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
mlir::Location loc = op.getLoc();
mlir::Block *normalDest = op.getNormalDest();
mlir::Block *unwindDest = op.getUnwindDest();
builder.setInsertionPoint(op);
if (op.rethrows()) {
ensureCxaRethrowDecl(loc);
cir::TryCallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(cxaRethrowFunc), voidType,
normalDest, unwindDest, mlir::ValueRange{});
op.erase();
return mlir::success();
}
ensureCxaThrowDecl(loc);
// Bitcast the exception pointer to void* if necessary.
mlir::Value exnPtr = op.getExceptionPtr();
if (exnPtr.getType() != voidPtrType)
exnPtr = cir::CastOp::create(builder, loc, voidPtrType,
cir::CastKind::bitcast, exnPtr);
// Materialize the type_info pointer, looking up the typed symbol in the
// module so we get the correct pointer type for cir.get_global, then
// bitcasting to void* to match the runtime signature.
mlir::FlatSymbolRefAttr typeInfoAttr = op.getTypeInfoAttr();
auto typeInfoGlobal = mod.lookupSymbol<cir::GlobalOp>(typeInfoAttr);
if (!typeInfoGlobal)
return op.emitError("type_info symbol not found in module");
auto typeInfoPtrTy = cir::PointerType::get(typeInfoGlobal.getSymType());
mlir::Value typeInfo = cir::GetGlobalOp::create(builder, loc, typeInfoPtrTy,
typeInfoAttr.getValue());
if (typeInfo.getType() != voidPtrType)
typeInfo = cir::CastOp::create(builder, loc, voidPtrType,
cir::CastKind::bitcast, typeInfo);
// Materialize the dtor pointer (or null if no dtor).
mlir::Value dtor;
if (mlir::FlatSymbolRefAttr dtorAttr = op.getDtorAttr()) {
auto dtorFunc = mod.lookupSymbol<cir::FuncOp>(dtorAttr);
if (!dtorFunc)
return op.emitError("dtor symbol not found in module");
auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
dtor =
cir::GetGlobalOp::create(builder, loc, dtorPtrTy, dtorAttr.getValue());
if (dtor.getType() != voidPtrType)
dtor = cir::CastOp::create(builder, loc, voidPtrType,
cir::CastKind::bitcast, dtor);
} else {
dtor = cir::ConstantOp::create(
builder, loc,
cir::ConstPtrAttr::get(voidPtrType, builder.getI64IntegerAttr(0)));
}
cir::TryCallOp::create(
builder, loc, mlir::FlatSymbolRefAttr::get(cxaThrowFunc), voidType,
normalDest, unwindDest, mlir::ValueRange{exnPtr, typeInfo, dtor});
op.erase();
return mlir::success();
}
/// Lower a cir.init_catch_param into the Itanium-specific sequence that
/// materializes the catch parameter's local variable from the exception
/// pointer returned by __cxa_begin_catch. The shape of the lowering
/// depends on the init catch kind:
///
/// - Reference: the begin_catch result is
/// the pointer value itself, so just bitcast and store it into the alloca
/// except if it reference of pointer of record.
/// - Pointer: the begin_catch result is
/// the pointer value itself, so just bitcast and store it into the
/// alloca.
/// - Scalar (any other by-value catch): treat the begin_catch result as a
/// pointer to the value, load it, and store it into the alloca.
/// - Objc: Handle pointer representation with ObjCLifetime.
/// - TrivialCopy: copy the exception
/// object's bytes into the alloca via cir.copy.
/// - NonTrivialCopy: the construction was already performed by the
/// companion `cir.construct_catch_param` before `cir.begin_catch`, so
/// this lowering is a no-op.
///
void ItaniumEHLowering::lowerInitCatchParam(cir::InitCatchParamOp op) {
builder.setInsertionPoint(op);
mlir::Location loc = op.getLoc();
mlir::Value exnPtr = op.getExnPtr();
mlir::Value paramAddr = op.getParamAddr();
auto paramAddrType = mlir::cast<cir::PointerType>(paramAddr.getType());
mlir::Type elementType = paramAddrType.getPointee();
cir::InitCatchKind kind = op.getKind();
switch (kind) {
case InitCatchKind::Reference: {
// We have no way to tell the personality function that we're
// catching by reference, so if we're catching a pointer,
// __cxa_begin_catch will actually return that pointer by value.
if (const auto ref = mlir::dyn_cast<cir::PointerType>(elementType)) {
// When catching by reference, generally we should just ignore
// this by-value pointer and use the exception object instead.
if (auto ptr = mlir::dyn_cast<cir::PointerType>(ref.getPointee()))
if (!mlir::isa<cir::RecordType>(ptr.getPointee()))
// Extracting and storing the actual exception object was performed by
// cir.construct_catch_param before cir.begin_catch.
break;
}
mlir::Value casted = cir::CastOp::create(builder, loc, elementType,
cir::CastKind::bitcast, exnPtr);
cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {});
break;
}
case InitCatchKind::TrivialCopy: {
mlir::Value srcPtr = cir::CastOp::create(builder, loc, paramAddrType,
cir::CastKind::bitcast, exnPtr);
cir::CopyOp::create(builder, loc, paramAddr, srcPtr, {}, {});
break;
}
case InitCatchKind::NonTrivialCopy:
// The non-trivial copy was performed by the matching
// cir.construct_catch_param before cir.begin_catch.
break;
case InitCatchKind::Scalar: {
// Scalar by-value catch (integer, float, complex, etc.). The begin_catch
// result points into the exception object; load the value through a
// typed pointer and store it into the alloca.
mlir::Value srcPtr = cir::CastOp::create(builder, loc, paramAddrType,
cir::CastKind::bitcast, exnPtr);
auto loadOp = cir::LoadOp::create(builder, loc, elementType, srcPtr);
cir::StoreOp::create(builder, loc, loadOp.getResult(), paramAddr, {}, {},
{}, {});
break;
}
case InitCatchKind::Pointer: {
mlir::Value casted = cir::CastOp::create(builder, loc, elementType,
cir::CastKind::bitcast, exnPtr);
cir::StoreOp::create(builder, loc, casted, paramAddr, {}, {}, {}, {});
break;
}
case InitCatchKind::Objc:
llvm_unreachable("InitCatchParam: ObjCLifetime is NYI");
break;
}
op.erase();
}
//===----------------------------------------------------------------------===//
// The Pass
//===----------------------------------------------------------------------===//
struct CIREHABILoweringPass
: public impl::CIREHABILoweringBase<CIREHABILoweringPass> {
CIREHABILoweringPass() = default;
void runOnOperation() override;
};
/// Erase all catch-init thunks after the EHABI lowering. CIRGen emits a thunk
/// for every `cir.construct_catch_param` op, but those uses should all have
/// been replaced during the lowering.
static void eraseCatchCopyThunks(mlir::ModuleOp mod) {
llvm::StringRef catchHelperAttr =
cir::CIRDialect::getCatchCopyThunkAttrName();
for (cir::FuncOp f : llvm::make_early_inc_range(mod.getOps<cir::FuncOp>())) {
if (!f->hasAttr(catchHelperAttr))
continue;
// This is an expensive check, so we need to rely on the implementation
// to have done the right thing.
assert(mlir::SymbolTable::symbolKnownUseEmpty(f, mod) &&
"catch-init helper has remaining users");
f.erase();
}
}
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();
// Sweep away any the thunk functions. They've been inlined to all users now.
eraseCatchCopyThunks(mod);
}
} // namespace
std::unique_ptr<Pass> mlir::createCIREHABILoweringPass() {
return std::make_unique<CIREHABILoweringPass>();
}