Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ struct MissingFeatures {
static bool opCallLandingPad() { return false; }
static bool opCallContinueBlock() { return false; }
static bool opCallChain() { return false; }
static bool opCallExceptionAttr() { return false; }

// CXXNewExpr
static bool exprNewNullCheck() { return false; }
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ CIRGenCXXABI::AddedStructorArgCounts CIRGenCXXABI::addImplicitConstructorArgs(
addedArgs.suffix.size());
}

CatchTypeInfo CIRGenCXXABI::getCatchAllTypeInfo() {
return CatchTypeInfo{nullptr, 0};
}

void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf,
FunctionArgList &params) {
const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl());
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H

#include "CIRGenCall.h"
#include "CIRGenCleanup.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"

Expand Down Expand Up @@ -155,6 +156,8 @@ class CIRGenCXXABI {
/// Loads the incoming C++ this pointer as it was passed by the caller.
mlir::Value loadIncomingCXXThis(CIRGenFunction &cgf);

virtual CatchTypeInfo getCatchAllTypeInfo();

/// Get the implicit (second) parameter that comes after the "this" pointer,
/// or nullptr if there is isn't one.
virtual mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &cgf,
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ void EHScopeStack::popCleanup() {
assert(!cir::MissingFeatures::ehCleanupBranchFixups());
}

EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) {
char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers));
assert(!cir::MissingFeatures::innermostEHScope());
EHCatchScope *scope = new (buffer) EHCatchScope(numHandlers);
return scope;
}

static void emitCleanup(CIRGenFunction &cgf, EHScopeStack::Cleanup *cleanup) {
// Ask the cleanup to emit itself.
assert(cgf.haveInsertPoint() && "expected insertion point");
Expand Down
88 changes: 88 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCleanup.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@

namespace clang::CIRGen {

/// The MS C++ ABI needs a pointer to RTTI data plus some flags to describe the
/// type of a catch handler, so we use this wrapper.
struct CatchTypeInfo {
mlir::TypedAttr rtti;
unsigned flags;
};

/// A protected scope for zero-cost EH handling.
class EHScope {
class CommonBitFields {
Expand All @@ -29,6 +36,12 @@ class EHScope {
enum { NumCommonBits = 3 };

protected:
class CatchBitFields {
friend class EHCatchScope;
unsigned : NumCommonBits;
unsigned numHandlers : 32 - NumCommonBits;
};

class CleanupBitFields {
friend class EHCleanupScope;
unsigned : NumCommonBits;
Expand Down Expand Up @@ -58,6 +71,7 @@ class EHScope {

union {
CommonBitFields commonBits;
CatchBitFields catchBits;
CleanupBitFields cleanupBits;
};

Expand All @@ -67,6 +81,72 @@ class EHScope {
EHScope(Kind kind) { commonBits.kind = kind; }

Kind getKind() const { return static_cast<Kind>(commonBits.kind); }

bool hasEHBranches() const {
// Traditional LLVM codegen also checks for `!block->use_empty()`, but
// in CIRGen the block content is not important, just used as a way to
// signal `hasEHBranches`.
assert(!cir::MissingFeatures::ehstackBranches());
return false;
}
};

/// A scope which attempts to handle some, possibly all, types of
/// exceptions.
///
/// Objective C \@finally blocks are represented using a cleanup scope
/// after the catch scope.

class EHCatchScope : public EHScope {
// In effect, we have a flexible array member
// Handler Handlers[0];
// But that's only standard in C99, not C++, so we have to do
// annoying pointer arithmetic instead.

public:
struct Handler {
/// A type info value, or null (C++ null, not an LLVM null pointer)
/// for a catch-all.
CatchTypeInfo type;

/// The catch handler for this type.
mlir::Block *block;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at all the uses of this in the incubator, but it seems like an mlir::Region would be more natural here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it seems like an mlir::Region would be more natural here.

Yes, I think it was Block because all uses are blocks too, not regions, I tested it in the incubator with Region and updated some small codes to access the first Block from region in users, and all tests passed

};

private:
friend class EHScopeStack;

Handler *getHandlers() { return reinterpret_cast<Handler *>(this + 1); }

public:
static size_t getSizeForNumHandlers(unsigned n) {
return sizeof(EHCatchScope) + n * sizeof(Handler);
}

EHCatchScope(unsigned numHandlers) : EHScope(Catch) {
catchBits.numHandlers = numHandlers;
assert(catchBits.numHandlers == numHandlers && "NumHandlers overflow?");
}

unsigned getNumHandlers() const { return catchBits.numHandlers; }

void setHandler(unsigned i, CatchTypeInfo type, mlir::Block *block) {
assert(i < getNumHandlers());
getHandlers()[i].type = type;
getHandlers()[i].block = block;
}

// Clear all handler blocks.
// FIXME: it's better to always call clearHandlerBlocks in DTOR and have a
// 'takeHandler' or some such function which removes ownership from the
// EHCatchScope object if the handlers should live longer than EHCatchScope.
void clearHandlerBlocks() {
// The blocks are owned by TryOp, nothing to delete.
}

static bool classof(const EHScope *scope) {
return scope->getKind() == Catch;
}
};

/// A cleanup scope which generates the cleanup blocks lazily.
Expand Down Expand Up @@ -147,5 +227,13 @@ EHScopeStack::find(stable_iterator savePoint) const {
return iterator(endOfBuffer - savePoint.size);
}

inline void EHScopeStack::popCatch() {
assert(!empty() && "popping exception stack when not empty");

EHCatchScope &scope = llvm::cast<EHCatchScope>(*begin());
assert(!cir::MissingFeatures::innermostEHScope());
deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers()));
}

} // namespace clang::CIRGen
#endif // CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H
149 changes: 147 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenException.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,151 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
if (s.getTryBlock()->body_empty())
return mlir::LogicalResult::success();

cgm.errorNYI("exitCXXTryStmt: CXXTryStmt with non-empty body");
return mlir::LogicalResult::success();
mlir::Location loc = getLoc(s.getSourceRange());
// Create a scope to hold try local storage for catch params.

mlir::OpBuilder::InsertPoint scopeIP;
cir::ScopeOp::create(
builder, loc,
/*scopeBuilder=*/[&](mlir::OpBuilder &b, mlir::Location loc) {
scopeIP = builder.saveInsertionPoint();
});

mlir::OpBuilder::InsertionGuard guard(builder);
builder.restoreInsertionPoint(scopeIP);
mlir::LogicalResult result = emitCXXTryStmtUnderScope(s);
cir::YieldOp::create(builder, loc);
return result;
}

mlir::LogicalResult
CIRGenFunction::emitCXXTryStmtUnderScope(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()));
if (isTargetDevice) {
cgm.errorNYI(
"emitCXXTryStmtUnderScope: OpenMP target region offloaded to GPU");
return mlir::success();
}

unsigned numHandlers = s.getNumHandlers();
mlir::Location tryLoc = getLoc(s.getBeginLoc());
mlir::OpBuilder::InsertPoint beginInsertTryBody;

bool hasCatchAll = false;
for (unsigned i = 0; i != numHandlers; ++i) {
hasCatchAll |= s.getHandler(i)->getExceptionDecl() == nullptr;
if (hasCatchAll)
break;
}

// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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
// don't populate right away. Create regions for the catch handlers,
// but don't emit the handler bodies yet. For now, only make sure the

I think this is what the comment meant? It's not clear to me in what way this is making sure the scope returns the exception info.

// scope returns the exception information.
auto tryOp = cir::TryOp::create(
builder, tryLoc,
/*tryBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc) {
beginInsertTryBody = builder.saveInsertionPoint();
},
/*handlersBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc,
mlir::OperationState &result) {
mlir::OpBuilder::InsertionGuard guard(b);

// We create an extra region for an unwind catch handler in case the
// catch-all handler doesn't exists
unsigned numRegionsToCreate =
hasCatchAll ? numHandlers : numHandlers + 1;

for (unsigned i = 0; i != numRegionsToCreate; ++i)
builder.createBlock(result.addRegion());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (unsigned i = 0; i != numRegionsToCreate; ++i)
builder.createBlock(result.addRegion());
for (unsigned i = 0; i != numRegionsToCreate; ++i) {
mlir::Region r = result.addRegion();
builder.createBlock(r);
}

I think this improves readability.

It seems a little odd that we're creating a block before we emit the handler. Is that because the EHCatchScope::Handler structure stores an mlir::Block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes, also to take care of the actual number of regions (Including unwind), because later when we create the handlers, we don't need to check and create an extra region then

});

// Finally emit the body for try/catch.
{
mlir::Location loc = tryOp.getLoc();
mlir::OpBuilder::InsertionGuard guard(builder);
builder.restoreInsertionPoint(beginInsertTryBody);
CIRGenFunction::LexicalScope tryScope{*this, loc,
builder.getInsertionBlock()};

tryScope.setAsTry(tryOp);

// Attach the basic blocks for the catch regions.
enterCXXTryStmt(s, tryOp);

// Emit the body for the `try {}` part.
{
mlir::OpBuilder::InsertionGuard guard(builder);
CIRGenFunction::LexicalScope tryBodyScope{*this, loc,
builder.getInsertionBlock()};
if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed())
return mlir::failure();
}

// Emit catch clauses.
exitCXXTryStmt(s);
}

return mlir::success();
}

void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
bool isFnTryBlock) {
unsigned numHandlers = s.getNumHandlers();
EHCatchScope *catchScope = ehStack.pushCatch(numHandlers);
for (unsigned i = 0; i != numHandlers; ++i) {
const CXXCatchStmt *catchStmt = s.getHandler(i);
if (catchStmt->getExceptionDecl()) {
cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl");
return;
}

// No exception decl indicates '...', a catch-all.
mlir::Block *handler = &tryOp.getHandlerRegions()[i].getBlocks().front();
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) {
cgm.errorNYI("enterCXXTryStmt: EHAsynch");
return;
}
}
}

void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
unsigned numHandlers = s.getNumHandlers();
EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin());
assert(catchScope.getNumHandlers() == numHandlers);
cir::TryOp tryOp = curLexScope->getTry();

// If the catch was not required, bail out now.
if (!catchScope.hasEHBranches()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!catchScope.hasEHBranches()) {
if (!catchScope.mayThrow()) {

I'm concerned about the lack of alignment between the names we're keeping from the OGCG implementation of exception handling and the actual structure of the CIR implementation. For example, hasEHBranches here is referring to the fact that when the generated LLVM IR executes an invoke instruction, there is a control flow path to the basic block containing a landingpad instruction which literally branches to the basic block for a catch handler. We won't have anything like that in CIR.

I believe what this condition means is "catchScope contains instructions that might throw an exception" so I suggest we use a more appropriate name. (I also suggest that we shouldn't base it on CachedEHDispatchBlock as the incubator does.)

catchScope.clearHandlerBlocks();
ehStack.popCatch();

// Drop all basic block from all catch regions.
SmallVector<mlir::Block *> eraseBlocks;
for (mlir::Region &handlerRegion : tryOp.getHandlerRegions()) {
if (handlerRegion.empty())
continue;

for (mlir::Block &b : handlerRegion.getBlocks())
eraseBlocks.push_back(&b);
}

for (mlir::Block *b : eraseBlocks)
b->erase();

tryOp.setHandlerTypesAttr({});
return;
}

cgm.errorNYI("exitCXXTryStmt: Required catch");
}
19 changes: 19 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -942,6 +942,9 @@ class CIRGenFunction : public CIRGenTypeCache {

LexicalScope *parentScope = nullptr;

// Holds actual value for ScopeKind::Try
cir::TryOp tryOp = nullptr;

// Only Regular is used at the moment. Support for other kinds will be
// added as the relevant statements/expressions are upstreamed.
enum Kind {
Expand Down Expand Up @@ -1001,6 +1004,10 @@ class CIRGenFunction : public CIRGenTypeCache {
void setAsGlobalInit() { scopeKind = Kind::GlobalInit; }
void setAsSwitch() { scopeKind = Kind::Switch; }
void setAsTernary() { scopeKind = Kind::Ternary; }
void setAsTry(cir::TryOp op) {
scopeKind = Kind::Try;
tryOp = op;
}

// Lazy create cleanup block or return what's available.
mlir::Block *getOrCreateCleanupBlock(mlir::OpBuilder &builder) {
Expand All @@ -1010,6 +1017,11 @@ class CIRGenFunction : public CIRGenTypeCache {
return cleanupBlock;
}

cir::TryOp getTry() {
assert(isTry());
return tryOp;
}

mlir::Block *getCleanupBlock(mlir::OpBuilder &builder) {
return cleanupBlock;
}
Expand Down Expand Up @@ -1331,6 +1343,13 @@ class CIRGenFunction : public CIRGenTypeCache {

mlir::LogicalResult emitCXXTryStmt(const clang::CXXTryStmt &s);

mlir::LogicalResult emitCXXTryStmtUnderScope(const clang::CXXTryStmt &s);

void enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
bool isFnTryBlock = false);

void exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock = false);

void emitCtorPrologue(const clang::CXXConstructorDecl *ctor,
clang::CXXCtorType ctorType, FunctionArgList &args);

Expand Down
8 changes: 8 additions & 0 deletions clang/lib/CIR/CodeGen/EHScopeStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ class EHScopeStack {
/// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.
void popCleanup();

/// Push a set of catch handlers on the stack. The catch is
/// uninitialized and will need to have the given number of handlers
/// set on it.
class EHCatchScope *pushCatch(unsigned numHandlers);

/// Pops a catch scope off the stack. This is private to CIRGenException.cpp.
void popCatch();

/// Determines whether the exception-scopes stack is empty.
bool empty() const { return startOfData == endOfBuffer; }

Expand Down
Loading