[Attributor][Fix] Keep invokes if handlers catch asynchronous exceptions

Similar to other places where we transform invokes to calls we need to
be careful if the handler (=personality) can catch asynchronous
exceptions as they are not modeled as part of nounwind.

This is tested with D59978.

llvm-svn: 367931
This commit is contained in:
Johannes Doerfert 2019-08-05 21:34:45 +00:00
parent d131713307
commit 924d2138fc
1 changed files with 31 additions and 9 deletions

View File

@ -22,6 +22,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Analysis/CaptureTracking.h"
#include "llvm/Analysis/EHPersonalities.h"
#include "llvm/Analysis/GlobalsModRef.h"
#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/ValueTracking.h"
@ -1563,6 +1564,12 @@ struct AAIsDeadFunction : AAIsDead, BooleanState {
"Attempted to manifest an invalid state!");
ChangeStatus HasChanged = ChangeStatus::UNCHANGED;
const Function &F = getAnchorScope();
// Flag to determine if we can change an invoke to a call assuming the callee
// is nounwind. This is not possible if the personality of the function allows
// to catch asynchronous exceptions.
bool Invoke2CallAllowed = !mayCatchAsynchronousExceptions(F);
for (const Instruction *NRC : NoReturnCalls) {
Instruction *I = const_cast<Instruction *>(NRC);
@ -1573,16 +1580,19 @@ struct AAIsDeadFunction : AAIsDead, BooleanState {
/// Invoke is replaced with a call and unreachable is placed after it if
/// the callee is nounwind and noreturn. Otherwise, we keep the invoke
/// and only place an unreachable in the normal successor.
if (Invoke2CallAllowed) {
if (Function *Callee = II->getCalledFunction()) {
auto *AANoUnw = A.getAAFor<AANoUnwind>(*this, *Callee);
if (Callee->hasFnAttribute(Attribute::NoUnwind) ||
(AANoUnw && AANoUnw->isAssumedNoUnwind())) {
LLVM_DEBUG(dbgs() << "[AAIsDead] Replace invoke with call inst\n");
LLVM_DEBUG(dbgs()
<< "[AAIsDead] Replace invoke with call inst\n");
changeToCall(II);
changeToUnreachable(BB->getTerminator(), /* UseLLVMTrap */ false);
continue;
}
}
}
BB = II->getNormalDest();
SplitPos = &BB->front();
@ -1639,6 +1649,11 @@ struct AAIsDeadFunction : AAIsDead, BooleanState {
/// Check if instruction is after noreturn call, in other words, assumed dead.
bool isAfterNoReturn(const Instruction *I) const;
/// Determine if \p F might catch asynchronous exceptions.
static bool mayCatchAsynchronousExceptions(const Function &F) {
return F.hasPersonalityFn() && !canSimplifyInvokeNoUnwind(&F);
}
/// Collection of to be explored paths.
SmallSetVector<const Instruction *, 8> ToBeExploredPaths;
@ -1662,6 +1677,12 @@ bool AAIsDeadFunction::isAfterNoReturn(const Instruction *I) const {
const Instruction *AAIsDeadFunction::findNextNoReturn(Attributor &A,
const Instruction *I) {
const BasicBlock *BB = I->getParent();
const Function &F = *BB->getParent();
// Flag to determine if we can change an invoke to a call assuming the callee
// is nounwind. This is not possible if the personality of the function allows
// to catch asynchronous exceptions.
bool Invoke2CallAllowed = !mayCatchAsynchronousExceptions(F);
// TODO: We should have a function that determines if an "edge" is dead.
// Edges could be from an instruction to the next or from a terminator
@ -1678,7 +1699,8 @@ const Instruction *AAIsDeadFunction::findNextNoReturn(Attributor &A,
if (auto *Invoke = dyn_cast<InvokeInst>(I)) {
// Use nounwind to justify the unwind block is dead as well.
auto *AANoUnw = A.getAAFor<AANoUnwind>(*this, *Invoke);
if (!AANoUnw || !AANoUnw->isAssumedNoUnwind()) {
if (!Invoke2CallAllowed ||
(!AANoUnw || !AANoUnw->isAssumedNoUnwind())) {
AssumedLiveBlocks.insert(Invoke->getUnwindDest());
ToBeExploredPaths.insert(&Invoke->getUnwindDest()->front());
}