Skip to content

Commit d019a02

Browse files
authored
[CIR] Upstream Exception CXXTryStmt (#162528)
Upstream the basic support for the C++ try catch statement with a try block that doesn't contain any call instructions and with a catch-all statement Issue #154992
1 parent 12abe8a commit d019a02

File tree

10 files changed

+457
-4
lines changed

10 files changed

+457
-4
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ struct MissingFeatures {
119119
static bool opCallLandingPad() { return false; }
120120
static bool opCallContinueBlock() { return false; }
121121
static bool opCallChain() { return false; }
122+
static bool opCallExceptionAttr() { return false; }
122123

123124
// CXXNewExpr
124125
static bool exprNewNullCheck() { return false; }

clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ CIRGenCXXABI::AddedStructorArgCounts CIRGenCXXABI::addImplicitConstructorArgs(
3737
addedArgs.suffix.size());
3838
}
3939

40+
CatchTypeInfo CIRGenCXXABI::getCatchAllTypeInfo() {
41+
return CatchTypeInfo{{}, 0};
42+
}
43+
4044
void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf,
4145
FunctionArgList &params) {
4246
const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl());

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H
1616

1717
#include "CIRGenCall.h"
18+
#include "CIRGenCleanup.h"
1819
#include "CIRGenFunction.h"
1920
#include "CIRGenModule.h"
2021

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

159+
virtual CatchTypeInfo getCatchAllTypeInfo();
160+
158161
/// Get the implicit (second) parameter that comes after the "this" pointer,
159162
/// or nullptr if there is isn't one.
160163
virtual mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &cgf,

clang/lib/CIR/CodeGen/CIRGenCleanup.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ void EHScopeStack::popCleanup() {
108108
assert(!cir::MissingFeatures::ehCleanupBranchFixups());
109109
}
110110

111+
EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) {
112+
char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers));
113+
assert(!cir::MissingFeatures::innermostEHScope());
114+
EHCatchScope *scope = new (buffer) EHCatchScope(numHandlers);
115+
return scope;
116+
}
117+
111118
static void emitCleanup(CIRGenFunction &cgf, EHScopeStack::Cleanup *cleanup) {
112119
// Ask the cleanup to emit itself.
113120
assert(cgf.haveInsertPoint() && "expected insertion point");

clang/lib/CIR/CodeGen/CIRGenCleanup.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@
2020

2121
namespace clang::CIRGen {
2222

23+
/// The MS C++ ABI needs a pointer to RTTI data plus some flags to describe the
24+
/// type of a catch handler, so we use this wrapper.
25+
struct CatchTypeInfo {
26+
mlir::TypedAttr rtti;
27+
unsigned flags;
28+
};
29+
2330
/// A protected scope for zero-cost EH handling.
2431
class EHScope {
2532
class CommonBitFields {
@@ -29,6 +36,12 @@ class EHScope {
2936
enum { NumCommonBits = 3 };
3037

3138
protected:
39+
class CatchBitFields {
40+
friend class EHCatchScope;
41+
unsigned : NumCommonBits;
42+
unsigned numHandlers : 32 - NumCommonBits;
43+
};
44+
3245
class CleanupBitFields {
3346
friend class EHCleanupScope;
3447
unsigned : NumCommonBits;
@@ -58,6 +71,7 @@ class EHScope {
5871

5972
union {
6073
CommonBitFields commonBits;
74+
CatchBitFields catchBits;
6175
CleanupBitFields cleanupBits;
6276
};
6377

@@ -67,6 +81,71 @@ class EHScope {
6781
EHScope(Kind kind) { commonBits.kind = kind; }
6882

6983
Kind getKind() const { return static_cast<Kind>(commonBits.kind); }
84+
85+
bool mayThrow() const {
86+
// Traditional LLVM codegen also checks for `!block->use_empty()`, but
87+
// in CIRGen the block content is not important, just used as a way to
88+
// signal `hasEHBranches`.
89+
assert(!cir::MissingFeatures::ehstackBranches());
90+
return false;
91+
}
92+
};
93+
94+
/// A scope which attempts to handle some, possibly all, types of
95+
/// exceptions.
96+
///
97+
/// Objective C \@finally blocks are represented using a cleanup scope
98+
/// after the catch scope.
99+
100+
class EHCatchScope : public EHScope {
101+
// In effect, we have a flexible array member
102+
// Handler Handlers[0];
103+
// But that's only standard in C99, not C++, so we have to do
104+
// annoying pointer arithmetic instead.
105+
106+
public:
107+
struct Handler {
108+
/// A type info value, or null MLIR attribute for a catch-all
109+
CatchTypeInfo type;
110+
111+
/// The catch handler for this type.
112+
mlir::Region *region;
113+
};
114+
115+
private:
116+
friend class EHScopeStack;
117+
118+
Handler *getHandlers() { return reinterpret_cast<Handler *>(this + 1); }
119+
120+
public:
121+
static size_t getSizeForNumHandlers(unsigned n) {
122+
return sizeof(EHCatchScope) + n * sizeof(Handler);
123+
}
124+
125+
EHCatchScope(unsigned numHandlers) : EHScope(Catch) {
126+
catchBits.numHandlers = numHandlers;
127+
assert(catchBits.numHandlers == numHandlers && "NumHandlers overflow?");
128+
}
129+
130+
unsigned getNumHandlers() const { return catchBits.numHandlers; }
131+
132+
void setHandler(unsigned i, CatchTypeInfo type, mlir::Region *region) {
133+
assert(i < getNumHandlers());
134+
getHandlers()[i].type = type;
135+
getHandlers()[i].region = region;
136+
}
137+
138+
// Clear all handler blocks.
139+
// FIXME: it's better to always call clearHandlerBlocks in DTOR and have a
140+
// 'takeHandler' or some such function which removes ownership from the
141+
// EHCatchScope object if the handlers should live longer than EHCatchScope.
142+
void clearHandlerBlocks() {
143+
// The blocks are owned by TryOp, nothing to delete.
144+
}
145+
146+
static bool classof(const EHScope *scope) {
147+
return scope->getKind() == Catch;
148+
}
70149
};
71150

72151
/// A cleanup scope which generates the cleanup blocks lazily.
@@ -147,5 +226,13 @@ EHScopeStack::find(stable_iterator savePoint) const {
147226
return iterator(endOfBuffer - savePoint.size);
148227
}
149228

229+
inline void EHScopeStack::popCatch() {
230+
assert(!empty() && "popping exception stack when not empty");
231+
232+
EHCatchScope &scope = llvm::cast<EHCatchScope>(*begin());
233+
assert(!cir::MissingFeatures::innermostEHScope());
234+
deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers()));
235+
}
236+
150237
} // namespace clang::CIRGen
151238
#endif // CLANG_LIB_CIR_CODEGEN_CIRGENCLEANUP_H

clang/lib/CIR/CodeGen/CIRGenException.cpp

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,153 @@ mlir::LogicalResult CIRGenFunction::emitCXXTryStmt(const CXXTryStmt &s) {
6969
if (s.getTryBlock()->body_empty())
7070
return mlir::LogicalResult::success();
7171

72-
cgm.errorNYI("exitCXXTryStmt: CXXTryStmt with non-empty body");
73-
return mlir::LogicalResult::success();
72+
mlir::Location loc = getLoc(s.getSourceRange());
73+
// Create a scope to hold try local storage for catch params.
74+
75+
mlir::OpBuilder::InsertPoint scopeIP;
76+
cir::ScopeOp::create(
77+
builder, loc,
78+
/*scopeBuilder=*/[&](mlir::OpBuilder &b, mlir::Location loc) {
79+
scopeIP = builder.saveInsertionPoint();
80+
});
81+
82+
mlir::OpBuilder::InsertionGuard guard(builder);
83+
builder.restoreInsertionPoint(scopeIP);
84+
mlir::LogicalResult result = emitCXXTryStmtUnderScope(s);
85+
cir::YieldOp::create(builder, loc);
86+
return result;
87+
}
88+
89+
mlir::LogicalResult
90+
CIRGenFunction::emitCXXTryStmtUnderScope(const CXXTryStmt &s) {
91+
const llvm::Triple &t = getTarget().getTriple();
92+
// If we encounter a try statement on in an OpenMP target region offloaded to
93+
// a GPU, we treat it as a basic block.
94+
const bool isTargetDevice =
95+
(cgm.getLangOpts().OpenMPIsTargetDevice && (t.isNVPTX() || t.isAMDGCN()));
96+
if (isTargetDevice) {
97+
cgm.errorNYI(
98+
"emitCXXTryStmtUnderScope: OpenMP target region offloaded to GPU");
99+
return mlir::success();
100+
}
101+
102+
unsigned numHandlers = s.getNumHandlers();
103+
mlir::Location tryLoc = getLoc(s.getBeginLoc());
104+
mlir::OpBuilder::InsertPoint beginInsertTryBody;
105+
106+
bool hasCatchAll = false;
107+
for (unsigned i = 0; i != numHandlers; ++i) {
108+
hasCatchAll |= s.getHandler(i)->getExceptionDecl() == nullptr;
109+
if (hasCatchAll)
110+
break;
111+
}
112+
113+
// Create the scope to represent only the C/C++ `try {}` part. However,
114+
// don't populate right away. Create regions for the catch handlers,
115+
// but don't emit the handler bodies yet. For now, only make sure the
116+
// scope returns the exception information.
117+
auto tryOp = cir::TryOp::create(
118+
builder, tryLoc,
119+
/*tryBuilder=*/
120+
[&](mlir::OpBuilder &b, mlir::Location loc) {
121+
beginInsertTryBody = builder.saveInsertionPoint();
122+
},
123+
/*handlersBuilder=*/
124+
[&](mlir::OpBuilder &b, mlir::Location loc,
125+
mlir::OperationState &result) {
126+
mlir::OpBuilder::InsertionGuard guard(b);
127+
128+
// We create an extra region for an unwind catch handler in case the
129+
// catch-all handler doesn't exists
130+
unsigned numRegionsToCreate =
131+
hasCatchAll ? numHandlers : numHandlers + 1;
132+
133+
for (unsigned i = 0; i != numRegionsToCreate; ++i) {
134+
mlir::Region *region = result.addRegion();
135+
builder.createBlock(region);
136+
}
137+
});
138+
139+
// Finally emit the body for try/catch.
140+
{
141+
mlir::Location loc = tryOp.getLoc();
142+
mlir::OpBuilder::InsertionGuard guard(builder);
143+
builder.restoreInsertionPoint(beginInsertTryBody);
144+
CIRGenFunction::LexicalScope tryScope{*this, loc,
145+
builder.getInsertionBlock()};
146+
147+
tryScope.setAsTry(tryOp);
148+
149+
// Attach the basic blocks for the catch regions.
150+
enterCXXTryStmt(s, tryOp);
151+
152+
// Emit the body for the `try {}` part.
153+
{
154+
mlir::OpBuilder::InsertionGuard guard(builder);
155+
CIRGenFunction::LexicalScope tryBodyScope{*this, loc,
156+
builder.getInsertionBlock()};
157+
if (emitStmt(s.getTryBlock(), /*useCurrentScope=*/true).failed())
158+
return mlir::failure();
159+
}
160+
161+
// Emit catch clauses.
162+
exitCXXTryStmt(s);
163+
}
164+
165+
return mlir::success();
166+
}
167+
168+
void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
169+
bool isFnTryBlock) {
170+
unsigned numHandlers = s.getNumHandlers();
171+
EHCatchScope *catchScope = ehStack.pushCatch(numHandlers);
172+
for (unsigned i = 0; i != numHandlers; ++i) {
173+
const CXXCatchStmt *catchStmt = s.getHandler(i);
174+
if (catchStmt->getExceptionDecl()) {
175+
cgm.errorNYI("enterCXXTryStmt: CatchStmt with ExceptionDecl");
176+
return;
177+
}
178+
179+
// No exception decl indicates '...', a catch-all.
180+
mlir::Region *handler = &tryOp.getHandlerRegions()[i];
181+
catchScope->setHandler(i, cgm.getCXXABI().getCatchAllTypeInfo(), handler);
182+
183+
// Under async exceptions, catch(...) needs to catch HW exception too
184+
// Mark scope with SehTryBegin as a SEH __try scope
185+
if (getLangOpts().EHAsynch) {
186+
cgm.errorNYI("enterCXXTryStmt: EHAsynch");
187+
return;
188+
}
189+
}
190+
}
191+
192+
void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
193+
unsigned numHandlers = s.getNumHandlers();
194+
EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin());
195+
assert(catchScope.getNumHandlers() == numHandlers);
196+
cir::TryOp tryOp = curLexScope->getTry();
197+
198+
// If the catch was not required, bail out now.
199+
if (!catchScope.mayThrow()) {
200+
catchScope.clearHandlerBlocks();
201+
ehStack.popCatch();
202+
203+
// Drop all basic block from all catch regions.
204+
SmallVector<mlir::Block *> eraseBlocks;
205+
for (mlir::Region &handlerRegion : tryOp.getHandlerRegions()) {
206+
if (handlerRegion.empty())
207+
continue;
208+
209+
for (mlir::Block &b : handlerRegion.getBlocks())
210+
eraseBlocks.push_back(&b);
211+
}
212+
213+
for (mlir::Block *b : eraseBlocks)
214+
b->erase();
215+
216+
tryOp.setHandlerTypesAttr({});
217+
return;
218+
}
219+
220+
cgm.errorNYI("exitCXXTryStmt: Required catch");
74221
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,9 @@ class CIRGenFunction : public CIRGenTypeCache {
954954

955955
LexicalScope *parentScope = nullptr;
956956

957+
// Holds the actual value for ScopeKind::Try
958+
cir::TryOp tryOp = nullptr;
959+
957960
// Only Regular is used at the moment. Support for other kinds will be
958961
// added as the relevant statements/expressions are upstreamed.
959962
enum Kind {
@@ -1013,6 +1016,10 @@ class CIRGenFunction : public CIRGenTypeCache {
10131016
void setAsGlobalInit() { scopeKind = Kind::GlobalInit; }
10141017
void setAsSwitch() { scopeKind = Kind::Switch; }
10151018
void setAsTernary() { scopeKind = Kind::Ternary; }
1019+
void setAsTry(cir::TryOp op) {
1020+
scopeKind = Kind::Try;
1021+
tryOp = op;
1022+
}
10161023

10171024
// Lazy create cleanup block or return what's available.
10181025
mlir::Block *getOrCreateCleanupBlock(mlir::OpBuilder &builder) {
@@ -1022,6 +1029,11 @@ class CIRGenFunction : public CIRGenTypeCache {
10221029
return cleanupBlock;
10231030
}
10241031

1032+
cir::TryOp getTry() {
1033+
assert(isTry());
1034+
return tryOp;
1035+
}
1036+
10251037
mlir::Block *getCleanupBlock(mlir::OpBuilder &builder) {
10261038
return cleanupBlock;
10271039
}
@@ -1348,6 +1360,13 @@ class CIRGenFunction : public CIRGenTypeCache {
13481360

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

1363+
mlir::LogicalResult emitCXXTryStmtUnderScope(const clang::CXXTryStmt &s);
1364+
1365+
void enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
1366+
bool isFnTryBlock = false);
1367+
1368+
void exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock = false);
1369+
13511370
void emitCtorPrologue(const clang::CXXConstructorDecl *ctor,
13521371
clang::CXXCtorType ctorType, FunctionArgList &args);
13531372

clang/lib/CIR/CodeGen/EHScopeStack.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,14 @@ class EHScopeStack {
155155
/// Pops a cleanup scope off the stack. This is private to CIRGenCleanup.cpp.
156156
void popCleanup();
157157

158+
/// Push a set of catch handlers on the stack. The catch is
159+
/// uninitialized and will need to have the given number of handlers
160+
/// set on it.
161+
class EHCatchScope *pushCatch(unsigned numHandlers);
162+
163+
/// Pops a catch scope off the stack. This is private to CIRGenException.cpp.
164+
void popCatch();
165+
158166
/// Determines whether the exception-scopes stack is empty.
159167
bool empty() const { return startOfData == endOfBuffer; }
160168

0 commit comments

Comments
 (0)