-
Couldn't load subscription status.
- Fork 15k
[CIR] Upstream non-empty Try block with catch all #165158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[CIR] Upstream non-empty Try block with catch all #165158
Conversation
|
@llvm/pr-subscribers-clangir @llvm/pr-subscribers-clang Author: Amr Hesham (AmrDeveloper) ChangesUpstream support for try block and catch all block with a function call that may throw an exception. Issue #154992 Patch is 26.61 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/165158.diff 8 Files Affected:
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 50d4c035d30a1..ad0cd90b665d8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -465,12 +465,48 @@ static cir::CIRCallOpInterface
emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
cir::FuncType indirectFuncTy, mlir::Value indirectFuncVal,
cir::FuncOp directFuncOp,
- const SmallVectorImpl<mlir::Value> &cirCallArgs,
+ const SmallVectorImpl<mlir::Value> &cirCallArgs, bool isInvoke,
const mlir::NamedAttrList &attrs) {
CIRGenBuilderTy &builder = cgf.getBuilder();
assert(!cir::MissingFeatures::opCallSurroundingTry());
- assert(!cir::MissingFeatures::invokeOp());
+
+ if (isInvoke) {
+ // This call can throw, few options:
+ // - If this call does not have an associated cir.try, use the
+ // one provided by InvokeDest,
+ // - User written try/catch clauses require calls to handle
+ // exceptions under cir.try.
+
+ // In OG, we build the landing pad for this scope. In CIR, we emit a
+ // synthetic cir.try because this didn't come from code generating from a
+ // try/catch in C++.
+ assert(cgf.curLexScope && "expected scope");
+ cir::TryOp tryOp = cgf.curLexScope->getClosestTryParent();
+ if (!tryOp) {
+ cgf.cgm.errorNYI(
+ "emitCallLikeOp: call does not have an associated cir.try");
+ return {};
+ }
+
+ if (tryOp.getSynthetic()) {
+ cgf.cgm.errorNYI("emitCallLikeOp: tryOp synthetic");
+ return {};
+ }
+
+ cir::CallOp callOpWithExceptions;
+ if (indirectFuncTy) {
+ cgf.cgm.errorNYI("emitCallLikeOp: indirect function type");
+ return {};
+ }
+
+ callOpWithExceptions =
+ builder.createTryCallOp(callLoc, directFuncOp, cirCallArgs);
+
+ (void)cgf.getInvokeDest(tryOp);
+
+ return callOpWithExceptions;
+ }
assert(builder.getInsertionBlock() && "expected valid basic block");
@@ -628,10 +664,11 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
indirectFuncVal = calleePtr->getResult(0);
}
+ bool isInvoke = isInvokeDest();
mlir::Location callLoc = loc;
cir::CIRCallOpInterface theCall =
emitCallLikeOp(*this, loc, indirectFuncTy, indirectFuncVal, directFuncOp,
- cirCallArgs, attrs);
+ cirCallArgs, isInvoke, attrs);
if (callOp)
*callOp = theCall;
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
index 851328a7db680..3550a78cc1816 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp
@@ -147,8 +147,8 @@ void *EHScopeStack::pushCleanup(CleanupKind kind, size_t size) {
assert(!cir::MissingFeatures::innermostEHScope());
- EHCleanupScope *scope = new (buffer)
- EHCleanupScope(size, branchFixups.size(), innermostNormalCleanup);
+ EHCleanupScope *scope = new (buffer) EHCleanupScope(
+ size, branchFixups.size(), innermostNormalCleanup, innermostEHScope);
if (isNormalCleanup)
innermostNormalCleanup = stable_begin();
@@ -188,10 +188,20 @@ void EHScopeStack::popCleanup() {
}
}
+bool EHScopeStack::requiresLandingPad() const {
+ for (stable_iterator si = getInnermostEHScope(); si != stable_end();) {
+ // TODO(cir): Skip lifetime markers.
+ assert(!cir::MissingFeatures::emitLifetimeMarkers());
+ return true;
+ }
+ return false;
+}
+
EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) {
char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers));
- assert(!cir::MissingFeatures::innermostEHScope());
- EHCatchScope *scope = new (buffer) EHCatchScope(numHandlers);
+ EHCatchScope *scope =
+ new (buffer) EHCatchScope(numHandlers, innermostEHScope);
+ innermostEHScope = stable_begin();
return scope;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
index 61a09a59b05c0..4e4e913574991 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCleanup.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h
@@ -30,12 +30,16 @@ struct CatchTypeInfo {
/// A protected scope for zero-cost EH handling.
class EHScope {
+ EHScopeStack::stable_iterator enclosingEHScope;
+
class CommonBitFields {
friend class EHScope;
unsigned kind : 3;
};
enum { NumCommonBits = 3 };
+ bool isScopeMayThrow;
+
protected:
class CatchBitFields {
friend class EHCatchScope;
@@ -79,7 +83,10 @@ class EHScope {
public:
enum Kind { Cleanup, Catch, Terminate, Filter };
- EHScope(Kind kind) { commonBits.kind = kind; }
+ EHScope(Kind kind, EHScopeStack::stable_iterator enclosingEHScope)
+ : enclosingEHScope(enclosingEHScope) {
+ commonBits.kind = kind;
+ }
Kind getKind() const { return static_cast<Kind>(commonBits.kind); }
@@ -87,8 +94,13 @@ class EHScope {
// 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;
+ return isScopeMayThrow;
+ }
+
+ void setMayThrow(bool mayThrow) { isScopeMayThrow = mayThrow; }
+
+ EHScopeStack::stable_iterator getEnclosingEHScope() const {
+ return enclosingEHScope;
}
};
@@ -111,6 +123,8 @@ class EHCatchScope : public EHScope {
/// The catch handler for this type.
mlir::Region *region;
+
+ bool isCatchAll() const { return type.rtti == nullptr; }
};
private:
@@ -118,12 +132,18 @@ class EHCatchScope : public EHScope {
Handler *getHandlers() { return reinterpret_cast<Handler *>(this + 1); }
+ const Handler *getHandlers() const {
+ return reinterpret_cast<const Handler *>(this + 1);
+ }
+
public:
static size_t getSizeForNumHandlers(unsigned n) {
return sizeof(EHCatchScope) + n * sizeof(Handler);
}
- EHCatchScope(unsigned numHandlers) : EHScope(Catch) {
+ EHCatchScope(unsigned numHandlers,
+ EHScopeStack::stable_iterator enclosingEHScope)
+ : EHScope(Catch, enclosingEHScope) {
catchBits.numHandlers = numHandlers;
assert(catchBits.numHandlers == numHandlers && "NumHandlers overflow?");
}
@@ -136,6 +156,11 @@ class EHCatchScope : public EHScope {
getHandlers()[i].region = region;
}
+ const Handler &getHandler(unsigned i) const {
+ assert(i < getNumHandlers());
+ return getHandlers()[i];
+ }
+
// 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
@@ -144,6 +169,10 @@ class EHCatchScope : public EHScope {
// The blocks are owned by TryOp, nothing to delete.
}
+ using iterator = const Handler *;
+ iterator begin() const { return getHandlers(); }
+ iterator end() const { return getHandlers() + getNumHandlers(); }
+
static bool classof(const EHScope *scope) {
return scope->getKind() == Catch;
}
@@ -176,9 +205,10 @@ class alignas(EHScopeStack::ScopeStackAlignment) EHCleanupScope
}
EHCleanupScope(unsigned cleanupSize, unsigned fixupDepth,
- EHScopeStack::stable_iterator enclosingNormal)
- : EHScope(EHScope::Cleanup), enclosingNormal(enclosingNormal),
- fixupDepth(fixupDepth) {
+ EHScopeStack::stable_iterator enclosingNormal,
+ EHScopeStack::stable_iterator enclosingEH)
+ : EHScope(EHScope::Cleanup, enclosingEH),
+ enclosingNormal(enclosingNormal), fixupDepth(fixupDepth) {
// TODO(cir): When exception handling is upstreamed, isNormalCleanup and
// isEHCleanup will be arguments to the constructor.
cleanupBits.isNormalCleanup = true;
@@ -235,13 +265,45 @@ class EHScopeStack::iterator {
EHScope *get() const { return reinterpret_cast<EHScope *>(ptr); }
+ EHScope *operator->() const { return get(); }
EHScope &operator*() const { return *get(); }
+
+ iterator &operator++() {
+ size_t size;
+ switch (get()->getKind()) {
+ case EHScope::Catch:
+ size = EHCatchScope::getSizeForNumHandlers(
+ static_cast<const EHCatchScope *>(get())->getNumHandlers());
+ break;
+
+ case EHScope::Filter:
+ llvm_unreachable("EHScopeStack::iterator Filter");
+ break;
+
+ case EHScope::Cleanup:
+ llvm_unreachable("EHScopeStack::iterator Cleanup");
+ break;
+
+ case EHScope::Terminate:
+ llvm_unreachable("EHScopeStack::iterator Terminate");
+ break;
+ }
+ ptr += llvm::alignTo(size, ScopeStackAlignment);
+ return *this;
+ }
+
+ bool operator==(iterator other) const { return ptr == other.ptr; }
+ bool operator!=(iterator other) const { return ptr != other.ptr; }
};
inline EHScopeStack::iterator EHScopeStack::begin() const {
return iterator(startOfData);
}
+inline EHScopeStack::iterator EHScopeStack::end() const {
+ return iterator(endOfBuffer);
+}
+
inline EHScopeStack::iterator
EHScopeStack::find(stable_iterator savePoint) const {
assert(savePoint.isValid() && "finding invalid savepoint");
@@ -254,7 +316,7 @@ inline void EHScopeStack::popCatch() {
assert(!empty() && "popping exception stack when not empty");
EHCatchScope &scope = llvm::cast<EHCatchScope>(*begin());
- assert(!cir::MissingFeatures::innermostEHScope());
+ innermostEHScope = scope.getEnclosingEHScope();
deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers()));
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp
index 67f46ffde8fda..700e5e0c67c45 100644
--- a/clang/lib/CIR/CodeGen/CIRGenException.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp
@@ -14,6 +14,7 @@
#include "CIRGenFunction.h"
#include "clang/AST/StmtVisitor.h"
+#include "llvm/Support/SaveAndRestore.h"
using namespace clang;
using namespace clang::CIRGen;
@@ -354,6 +355,33 @@ void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &s, cir::TryOp tryOp,
}
}
+/// Emit the structure of the dispatch block for the given catch scope.
+/// It is an invariant that the dispatch block already exists.
+static void emitCatchDispatchBlock(CIRGenFunction &cgf,
+ EHCatchScope &catchScope, cir::TryOp tryOp) {
+ if (EHPersonality::get(cgf).isWasmPersonality()) {
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: WASM personality");
+ return;
+ }
+
+ if (EHPersonality::get(cgf).usesFuncletPads()) {
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: usesFuncletPads");
+ return;
+ }
+
+ assert(catchScope.mayThrow() &&
+ "Expected catchScope that may throw exception");
+
+ // 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()) {
+ return;
+ }
+
+ cgf.cgm.errorNYI("emitCatchDispatchBlock: non-catch all handler");
+}
+
void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
unsigned numHandlers = s.getNumHandlers();
EHCatchScope &catchScope = cast<EHCatchScope>(*ehStack.begin());
@@ -382,5 +410,279 @@ void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &s, bool isFnTryBlock) {
return;
}
- cgm.errorNYI("exitCXXTryStmt: Required catch");
+ // Emit the structure of the EH dispatch for this catch.
+ emitCatchDispatchBlock(*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 =
+ isFnTryBlock && isa<CXXDestructorDecl, 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.
+ if (EHPersonality::get(*this).isWasmPersonality()) {
+ cgm.errorNYI("exitCXXTryStmt: WASM personality");
+ return;
+ }
+
+ bool hasCatchAll = false;
+ for (unsigned i = numHandlers; i != 0; --i) {
+ hasCatchAll |= handlers[i - 1].isCatchAll();
+ mlir::Region *catchRegion = handlers[i - 1].region;
+
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPointToStart(&catchRegion->front());
+
+ const CXXCatchStmt *catchStmt = 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.
+ // TODO: emitBeginCatch
+
+ // Emit the PGO counter increment.
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+
+ // Perform the body of the catch.
+ mlir::LogicalResult emitResult =
+ emitStmt(catchStmt->getHandlerBlock(), /*useCurrentScope=*/true);
+ assert(emitResult.succeeded() && "failed to emit catch handler block");
+
+ // TODO(cir): This yeild should replaced by CatchParamOp once it upstreamed
+ cir::YieldOp::create(builder, tryOp->getLoc());
+
+ // [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) {
+ cgm.errorNYI("exitCXXTryStmt: doImplicitRethrow");
+ return;
+ }
+
+ // 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) {
+ cgm.errorNYI("exitCXXTryStmt: WASM personality without catch all");
+ }
+
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+}
+
+mlir::Operation *CIRGenFunction::emitLandingPad(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:
+ cgm.errorNYI("emitLandingPad: terminate");
+ return {};
+
+ case EHScope::Catch:
+ case EHScope::Cleanup:
+ case EHScope::Filter:
+ // CIR does not cache landing pads.
+ break;
+ }
+
+ // 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).
+ mlir::ArrayAttr handlerTypesAttr = tryOp.getHandlerTypesAttr();
+ if (!handlerTypesAttr || handlerTypesAttr.empty()) {
+ // Accumulate all the handlers in scope.
+ bool hasCatchAll = false;
+ llvm::SmallVector<mlir::Attribute, 4> handlerAttrs;
+ for (EHScopeStack::iterator i = ehStack.begin(), e = ehStack.end(); i != e;
+ ++i) {
+ switch (i->getKind()) {
+ case EHScope::Cleanup: {
+ cgm.errorNYI("emitLandingPad: Cleanup");
+ return {};
+ }
+
+ case EHScope::Filter: {
+ cgm.errorNYI("emitLandingPad: Filter");
+ return {};
+ }
+
+ case EHScope::Terminate: {
+ cgm.errorNYI("emitLandingPad: Terminate");
+ return {};
+ }
+
+ case EHScope::Catch:
+ break;
+ }
+
+ EHCatchScope &catchScope = cast<EHCatchScope>(*i);
+ for (unsigned handlerIdx = 0, he = catchScope.getNumHandlers();
+ handlerIdx != he; ++handlerIdx) {
+ EHCatchScope::Handler handler = catchScope.getHandler(handlerIdx);
+ assert(handler.type.flags == 0 &&
+ "landingpads do not support catch handler flags");
+
+ // If this is a catch-all, register that and abort.
+ if (handler.isCatchAll()) {
+ assert(!hasCatchAll);
+ hasCatchAll = true;
+ goto done;
+ }
+
+ cgm.errorNYI("emitLandingPad: non catch-all");
+ return {};
+ }
+
+ goto done;
+ }
+
+ done:
+ if (hasCatchAll) {
+ handlerAttrs.push_back(cir::CatchAllAttr::get(&getMLIRContext()));
+ } else {
+ cgm.errorNYI("emitLandingPad: non catch-all");
+ return {};
+ }
+
+ // Add final array of clauses into TryOp.
+ tryOp.setHandlerTypesAttr(
+ mlir::ArrayAttr::get(&getMLIRContext(), handlerAttrs));
+ }
+
+ // In traditional LLVM codegen. this tells the backend how to generate the
+ // landing pad by generating a branch to the dispatch block. In CIR,
+ // getEHDispatchBlock is used to populate blocks for later filing during
+ // cleanup handling.
+ (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 scope,
+ cir::TryOp tryOp) {
+ if (EHPersonality::get(*this).usesFuncletPads()) {
+ cgm.errorNYI("getEHDispatchBlock: usesFuncletPads");
+ return {};
+ }
+
+ // Otherwise, we should look at the actual scope.
+ EHScope &ehScope = *ehStack.find(scope);
+ bool mayThrow = ehScope.mayThrow();
+
+ mlir::Block *originalBlock = nullptr;
+ if (mayThrow && tryOp) {
+ // If the dispatch is cached but comes from a different tryOp, make sure:
+ // - Populate current `tryOp` with a new dispatch block regardless.
+ // - Update the map to enqueue new dispatchBlock to also get a cleanup. See
+ // code at the end of the function.
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & tryOp");
+ return {};
+ }
+
+ if (!mayThrow) {
+ switch (ehScope.getKind()) {
+ case EHScope::Catch: {
+ // LLVM does some optimization with branches here, CIR just keep track of
+ // the corresponding calls.
+ EHCatchScope &catchScope = cast<EHCatchScope>(ehScope);
+ if (catchScope.getNumHandlers() == 1 &&
+ catchScope.getHandler(0).isCatchAll()) {
+ mayThrow = true;
+ break;
+ }
+ cgm.errorNYI("getEHDispatchBlock: mayThrow non-catch all");
+ return {};
+ }
+ case EHScope::Cleanup: {
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & cleanup");
+ return {};
+ }
+ case EHScope::Filter: {
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & Filter");
+ return {};
+ }
+ case EHScope::Terminate: {
+ cgm.errorNYI("getEHDispatchBlock: mayThrow & Terminate");
+ return {};
+ }
+ }
+ }
+
+ if (originalBlock) {
+ cgm.errorNYI("getEHDispatchBlock: originalBlock");
+ return {};
+ }
+
+ ehScope.setMayThrow(mayThrow);
+ return {};
+}
+
+bool CIRGenFunction::isInvokeDest() {
+ if (!ehStack.requiresLandingPad())
+ return false;
+
+ // 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) {
+ cgm.errorNYI("isInvokeDest: no exceptions or ignore exception");
+ return false;
+ }
+
+ // CUDA device code doesn't have exceptions.
+ if (lo.CUDA && lo.CUDAIsDevice)
+ return false;
+
+ return true;
+}
+
+mlir::Operation *CIRGenFunction::getInvokeDes...
[truncated]
|
|
Notes:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please split this off into a NFC PR that only introduces the NYI skeleton?
Some of the NYI will not be possible to merge before this PR because they require other features. What I can do is to move the Cleanup and EHScope changes to NFC, and once we merge this PR, I can open another NFC to add the NYI back if that's okay |
Upstream support for try block and catch all block with a function call that may throw an exception.
Issue #154992