blob: 8d33af65eb848408b8d3de22c44bfd0a9ec09621 [file] [log] [blame] [edit]
//===--- CIRGenException.cpp - Emit CIR Code for C++ exceptions -*- C++ -*-===//
//
// 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 contains code dealing with C++ exception related code generation.
//
//===----------------------------------------------------------------------===//
#include "CIRGenCXXABI.h"
#include "CIRGenCleanup.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIRDataLayout.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/MissingFeatures.h"
#include "llvm/Support/ErrorHandling.h"
#include <cstdint>
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Value.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace cir;
using namespace clang;
const EHPersonality EHPersonality::GNU_C = {"__gcc_personality_v0", nullptr};
const EHPersonality EHPersonality::GNU_C_SJLJ = {"__gcc_personality_sj0",
nullptr};
const EHPersonality EHPersonality::GNU_C_SEH = {"__gcc_personality_seh0",
nullptr};
const EHPersonality EHPersonality::NeXT_ObjC = {"__objc_personality_v0",
nullptr};
const EHPersonality EHPersonality::GNU_CPlusPlus = {"__gxx_personality_v0",
nullptr};
const EHPersonality EHPersonality::GNU_CPlusPlus_SJLJ = {
"__gxx_personality_sj0", nullptr};
const EHPersonality EHPersonality::GNU_CPlusPlus_SEH = {
"__gxx_personality_seh0", nullptr};
const EHPersonality EHPersonality::GNU_ObjC = {"__gnu_objc_personality_v0",
"objc_exception_throw"};
const EHPersonality EHPersonality::GNU_ObjC_SJLJ = {
"__gnu_objc_personality_sj0", "objc_exception_throw"};
const EHPersonality EHPersonality::GNU_ObjC_SEH = {
"__gnu_objc_personality_seh0", "objc_exception_throw"};
const EHPersonality EHPersonality::GNU_ObjCXX = {
"__gnustep_objcxx_personality_v0", nullptr};
const EHPersonality EHPersonality::GNUstep_ObjC = {
"__gnustep_objc_personality_v0", nullptr};
const EHPersonality EHPersonality::MSVC_except_handler = {"_except_handler3",
nullptr};
const EHPersonality EHPersonality::MSVC_C_specific_handler = {
"__C_specific_handler", nullptr};
const EHPersonality EHPersonality::MSVC_CxxFrameHandler3 = {
"__CxxFrameHandler3", nullptr};
const EHPersonality EHPersonality::GNU_Wasm_CPlusPlus = {
"__gxx_wasm_personality_v0", nullptr};
const EHPersonality EHPersonality::XL_CPlusPlus = {"__xlcxx_personality_v1",
nullptr};
static const EHPersonality &getCPersonality(const TargetInfo &Target,
const LangOptions &L) {
const llvm::Triple &T = Target.getTriple();
if (T.isWindowsMSVCEnvironment())
return EHPersonality::MSVC_CxxFrameHandler3;
if (L.hasSjLjExceptions())
return EHPersonality::GNU_C_SJLJ;
if (L.hasDWARFExceptions())
return EHPersonality::GNU_C;
if (L.hasSEHExceptions())
return EHPersonality::GNU_C_SEH;
return EHPersonality::GNU_C;
}
static const EHPersonality &getObjCPersonality(const TargetInfo &Target,
const LangOptions &L) {
const llvm::Triple &T = Target.getTriple();
if (T.isWindowsMSVCEnvironment())
return EHPersonality::MSVC_CxxFrameHandler3;
switch (L.ObjCRuntime.getKind()) {
case ObjCRuntime::FragileMacOSX:
return getCPersonality(Target, L);
case ObjCRuntime::MacOSX:
case ObjCRuntime::iOS:
case ObjCRuntime::WatchOS:
return EHPersonality::NeXT_ObjC;
case ObjCRuntime::GNUstep:
if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7))
return EHPersonality::GNUstep_ObjC;
[[fallthrough]];
case ObjCRuntime::GCC:
case ObjCRuntime::ObjFW:
if (L.hasSjLjExceptions())
return EHPersonality::GNU_ObjC_SJLJ;
if (L.hasSEHExceptions())
return EHPersonality::GNU_ObjC_SEH;
return EHPersonality::GNU_ObjC;
}
llvm_unreachable("bad runtime kind");
}
static const EHPersonality &getCXXPersonality(const TargetInfo &Target,
const LangOptions &L) {
const llvm::Triple &T = Target.getTriple();
if (T.isWindowsMSVCEnvironment())
return EHPersonality::MSVC_CxxFrameHandler3;
if (T.isOSAIX())
return EHPersonality::XL_CPlusPlus;
if (L.hasSjLjExceptions())
return EHPersonality::GNU_CPlusPlus_SJLJ;
if (L.hasDWARFExceptions())
return EHPersonality::GNU_CPlusPlus;
if (L.hasSEHExceptions())
return EHPersonality::GNU_CPlusPlus_SEH;
if (L.hasWasmExceptions())
return EHPersonality::GNU_Wasm_CPlusPlus;
return EHPersonality::GNU_CPlusPlus;
}
/// Determines the personality function to use when both C++
/// and Objective-C exceptions are being caught.
static const EHPersonality &getObjCXXPersonality(const TargetInfo &Target,
const LangOptions &L) {
if (Target.getTriple().isWindowsMSVCEnvironment())
return EHPersonality::MSVC_CxxFrameHandler3;
switch (L.ObjCRuntime.getKind()) {
// In the fragile ABI, just use C++ exception handling and hope
// they're not doing crazy exception mixing.
case ObjCRuntime::FragileMacOSX:
return getCXXPersonality(Target, L);
// The ObjC personality defers to the C++ personality for non-ObjC
// handlers. Unlike the C++ case, we use the same personality
// function on targets using (backend-driven) SJLJ EH.
case ObjCRuntime::MacOSX:
case ObjCRuntime::iOS:
case ObjCRuntime::WatchOS:
return getObjCPersonality(Target, L);
case ObjCRuntime::GNUstep:
return EHPersonality::GNU_ObjCXX;
// The GCC runtime's personality function inherently doesn't support
// mixed EH. Use the ObjC personality just to avoid returning null.
case ObjCRuntime::GCC:
case ObjCRuntime::ObjFW:
return getObjCPersonality(Target, L);
}
llvm_unreachable("bad runtime kind");
}
static const EHPersonality &getSEHPersonalityMSVC(const llvm::Triple &T) {
if (T.getArch() == llvm::Triple::x86)
return EHPersonality::MSVC_except_handler;
return EHPersonality::MSVC_C_specific_handler;
}
const EHPersonality &EHPersonality::get(CIRGenModule &CGM,
const FunctionDecl *FD) {
const llvm::Triple &T = CGM.getTarget().getTriple();
const LangOptions &L = CGM.getLangOpts();
const TargetInfo &Target = CGM.getTarget();
// Functions using SEH get an SEH personality.
if (FD && FD->usesSEHTry())
return getSEHPersonalityMSVC(T);
if (L.ObjC)
return L.CPlusPlus ? getObjCXXPersonality(Target, L)
: getObjCPersonality(Target, L);
return L.CPlusPlus ? getCXXPersonality(Target, L)
: getCPersonality(Target, L);
}
const EHPersonality &EHPersonality::get(CIRGenFunction &CGF) {
const auto *FD = CGF.CurCodeDecl;
// For outlined finallys and filters, use the SEH personality in case they
// contain more SEH. This mostly only affects finallys. Filters could
// hypothetically use gnu statement expressions to sneak in nested SEH.
FD = FD ? FD : CGF.CurSEHParent.getDecl();
return get(CGF.CGM, dyn_cast_or_null<FunctionDecl>(FD));
}
void CIRGenFunction::buildCXXThrowExpr(const CXXThrowExpr *E) {
if (const Expr *SubExpr = E->getSubExpr()) {
QualType ThrowType = SubExpr->getType();
if (ThrowType->isObjCObjectPointerType()) {
llvm_unreachable("NYI");
} else {
CGM.getCXXABI().buildThrow(*this, E);
}
} else {
CGM.getCXXABI().buildRethrow(*this, /*isNoReturn=*/true);
}
// In LLVM codegen the expression emitters expect to leave this
// path by starting a new basic block. We do not need that in CIR.
}
namespace {
/// A cleanup to free the exception object if its initialization
/// throws.
struct FreeException final : EHScopeStack::Cleanup {
mlir::Value exn;
FreeException(mlir::Value exn) : exn(exn) {}
void Emit(CIRGenFunction &CGF, Flags flags) override {
llvm_unreachable("call to cxa_free or equivalent op NYI");
}
};
} // end anonymous namespace
// Emits an exception expression into the given location. This
// differs from buildAnyExprToMem only in that, if a final copy-ctor
// call is required, an exception within that copy ctor causes
// std::terminate to be invoked.
void CIRGenFunction::buildAnyExprToExn(const Expr *e, Address addr) {
// Make sure the exception object is cleaned up if there's an
// exception during initialization.
pushFullExprCleanup<FreeException>(EHCleanup, addr.getPointer());
EHScopeStack::stable_iterator cleanup = EHStack.stable_begin();
// __cxa_allocate_exception returns a void*; we need to cast this
// to the appropriate type for the object.
auto ty = convertTypeForMem(e->getType());
Address typedAddr = addr.withElementType(ty);
// From LLVM's codegen:
// FIXME: this isn't quite right! If there's a final unelided call
// to a copy constructor, then according to [except.terminate]p1 we
// must call std::terminate() if that constructor throws, because
// technically that copy occurs after the exception expression is
// evaluated but before the exception is caught. But the best way
// to handle that is to teach EmitAggExpr to do the final copy
// differently if it can't be elided.
buildAnyExprToMem(e, typedAddr, e->getType().getQualifiers(),
/*IsInit*/ true);
// Deactivate the cleanup block.
auto op = typedAddr.getPointer().getDefiningOp();
assert(op &&
"expected valid Operation *, block arguments are not meaningful here");
DeactivateCleanupBlock(cleanup, op);
}
static mlir::Block *getResumeBlockFromCatch(mlir::cir::TryOp &tryOp,
mlir::cir::GlobalOp globalParent) {
assert(tryOp && "cir.try expected");
unsigned numCatchRegions = tryOp.getCatchRegions().size();
assert(numCatchRegions && "expected at least one region");
auto &fallbackRegion = tryOp.getCatchRegions()[numCatchRegions - 1];
return &fallbackRegion.getBlocks().back();
return nullptr;
}
mlir::Block *CIRGenFunction::getEHResumeBlock(bool isCleanup,
mlir::cir::TryOp tryOp) {
if (ehResumeBlock)
return ehResumeBlock;
// Just like some other try/catch related logic: return the basic block
// pointer but only use it to denote we're tracking things, but there
// shouldn't be any changes to that block after work done in this function.
ehResumeBlock = getResumeBlockFromCatch(tryOp, CGM.globalOpContext);
if (!ehResumeBlock->empty())
return ehResumeBlock;
auto ip = getBuilder().saveInsertionPoint();
getBuilder().setInsertionPointToStart(ehResumeBlock);
const EHPersonality &Personality = EHPersonality::get(*this);
// This can always be a call
// because we necessarily didn't
// find anything on the EH stack
// which needs our help.
const char *RethrowName = Personality.CatchallRethrowFn;
if (RethrowName != nullptr && !isCleanup) {
// FIXME(cir): upon testcase
// this should just add the
// 'rethrow' attribute to
// mlir::cir::ResumeOp below.
llvm_unreachable("NYI");
}
getBuilder().create<mlir::cir::ResumeOp>(tryOp.getLoc(), mlir::Value{},
mlir::Value{});
getBuilder().restoreInsertionPoint(ip);
return ehResumeBlock;
}
mlir::LogicalResult CIRGenFunction::buildCXXTryStmt(const CXXTryStmt &S) {
auto loc = getLoc(S.getSourceRange());
mlir::OpBuilder::InsertPoint scopeIP;
// Create a scope to hold try local storage for catch params.
[[maybe_unused]] auto s = builder.create<mlir::cir::ScopeOp>(
loc, /*scopeBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc) {
scopeIP = getBuilder().saveInsertionPoint();
});
auto r = mlir::success();
{
mlir::OpBuilder::InsertionGuard guard(getBuilder());
getBuilder().restoreInsertionPoint(scopeIP);
r = buildCXXTryStmtUnderScope(S);
getBuilder().create<mlir::cir::YieldOp>(loc);
}
return r;
}
mlir::LogicalResult
CIRGenFunction::buildCXXTryStmtUnderScope(const CXXTryStmt &S) {
const llvm::Triple &T = getTarget().getTriple();
// If we encounter a try statement on in an OpenMP target region offloaded to
// a GPU, we treat it as a basic block.
const bool IsTargetDevice =
(CGM.getLangOpts().OpenMPIsTargetDevice && (T.isNVPTX() || T.isAMDGCN()));
assert(!IsTargetDevice && "NYI");
auto hasCatchAll = [&]() {
if (!S.getNumHandlers())
return false;
unsigned lastHandler = S.getNumHandlers() - 1;
if (!S.getHandler(lastHandler)->getExceptionDecl())
return true;
return false;
};
auto numHandlers = S.getNumHandlers();
auto tryLoc = getLoc(S.getBeginLoc());
mlir::OpBuilder::InsertPoint beginInsertTryBody;
// Create the scope to represent only the C/C++ `try {}` part. However,
// don't populate right away. Reserve some space to store the exception
// info but don't emit the bulk right away, for now only make sure the
// scope returns the exception information.
auto tryOp = builder.create<mlir::cir::TryOp>(
tryLoc, /*scopeBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc) {
beginInsertTryBody = getBuilder().saveInsertionPoint();
},
// Don't emit the code right away for catch clauses, for
// now create the regions and consume the try scope result.
// Note that clauses are later populated in
// CIRGenFunction::buildLandingPad.
[&](mlir::OpBuilder &b, mlir::Location loc,
mlir::OperationState &result) {
mlir::OpBuilder::InsertionGuard guard(b);
auto numRegionsToCreate = numHandlers;
if (!hasCatchAll())
numRegionsToCreate++;
// Once for each handler + (catch_all or unwind).
for (int i = 0, e = numRegionsToCreate; i != e; ++i) {
auto *r = result.addRegion();
builder.createBlock(r);
}
});
// Finally emit the body for try/catch.
auto emitTryCatchBody = [&]() -> mlir::LogicalResult {
auto loc = tryOp.getLoc();
mlir::OpBuilder::InsertionGuard guard(getBuilder());
getBuilder().restoreInsertionPoint(beginInsertTryBody);
CIRGenFunction::LexicalScope tryScope{*this, loc,
getBuilder().getInsertionBlock()};
{
tryScope.setAsTry(tryOp);
// Attach the basic blocks for the catch regions.
enterCXXTryStmt(S, tryOp);
// Emit the body for the `try {}` part.
{
CIRGenFunction::LexicalScope tryBodyScope{
*this, loc, getBuilder().getInsertionBlock()};
if (buildStmt(S.getTryBlock(), /*useCurrentScope=*/true).failed())
return mlir::failure();
}
}
{
// Emit catch clauses.
exitCXXTryStmt(S);
}
return mlir::success();
};
return emitTryCatchBody();
}
/// Emit the structure of the dispatch block for the given catch scope.
/// It is an invariant that the dispatch block already exists.
static void buildCatchDispatchBlock(CIRGenFunction &CGF,
EHCatchScope &catchScope,
mlir::cir::TryOp tryOp) {
if (EHPersonality::get(CGF).isWasmPersonality())
llvm_unreachable("NYI");
if (EHPersonality::get(CGF).usesFuncletPads())
llvm_unreachable("NYI");
auto *dispatchBlock = catchScope.getCachedEHDispatchBlock();
assert(dispatchBlock);
// If there's only a single catch-all, getEHDispatchBlock returned
// that catch-all as the dispatch block.
if (catchScope.getNumHandlers() == 1 &&
catchScope.getHandler(0).isCatchAll()) {
assert(dispatchBlock == catchScope.getHandler(0).Block);
return;
}
// In traditional LLVM codegen, the right handler is selected (with
// calls to eh_typeid_for) and the selector value is loaded. After that,
// blocks get connected for later codegen. In CIR, these are all
// implicit behaviors of cir.catch - not a lot of work to do.
//
// Test against each of the exception types we claim to catch.
for (unsigned i = 0, e = catchScope.getNumHandlers();; ++i) {
assert(i < e && "ran off end of handlers!");
const EHCatchScope::Handler &handler = catchScope.getHandler(i);
auto typeValue = handler.Type.RTTI;
assert(handler.Type.Flags == 0 && "catch handler flags not supported");
assert(typeValue && "fell into catch-all case!");
// Check for address space mismatch: if (typeValue->getType() !=
// argTy)
assert(!MissingFeatures::addressSpace());
bool nextIsEnd = false;
// If this is the last handler, we're at the end, and the next
// block is the block for the enclosing EH scope. Make sure to call
// getEHDispatchBlock for caching it.
if (i + 1 == e) {
(void)CGF.getEHDispatchBlock(catchScope.getEnclosingEHScope(), tryOp);
nextIsEnd = true;
// If the next handler is a catch-all, we're at the end, and the
// next block is that handler.
} else if (catchScope.getHandler(i + 1).isCatchAll()) {
// Block already created when creating catch regions, just mark this
// is the end.
nextIsEnd = true;
}
// If the next handler is a catch-all, we're completely done.
if (nextIsEnd)
return;
}
}
void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &S,
mlir::cir::TryOp tryOp,
bool IsFnTryBlock) {
unsigned NumHandlers = S.getNumHandlers();
EHCatchScope *CatchScope = EHStack.pushCatch(NumHandlers);
for (unsigned I = 0; I != NumHandlers; ++I) {
const CXXCatchStmt *C = S.getHandler(I);
mlir::Block *Handler = &tryOp.getCatchRegions()[I].getBlocks().front();
if (C->getExceptionDecl()) {
// FIXME: Dropping the reference type on the type into makes it
// impossible to correctly implement catch-by-reference
// semantics for pointers. Unfortunately, this is what all
// existing compilers do, and it's not clear that the standard
// personality routine is capable of doing this right. See C++ DR 388 :
// http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#388
Qualifiers CaughtTypeQuals;
QualType CaughtType = CGM.getASTContext().getUnqualifiedArrayType(
C->getCaughtType().getNonReferenceType(), CaughtTypeQuals);
CatchTypeInfo TypeInfo{nullptr, 0};
if (CaughtType->isObjCObjectPointerType())
llvm_unreachable("NYI");
else
TypeInfo = CGM.getCXXABI().getAddrOfCXXCatchHandlerType(
getLoc(S.getSourceRange()), CaughtType, C->getCaughtType());
CatchScope->setHandler(I, TypeInfo, Handler);
} else {
// No exception decl indicates '...', a catch-all.
CatchScope->setHandler(I, CGM.getCXXABI().getCatchAllTypeInfo(), Handler);
// Under async exceptions, catch(...) need to catch HW exception too
// Mark scope with SehTryBegin as a SEH __try scope
if (getLangOpts().EHAsynch)
llvm_unreachable("NYI");
}
}
}
void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) {
unsigned NumHandlers = S.getNumHandlers();
EHCatchScope &CatchScope = cast<EHCatchScope>(*EHStack.begin());
assert(CatchScope.getNumHandlers() == NumHandlers);
mlir::cir::TryOp tryOp = currLexScope->getTry();
// If the catch was not required, bail out now.
if (!CatchScope.hasEHBranches()) {
CatchScope.clearHandlerBlocks();
EHStack.popCatch();
// Drop all basic block from all catch regions.
SmallVector<mlir::Block *> eraseBlocks;
for (mlir::Region &r : tryOp.getCatchRegions()) {
if (r.empty())
continue;
for (mlir::Block &b : r.getBlocks())
eraseBlocks.push_back(&b);
}
for (mlir::Block *b : eraseBlocks)
b->erase();
tryOp.setCatchTypesAttr({});
return;
}
// Emit the structure of the EH dispatch for this catch.
buildCatchDispatchBlock(*this, CatchScope, tryOp);
// Copy the handler blocks off before we pop the EH stack. Emitting
// the handlers might scribble on this memory.
SmallVector<EHCatchScope::Handler, 8> Handlers(
CatchScope.begin(), CatchScope.begin() + NumHandlers);
EHStack.popCatch();
// Determine if we need an implicit rethrow for all these catch handlers;
// see the comment below.
bool doImplicitRethrow = false;
if (IsFnTryBlock)
doImplicitRethrow = isa<CXXDestructorDecl>(CurCodeDecl) ||
isa<CXXConstructorDecl>(CurCodeDecl);
// Wasm uses Windows-style EH instructions, but merges all catch clauses into
// one big catchpad. So we save the old funclet pad here before we traverse
// each catch handler.
SaveAndRestore RestoreCurrentFuncletPad(CurrentFuncletPad);
mlir::Block *WasmCatchStartBlock = nullptr;
if (EHPersonality::get(*this).isWasmPersonality()) {
llvm_unreachable("NYI");
}
bool HasCatchAll = false;
for (unsigned I = NumHandlers; I != 0; --I) {
HasCatchAll |= Handlers[I - 1].isCatchAll();
mlir::Block *CatchBlock = Handlers[I - 1].Block;
mlir::OpBuilder::InsertionGuard guard(getBuilder());
getBuilder().setInsertionPointToStart(CatchBlock);
// Catch the exception if this isn't a catch-all.
const CXXCatchStmt *C = S.getHandler(I - 1);
// Enter a cleanup scope, including the catch variable and the
// end-catch.
RunCleanupsScope CatchScope(*this);
// Initialize the catch variable and set up the cleanups.
SaveAndRestore RestoreCurrentFuncletPad(CurrentFuncletPad);
CGM.getCXXABI().emitBeginCatch(*this, C);
// Emit the PGO counter increment.
assert(!MissingFeatures::incrementProfileCounter());
// Perform the body of the catch.
(void)buildStmt(C->getHandlerBlock(), /*useCurrentScope=*/true);
// [except.handle]p11:
// The currently handled exception is rethrown if control
// reaches the end of a handler of the function-try-block of a
// constructor or destructor.
// It is important that we only do this on fallthrough and not on
// return. Note that it's illegal to put a return in a
// constructor function-try-block's catch handler (p14), so this
// really only applies to destructors.
if (doImplicitRethrow && HaveInsertPoint()) {
llvm_unreachable("NYI");
}
// Fall out through the catch cleanups.
CatchScope.ForceCleanup();
}
// Because in wasm we merge all catch clauses into one big catchpad, in case
// none of the types in catch handlers matches after we test against each of
// them, we should unwind to the next EH enclosing scope. We generate a call
// to rethrow function here to do that.
if (EHPersonality::get(*this).isWasmPersonality() && !HasCatchAll) {
assert(WasmCatchStartBlock);
// Navigate for the "rethrow" block we created in emitWasmCatchPadBlock().
// Wasm uses landingpad-style conditional branches to compare selectors, so
// we follow the false destination for each of the cond branches to reach
// the rethrow block.
llvm_unreachable("NYI");
}
assert(!MissingFeatures::incrementProfileCounter());
}
/// Check whether this is a non-EH scope, i.e. a scope which doesn't
/// affect exception handling. Currently, the only non-EH scopes are
/// normal-only cleanup scopes.
static bool isNonEHScope(const EHScope &S) {
switch (S.getKind()) {
case EHScope::Cleanup:
return !cast<EHCleanupScope>(S).isEHCleanup();
case EHScope::Filter:
case EHScope::Catch:
case EHScope::Terminate:
return false;
}
llvm_unreachable("Invalid EHScope Kind!");
}
mlir::Operation *CIRGenFunction::buildLandingPad(mlir::cir::TryOp tryOp) {
assert(EHStack.requiresLandingPad());
assert(!CGM.getLangOpts().IgnoreExceptions &&
"LandingPad should not be emitted when -fignore-exceptions are in "
"effect.");
EHScope &innermostEHScope = *EHStack.find(EHStack.getInnermostEHScope());
switch (innermostEHScope.getKind()) {
case EHScope::Terminate:
return getTerminateLandingPad();
case EHScope::Catch:
case EHScope::Cleanup:
case EHScope::Filter:
if (auto *lpad = innermostEHScope.getCachedLandingPad())
return lpad;
}
// If there's an existing TryOp, it means we got a `cir.try` scope
// that leads to this "landing pad" creation site. Otherwise, exceptions
// are enabled but a throwing function is called anyways (common pattern
// with function local static initializers).
{
// Save the current CIR generation state.
mlir::OpBuilder::InsertionGuard guard(builder);
assert(!MissingFeatures::generateDebugInfo() && "NYI");
// Traditional LLVM codegen creates the lpad basic block, extract
// values, landing pad instructions, etc.
// Accumulate all the handlers in scope.
bool hasCatchAll = false;
bool hasCleanup = false;
bool hasFilter = false;
SmallVector<mlir::Value, 4> filterTypes;
llvm::SmallPtrSet<mlir::Attribute, 4> catchTypes;
SmallVector<mlir::Attribute, 4> clauses;
for (EHScopeStack::iterator I = EHStack.begin(), E = EHStack.end(); I != E;
++I) {
switch (I->getKind()) {
case EHScope::Cleanup:
// If we have a cleanup, remember that.
hasCleanup = (hasCleanup || cast<EHCleanupScope>(*I).isEHCleanup());
continue;
case EHScope::Filter: {
llvm_unreachable("NYI");
}
case EHScope::Terminate:
// Terminate scopes are basically catch-alls.
// assert(!hasCatchAll);
// hasCatchAll = true;
// goto done;
llvm_unreachable("NYI");
case EHScope::Catch:
break;
}
EHCatchScope &catchScope = cast<EHCatchScope>(*I);
for (unsigned hi = 0, he = catchScope.getNumHandlers(); hi != he; ++hi) {
EHCatchScope::Handler handler = catchScope.getHandler(hi);
assert(handler.Type.Flags == 0 &&
"landingpads do not support catch handler flags");
// If this is a catch-all, register that and abort.
if (!handler.Type.RTTI) {
assert(!hasCatchAll);
hasCatchAll = true;
goto done;
}
// Check whether we already have a handler for this type.
if (catchTypes.insert(handler.Type.RTTI).second) {
// If not, keep track to later add to catch op.
clauses.push_back(handler.Type.RTTI);
}
}
}
done:
// If we have a catch-all, add null to the landingpad.
assert(!(hasCatchAll && hasFilter));
if (hasCatchAll) {
// Attach the catch_all region. Can't coexist with an unwind one.
auto catchAll = mlir::cir::CatchAllAttr::get(builder.getContext());
clauses.push_back(catchAll);
// If we have an EH filter, we need to add those handlers in the
// right place in the landingpad, which is to say, at the end.
} else if (hasFilter) {
// Create a filter expression: a constant array indicating which filter
// types there are. The personality routine only lands here if the filter
// doesn't match.
llvm_unreachable("NYI");
// Otherwise, signal that we at least have cleanups.
} else if (hasCleanup) {
if (!tryOp.isCleanupActive())
builder.createBlock(&tryOp.getCleanupRegion());
}
assert((clauses.size() > 0 || hasCleanup) && "no catch clauses!");
// If there's no catch_all, attach the unwind region. This needs to be the
// last region in the TryOp operation catch list.
if (!hasCatchAll) {
auto catchUnwind = mlir::cir::CatchUnwindAttr::get(builder.getContext());
clauses.push_back(catchUnwind);
}
// Add final array of clauses into TryOp.
tryOp.setCatchTypesAttr(
mlir::ArrayAttr::get(builder.getContext(), clauses));
// In traditional LLVM codegen. this tells the backend how to generate the
// landing pad by generating a branch to the dispatch block. In CIR the same
// function is called to gather some state, but this block info it's not
// useful per-se.
(void)getEHDispatchBlock(EHStack.getInnermostEHScope(), tryOp);
}
return tryOp;
}
// Differently from LLVM traditional codegen, there are no dispatch blocks
// to look at given cir.try_call does not jump to blocks like invoke does.
// However, we keep this around since other parts of CIRGen use
// getCachedEHDispatchBlock to infer state.
mlir::Block *
CIRGenFunction::getEHDispatchBlock(EHScopeStack::stable_iterator si,
mlir::cir::TryOp tryOp) {
if (EHPersonality::get(*this).usesFuncletPads())
llvm_unreachable("NYI");
// The dispatch block for the end of the scope chain is a block that
// just resumes unwinding.
if (si == EHStack.stable_end())
return getEHResumeBlock(true, tryOp);
// Otherwise, we should look at the actual scope.
EHScope &scope = *EHStack.find(si);
auto *dispatchBlock = scope.getCachedEHDispatchBlock();
if (!dispatchBlock) {
switch (scope.getKind()) {
case EHScope::Catch: {
// Apply a special case to a single catch-all.
EHCatchScope &catchScope = cast<EHCatchScope>(scope);
if (catchScope.getNumHandlers() == 1 &&
catchScope.getHandler(0).isCatchAll()) {
dispatchBlock = catchScope.getHandler(0).Block;
// Otherwise, make a dispatch block.
} else {
// As said in the function comment, just signal back we
// have something - even though the block value doesn't
// have any real meaning.
dispatchBlock = catchScope.getHandler(0).Block;
assert(dispatchBlock && "find another approach to signal");
}
break;
}
case EHScope::Cleanup: {
assert(tryOp && "expected cir.try available");
llvm::MutableArrayRef<mlir::Region> regions = tryOp.getCatchRegions();
assert(regions.size() == 1 && "expected only one region");
dispatchBlock = &regions[0].getBlocks().back();
break;
}
case EHScope::Filter:
llvm_unreachable("NYI");
break;
case EHScope::Terminate:
llvm_unreachable("NYI");
break;
}
scope.setCachedEHDispatchBlock(dispatchBlock);
}
return dispatchBlock;
}
mlir::Operation *CIRGenFunction::getInvokeDestImpl() {
assert(EHStack.requiresLandingPad());
assert(!EHStack.empty());
// If exceptions are disabled/ignored and SEH is not in use, then there is no
// invoke destination. SEH "works" even if exceptions are off. In practice,
// this means that C++ destructors and other EH cleanups don't run, which is
// consistent with MSVC's behavior, except in the presence of -EHa
const LangOptions &LO = CGM.getLangOpts();
if (!LO.Exceptions || LO.IgnoreExceptions) {
if (!LO.Borland && !LO.MicrosoftExt)
return nullptr;
if (!currentFunctionUsesSEHTry())
return nullptr;
}
// CUDA device code doesn't have exceptions.
if (LO.CUDA && LO.CUDAIsDevice)
return nullptr;
// Check the innermost scope for a cached landing pad. If this is
// a non-EH cleanup, we'll check enclosing scopes in EmitLandingPad.
auto *LP = EHStack.begin()->getCachedLandingPad();
if (LP)
return LP;
const EHPersonality &Personality = EHPersonality::get(*this);
// FIXME(cir): add personality function
// if (!CurFn->hasPersonalityFn())
// CurFn->setPersonalityFn(getOpaquePersonalityFn(CGM, Personality));
auto createSurroundingTryOp = [&]() {
// In OG, we build the landing pad for this scope. In CIR, we emit a
// synthetic cir.try because this didn't come from codegenerating from a
// try/catch in C++.
auto tryOp = builder.create<mlir::cir::TryOp>(
*currSrcLoc, /*scopeBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc) {},
// Don't emit the code right away for catch clauses, for
// now create the regions and consume the try scope result.
// Note that clauses are later populated in
// CIRGenFunction::buildLandingPad.
[&](mlir::OpBuilder &b, mlir::Location loc,
mlir::OperationState &result) {
// Since this didn't come from an explicit try, we only need one
// handler: unwind.
auto *r = result.addRegion();
builder.createBlock(r);
});
tryOp.setSynthetic(true);
return tryOp;
};
if (Personality.usesFuncletPads()) {
// We don't need separate landing pads in the funclet model.
llvm::errs() << "PersonalityFn: " << Personality.PersonalityFn << "\n";
llvm_unreachable("NYI");
} else {
mlir::cir::TryOp tryOp = nullptr;
// Attempt to find a suitable existing parent try/catch, if none
// is available, create a synthetic cir.try in order to wrap the side
// effects of a potential throw.
if (currLexScope)
tryOp = currLexScope->getClosestTryParent();
if (!tryOp)
tryOp = createSurroundingTryOp();
assert(tryOp && "cir.try expected");
LP = buildLandingPad(tryOp);
}
assert(LP);
// Cache the landing pad on the innermost scope. If this is a
// non-EH scope, cache the landing pad on the enclosing scope, too.
for (EHScopeStack::iterator ir = EHStack.begin(); true; ++ir) {
ir->setCachedLandingPad(LP);
if (!isNonEHScope(*ir))
break;
}
return LP;
}
mlir::Operation *CIRGenFunction::getTerminateLandingPad() {
llvm_unreachable("NYI");
}