Skip to content
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

[Exception] landing pad like logic is NYI and some related question to exception handling #1183

Open
ChuanqiXu9 opened this issue Nov 28, 2024 · 2 comments
Assignees

Comments

@ChuanqiXu9
Copy link
Member

Reproducer:

class Error {
public:
  Error(const char *) noexcept;
};
void test()
{
  struct Guard {
    ~Guard() {}
  } __guard;

  throw Error("testing");
}

it will trigger

assert(!CGF.isInvokeDest() && "landing pad like logic NYI");

In the traditional pipeline, we would generate an invoke and one to unreachable BB and one to the invoke dest BB.

I tried to take a look. But the logics about creating and handling for invoke dest BB looks pretty much more different than the traditional pipeline.

e.g., in clangir, the methods to getInvokeDestImpl and emitLandingPad will take a tryOp argument

mlir::Operation *emitLandingPad(cir::TryOp tryOp);
void emitEHResumeBlock(bool isCleanup, mlir::Block *ehResumeBlock,
mlir::Location loc);
mlir::Block *getEHResumeBlock(bool isCleanup, cir::TryOp tryOp);
mlir::Block *getEHDispatchBlock(EHScopeStack::stable_iterator scope,
cir::TryOp tryOp);
/// Unified block containing a call to cir.resume
mlir::Block *ehResumeBlock = nullptr;
llvm::DenseMap<mlir::Block *, mlir::Block *> cleanupsToPatch;
/// The cleanup depth enclosing all the cleanups associated with the
/// parameters.
EHScopeStack::stable_iterator PrologueCleanupDepth;
mlir::Operation *getInvokeDestImpl(cir::TryOp tryOp);
mlir::Operation *getInvokeDest(cir::TryOp tryOp) {

while it is not the case in traditional pipeline

llvm::BasicBlock *EmitLandingPad();
llvm::BasicBlock *getInvokeDestImpl();

And also the return type in traditional pipeline is Block but the return type in CIR is Operation.

Other example including CIRGenFunction::getEHDispatchBlock, where the implementation in traditional pipeline looks much simpler:

case EHScope::Cleanup:
dispatchBlock = createBasicBlock("ehcleanup");
break;
case EHScope::Filter:
dispatchBlock = createBasicBlock("filter.dispatch");
break;
case EHScope::Terminate:
dispatchBlock = getTerminateHandler();
break;
}

than in CIR:

case EHScope::Catch: {
// LLVM does some optimization with branches here, CIR just keep track of
// the corresponding calls.
assert(callWithExceptionCtx && "expected call information");
{
mlir::OpBuilder::InsertionGuard guard(getBuilder());
assert(callWithExceptionCtx.getCleanup().empty() &&
"one per call: expected empty region at this point");
dispatchBlock = builder.createBlock(&callWithExceptionCtx.getCleanup());
builder.createYield(callWithExceptionCtx.getLoc());
}
break;
}
case EHScope::Cleanup: {
if (callWithExceptionCtx && "expected call information") {
mlir::OpBuilder::InsertionGuard guard(getBuilder());
assert(callWithExceptionCtx.getCleanup().empty() &&
"one per call: expected empty region at this point");
dispatchBlock = builder.createBlock(&callWithExceptionCtx.getCleanup());
builder.createYield(callWithExceptionCtx.getLoc());
} else {
// Usually coming from general cir.scope cleanups that aren't
// tried to a specific throwing call.
assert(currLexScope && currLexScope->isRegular() &&
"expected regular cleanup");
dispatchBlock = currLexScope->getOrCreateCleanupBlock(builder);
if (dispatchBlock->empty()) {
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToEnd(dispatchBlock);
mlir::Location loc =
currSrcLoc ? *currSrcLoc : builder.getUnknownLoc();
builder.createYield(loc);
}
}
break;
}


So I am pretty confused and have some questions.

  1. What is the design for supporting exceptions in CIR and what is the core design ideas?

Since I noticed the difference in supporting exceptions in CIR are traditional pipeline are much more different than other places I know. I guess there are some internal reasons and I want to know our plan for it.

  1. Can we move these handling for exceptions to later passes?

Given the existing implementation already differs from the traditional pipeline, I feel may be it will be good to move these handlings to later passes. It is helpful to make the CIRGen smaller and helpful for the analysis purpose. e.g., the generated CIR code may look like the codes in the frontend more.

@smeenai
Copy link
Collaborator

smeenai commented Dec 2, 2024

The general idea is to have a structured representation for exceptions and cleanups to whatever extent possible, and we end up deviating from CodeGen quite a bit here because they rely on unstructured control flow heavily. Simple code with exceptions works and should give a high-level sense of the IR design, e.g. https://godbolt.org/z/3afooPrGK. @bcardosolopes will of course be able to say much more on this.

You may be interested in #1123, where we're discussing the design of cleanups. What did you have in mind for moving the handling of exceptions to later passes?

@bcardosolopes
Copy link
Member

@ChuanqiXu9 we are heavily working on fixing both cleanups and exceptions in the next couple months, we'll also have some documentation on the design, stay tunned

@bcardosolopes bcardosolopes self-assigned this Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants