1
0
mirror of https://github.com/RPCS3/llvm-mirror.git synced 2024-11-25 04:02:41 +01:00

[WebAssembly] Disable wasm.lsda() optimization in WasmEHPrepare

In every catchpad except `catch (...)`, we add a call to
`_Unwind_CallPersonality`, which is a wapper to call the personality
function. (In most of other Itanium-based architectures the call is done
from libunwind, but in wasm we don't have the control over the VM.)
Because the personatlity function is called to figure out whether the
current exception is a type we should catch, such as `int` or
`SomeClass&`, `catch (...)` does not need the personality function call.
For the same reason, all cleanuppads don't need it.

When we call `_Unwind_CallPersonality`, we store some necessary info in
a data structure called `__wasm_lpad_context` of type
`_Unwind_LandingPadContext`, which is defined  in the wasm's port of
libunwind in Emscripten. Also the personality wrapper function returns
some info (selector and the caught pointer) in that data structure, so
it is used as a medium for communication.

One of the info we need to store is the address for LSDA info for the
current function. `wasm.lsda()` intrinsic returns that address. (This
intrinsic will be lowered to a symbol that points to the LSDA address.)
The simpliest thing is call `wasm.lsda()` every time we need to call
`_Unwind_CallPersonality` and store that info in `__wasm_lpad_context`
data structure. But we tried to be better than that (D77423 and some
more previous CLs), so if catchpad A dominates catchpad B and catchpad A
is not `catch (...)`, we didn't insert `wasm.lsda()` call in catchpad B,
thinking that the LSDA address is the same for a single function and we
already visited catchpad A and `__wasm_lpad_context.lsda` field would
already have that value.

But this can be incorrect if there is a call to another function, which
also can have the personality function and LSDA, between catchpad A and
catchpad B, because `__wasm_lpad_context` is a globally defined
structure and the callee function will overwrite its `lsda` field.

So in this CL we don't try to do any optimizaions on adding
`wasm.lsda()` call; we store the result of `wasm.lsda()` every time we
call `_Unwind_CallPersonality`. We can do some complicated analysis,
like checking if there is a function call between the dominating
catchpad and the current catchpad, but at this time it seems overkill.

This deletes three tests because they all tested `wasm.ldsa()` call
optimization.

Fixes https://github.com/emscripten-core/emscripten/issues/13548.

Reviewed By: tlively

Differential Revision: https://reviews.llvm.org/D97309
This commit is contained in:
Heejin Ahn 2021-02-23 11:00:11 -08:00
parent 9c426fd3a4
commit bf4a14e46a
2 changed files with 49 additions and 403 deletions

View File

@ -77,7 +77,6 @@
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/BreadthFirstIterator.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/Triple.h"
@ -117,13 +116,9 @@ class WasmEHPrepare : public FunctionPass {
FunctionCallee CallPersonalityF =
nullptr; // _Unwind_CallPersonality() wrapper
bool prepareEHPads(Function &F);
bool prepareThrows(Function &F);
bool IsEHPadFunctionsSetUp = false;
void setupEHPadFunctions(Function &F);
void prepareEHPad(BasicBlock *BB, bool NeedPersonality, bool NeedLSDA = false,
unsigned Index = 0);
bool prepareEHPads(Function &F);
void prepareEHPad(BasicBlock *BB, bool NeedPersonality, unsigned Index = 0);
public:
static char ID; // Pass identification, replacement for typeid
@ -176,7 +171,6 @@ static void eraseDeadBBsAndChildren(const Container &BBs, DomTreeUpdater *DTU) {
}
bool WasmEHPrepare::runOnFunction(Function &F) {
IsEHPadFunctionsSetUp = false;
bool Changed = false;
Changed |= prepareThrows(F);
Changed |= prepareEHPads(F);
@ -216,95 +210,23 @@ bool WasmEHPrepare::prepareThrows(Function &F) {
}
bool WasmEHPrepare::prepareEHPads(Function &F) {
auto &DT = getAnalysis<DominatorTreeWrapperPass>().getDomTree();
bool Changed = false;
// There are two things to decide: whether we need a personality function call
// and whether we need a `wasm.lsda()` call and its store.
//
// For the personality function call, catchpads with `catch (...)` and
// cleanuppads don't need it, because exceptions are always caught. Others all
// need it.
//
// For `wasm.lsda()` and its store, in order to minimize the number of them,
// we need a way to figure out whether we have encountered `wasm.lsda()` call
// in any of EH pads that dominates the current EH pad. To figure that out, we
// now visit EH pads in BFS order in the dominator tree so that we visit
// parent BBs first before visiting its child BBs in the domtree.
//
// We keep a set named `ExecutedLSDA`, which basically means "Do we have
// `wasm.lsda() either in the current EH pad or any of its parent EH pads in
// the dominator tree?". This is to prevent scanning the domtree up to the
// root every time we examine an EH pad, in the worst case: each EH pad only
// needs to check its immediate parent EH pad.
//
// - If any of its parent EH pads in the domtree has `wasm.lsda`, this means
// we don't need `wasm.lsda()` in the current EH pad. We also insert the
// current EH pad in `ExecutedLSDA` set.
// - If none of its parent EH pad has `wasm.lsda()`,
// - If the current EH pad is a `catch (...)` or a cleanuppad, done.
// - If the current EH pad is neither a `catch (...)` nor a cleanuppad,
// add `wasm.lsda()` and the store in the current EH pad, and add the
// current EH pad to `ExecutedLSDA` set.
//
// TODO Can we not store LSDA address in user function but make libcxxabi
// compute it?
DenseSet<Value *> ExecutedLSDA;
unsigned Index = 0;
for (auto DomNode : breadth_first(&DT)) {
auto *BB = DomNode->getBlock();
auto *Pad = BB->getFirstNonPHI();
if (!Pad || (!isa<CatchPadInst>(Pad) && !isa<CleanupPadInst>(Pad)))
continue;
Changed = true;
Value *ParentPad = nullptr;
if (CatchPadInst *CPI = dyn_cast<CatchPadInst>(Pad)) {
ParentPad = CPI->getCatchSwitch()->getParentPad();
if (ExecutedLSDA.count(ParentPad)) {
ExecutedLSDA.insert(CPI);
// We insert its associated catchswitch too, because
// FuncletPadInst::getParentPad() returns a CatchSwitchInst if the child
// FuncletPadInst is a CleanupPadInst.
ExecutedLSDA.insert(CPI->getCatchSwitch());
}
} else { // CleanupPadInst
ParentPad = cast<CleanupPadInst>(Pad)->getParentPad();
if (ExecutedLSDA.count(ParentPad))
ExecutedLSDA.insert(Pad);
}
if (CatchPadInst *CPI = dyn_cast<CatchPadInst>(Pad)) {
if (CPI->getNumArgOperands() == 1 &&
cast<Constant>(CPI->getArgOperand(0))->isNullValue())
// In case of a single catch (...), we need neither personality call nor
// wasm.lsda() call
prepareEHPad(BB, false);
else {
if (ExecutedLSDA.count(CPI))
// catch (type), but one of parents already has wasm.lsda() call
prepareEHPad(BB, true, false, Index++);
else {
// catch (type), and none of parents has wasm.lsda() call. We have to
// add the call in this EH pad, and record this EH pad in
// ExecutedLSDA.
ExecutedLSDA.insert(CPI);
ExecutedLSDA.insert(CPI->getCatchSwitch());
prepareEHPad(BB, true, true, Index++);
}
}
} else if (isa<CleanupPadInst>(Pad)) {
// Cleanup pads need neither personality call nor wasm.lsda() call
prepareEHPad(BB, false);
}
}
return Changed;
}
void WasmEHPrepare::setupEHPadFunctions(Function &F) {
Module &M = *F.getParent();
IRBuilder<> IRB(F.getContext());
SmallVector<BasicBlock *, 16> CatchPads;
SmallVector<BasicBlock *, 16> CleanupPads;
for (BasicBlock &BB : F) {
if (!BB.isEHPad())
continue;
auto *Pad = BB.getFirstNonPHI();
if (isa<CatchPadInst>(Pad))
CatchPads.push_back(&BB);
else if (isa<CleanupPadInst>(Pad))
CleanupPads.push_back(&BB);
}
if (CatchPads.empty() && CleanupPads.empty())
return false;
assert(F.hasPersonalityFn() && "Personality function not found");
// __wasm_lpad_context global variable
@ -336,16 +258,30 @@ void WasmEHPrepare::setupEHPadFunctions(Function &F) {
"_Unwind_CallPersonality", IRB.getInt32Ty(), IRB.getInt8PtrTy());
if (Function *F = dyn_cast<Function>(CallPersonalityF.getCallee()))
F->setDoesNotThrow();
unsigned Index = 0;
for (auto *BB : CatchPads) {
auto *CPI = cast<CatchPadInst>(BB->getFirstNonPHI());
// In case of a single catch (...), we don't need to emit a personalify
// function call
if (CPI->getNumArgOperands() == 1 &&
cast<Constant>(CPI->getArgOperand(0))->isNullValue())
prepareEHPad(BB, false);
else
prepareEHPad(BB, true, Index++);
}
// Cleanup pads don't need a personality function call.
for (auto *BB : CleanupPads)
prepareEHPad(BB, false);
return true;
}
// Prepare an EH pad for Wasm EH handling. If NeedPersonality is false, Index is
// ignored.
void WasmEHPrepare::prepareEHPad(BasicBlock *BB, bool NeedPersonality,
bool NeedLSDA, unsigned Index) {
if (!IsEHPadFunctionsSetUp) {
IsEHPadFunctionsSetUp = true;
setupEHPadFunctions(*BB->getParent());
}
unsigned Index) {
assert(BB->isEHPad() && "BB is not an EHPad!");
IRBuilder<> IRB(BB->getContext());
IRB.SetInsertPoint(&*BB->getFirstInsertionPt());
@ -399,9 +335,11 @@ void WasmEHPrepare::prepareEHPad(BasicBlock *BB, bool NeedPersonality,
IRB.CreateStore(IRB.getInt32(Index), LPadIndexField);
auto *CPI = cast<CatchPadInst>(FPI);
if (NeedLSDA)
// Pseudocode: __wasm_lpad_context.lsda = wasm.lsda();
IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);
// TODO Sometimes storing the LSDA address every time is not necessary, in
// case it is already set in a dominating EH pad and there is no function call
// between from that EH pad to here. Consider optimizing those cases.
// Pseudocode: __wasm_lpad_context.lsda = wasm.lsda();
IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField);
// Pseudocode: _Unwind_CallPersonality(exn);
CallInst *PersCI = IRB.CreateCall(CallPersonalityF, CatchCI,

View File

@ -132,310 +132,18 @@ try.cont7: ; preds = %try.cont, %catch4
ret void
}
; A nested try-catch within a catch. The outer catch catches 'int'.
;
; void foo();
; void test2() {
; try {
; foo();
; } catch (int) {
; try {
; foo();
; } catch (int) {
; }
; }
; }
; Within the nested catchpad, wasm.lsda() call should NOT be generated.
define void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test2()
entry:
invoke void @foo()
to label %try.cont9 unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind to caller
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [i8* bitcast (i8** @_ZTIi to i8*)]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*))
%matches = icmp eq i32 %3, %4
br i1 %matches, label %catch, label %rethrow
; CHECK: catch.start:
; CHECK: call i8* @llvm.wasm.lsda()
catch: ; preds = %catch.start
%5 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ]
invoke void @foo() [ "funclet"(token %1) ]
to label %try.cont unwind label %catch.dispatch2
catch.dispatch2: ; preds = %catch
%6 = catchswitch within %1 [label %catch.start3] unwind label %ehcleanup
catch.start3: ; preds = %catch.dispatch2
%7 = catchpad within %6 [i8* bitcast (i8** @_ZTIi to i8*)]
%8 = call i8* @llvm.wasm.get.exception(token %7)
%9 = call i32 @llvm.wasm.get.ehselector(token %7)
%10 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*))
%matches4 = icmp eq i32 %9, %10
br i1 %matches4, label %catch6, label %rethrow5
; CHECK: catch.start3:
; CHECK-NOT: call i8* @llvm.wasm.lsda()
catch6: ; preds = %catch.start3
%11 = call i8* @__cxa_begin_catch(i8* %8) [ "funclet"(token %7) ]
call void @__cxa_end_catch() [ "funclet"(token %7) ]
catchret from %7 to label %try.cont
rethrow5: ; preds = %catch.start3
invoke void @llvm.wasm.rethrow() [ "funclet"(token %7) ]
to label %unreachable unwind label %ehcleanup
try.cont: ; preds = %catch, %catch6
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont9
rethrow: ; preds = %catch.start
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
unreachable
try.cont9: ; preds = %entry, %try.cont
ret void
ehcleanup: ; preds = %rethrow5, %catch.dispatch2
%12 = cleanuppad within %1 []
call void @__cxa_end_catch() [ "funclet"(token %12) ]
cleanupret from %12 unwind to caller
; CHECK: ehcleanup:
; CHECK-NEXT: cleanuppad
; CHECK-NOT: call void @llvm.wasm.landingpad.index
; CHECK-NOT: store {{.*}} @__wasm_lpad_context
; CHECK-NOT: call i8* @llvm.wasm.lsda()
; CHECK-NOT: call i32 @_Unwind_CallPersonality
; CHECK-NOT: load {{.*}} @__wasm_lpad_context
unreachable: ; preds = %rethrow5
unreachable
}
; A nested try-catch within a catch. The outer catch is (...).
;
; void foo();
; void test2() {
; try {
; foo();
; } catch (...) {
; try {
; foo();
; } catch (int) {
; }
; }
; }
; Within the innermost catchpad, wasm.lsda() call should be generated, because
; the outer catch is 'catch (...)', which does not need wasm.lsda() call.
define void @test3() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test3()
entry:
invoke void @foo()
to label %try.cont8 unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind to caller
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [i8* null]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ]
invoke void @foo() [ "funclet"(token %1) ]
to label %try.cont unwind label %catch.dispatch2
; CHECK: catch.start:
; CHECK-NOT: call i8* @llvm.wasm.lsda()
catch.dispatch2: ; preds = %catch.start
%5 = catchswitch within %1 [label %catch.start3] unwind label %ehcleanup
catch.start3: ; preds = %catch.dispatch2
%6 = catchpad within %5 [i8* bitcast (i8** @_ZTIi to i8*)]
%7 = call i8* @llvm.wasm.get.exception(token %6)
%8 = call i32 @llvm.wasm.get.ehselector(token %6)
%9 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*))
%matches = icmp eq i32 %8, %9
br i1 %matches, label %catch4, label %rethrow
; CHECK: catch.start3:
; CHECK: call i8* @llvm.wasm.lsda()
catch4: ; preds = %catch.start3
%10 = call i8* @__cxa_begin_catch(i8* %7) [ "funclet"(token %6) ]
%11 = bitcast i8* %10 to i32*
%12 = load i32, i32* %11, align 4
call void @__cxa_end_catch() [ "funclet"(token %6) ]
catchret from %6 to label %try.cont
rethrow: ; preds = %catch.start3
invoke void @llvm.wasm.rethrow() [ "funclet"(token %6) ]
to label %unreachable unwind label %ehcleanup
try.cont: ; preds = %catch.start, %catch4
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont8
try.cont8: ; preds = %entry, %try.cont
ret void
ehcleanup: ; preds = %rethrow, %catch.dispatch2
%13 = cleanuppad within %1 []
invoke void @__cxa_end_catch() [ "funclet"(token %13) ]
to label %invoke.cont6 unwind label %terminate
invoke.cont6: ; preds = %ehcleanup
cleanupret from %13 unwind to caller
unreachable: ; preds = %rethrow
unreachable
terminate: ; preds = %ehcleanup
%14 = cleanuppad within %13 []
%15 = call i8* @llvm.wasm.get.exception(token %14)
call void @__clang_call_terminate(i8* %15) [ "funclet"(token %14) ]
unreachable
}
; void foo();
; void test4() {
; try {
; foo();
; } catch (int) {
; try {
; foo();
; } catch (...) {
; try {
; foo();
; } catch (int) {
; }
; }
; }
; }
; wasm.lsda() call should be generated only once in the outermost catchpad. The
; innermost 'catch (int)' can reuse the wasm.lsda() generated in the outermost
; catch.
define void @test4() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test4()
entry:
invoke void @foo()
to label %try.cont19 unwind label %catch.dispatch
catch.dispatch: ; preds = %entry
%0 = catchswitch within none [label %catch.start] unwind to caller
catch.start: ; preds = %catch.dispatch
%1 = catchpad within %0 [i8* bitcast (i8** @_ZTIi to i8*)]
%2 = call i8* @llvm.wasm.get.exception(token %1)
%3 = call i32 @llvm.wasm.get.ehselector(token %1)
%4 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*))
%matches = icmp eq i32 %3, %4
br i1 %matches, label %catch, label %rethrow
; CHECK: catch.start:
; CHECK: call i8* @llvm.wasm.lsda()
catch: ; preds = %catch.start
%5 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ]
%6 = bitcast i8* %5 to i32*
%7 = load i32, i32* %6, align 4
invoke void @foo() [ "funclet"(token %1) ]
to label %try.cont16 unwind label %catch.dispatch2
catch.dispatch2: ; preds = %catch
%8 = catchswitch within %1 [label %catch.start3] unwind label %ehcleanup17
catch.start3: ; preds = %catch.dispatch2
%9 = catchpad within %8 [i8* null]
%10 = call i8* @llvm.wasm.get.exception(token %9)
%11 = call i32 @llvm.wasm.get.ehselector(token %9)
%12 = call i8* @__cxa_begin_catch(i8* %10) [ "funclet"(token %9) ]
invoke void @foo() [ "funclet"(token %9) ]
to label %try.cont unwind label %catch.dispatch7
; CHECK: catch.start3:
; CHECK-NOT: call i8* @llvm.wasm.lsda()
catch.dispatch7: ; preds = %catch.start3
%13 = catchswitch within %9 [label %catch.start8] unwind label %ehcleanup
catch.start8: ; preds = %catch.dispatch7
%14 = catchpad within %13 [i8* bitcast (i8** @_ZTIi to i8*)]
%15 = call i8* @llvm.wasm.get.exception(token %14)
%16 = call i32 @llvm.wasm.get.ehselector(token %14)
%17 = call i32 @llvm.eh.typeid.for(i8* bitcast (i8** @_ZTIi to i8*))
%matches9 = icmp eq i32 %16, %17
br i1 %matches9, label %catch11, label %rethrow10
; CHECK: catch.start8:
; CHECK-NOT: call i8* @llvm.wasm.lsda()
catch11: ; preds = %catch.start8
%18 = call i8* @__cxa_begin_catch(i8* %15) [ "funclet"(token %14) ]
%19 = bitcast i8* %18 to i32*
%20 = load i32, i32* %19, align 4
call void @__cxa_end_catch() [ "funclet"(token %14) ]
catchret from %14 to label %try.cont
rethrow10: ; preds = %catch.start8
invoke void @llvm.wasm.rethrow() [ "funclet"(token %14) ]
to label %unreachable unwind label %ehcleanup
try.cont: ; preds = %catch.start3, %catch11
invoke void @__cxa_end_catch() [ "funclet"(token %9) ]
to label %invoke.cont13 unwind label %ehcleanup17
invoke.cont13: ; preds = %try.cont
catchret from %9 to label %try.cont16
try.cont16: ; preds = %catch, %invoke.cont13
call void @__cxa_end_catch() [ "funclet"(token %1) ]
catchret from %1 to label %try.cont19
rethrow: ; preds = %catch.start
call void @llvm.wasm.rethrow() [ "funclet"(token %1) ]
unreachable
try.cont19: ; preds = %entry, %try.cont16
ret void
ehcleanup: ; preds = %rethrow10, %catch.dispatch7
%21 = cleanuppad within %9 []
invoke void @__cxa_end_catch() [ "funclet"(token %21) ]
to label %invoke.cont14 unwind label %terminate
invoke.cont14: ; preds = %ehcleanup
cleanupret from %21 unwind label %ehcleanup17
ehcleanup17: ; preds = %invoke.cont14, %try.cont, %catch.dispatch2
%22 = cleanuppad within %1 []
call void @__cxa_end_catch() [ "funclet"(token %22) ]
cleanupret from %22 unwind to caller
unreachable: ; preds = %rethrow10
unreachable
terminate: ; preds = %ehcleanup
%23 = cleanuppad within %21 []
%24 = call i8* @llvm.wasm.get.exception(token %23)
call void @__clang_call_terminate(i8* %24) [ "funclet"(token %23) ]
unreachable
}
; A cleanuppad with a call to __clang_call_terminate().
;
; void foo();
; void test5() {
; void test2() {
; try {
; foo();
; } catch (...) {
; foo();
; }
; }
define void @test5() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test5
define void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test2
entry:
invoke void @foo()
to label %try.cont unwind label %catch.dispatch
@ -486,7 +194,7 @@ terminate: ; preds = %ehcleanup
; ~Temp() {}
; };
;
; void test6() {
; void test3() {
; int num;
; try {
; Temp t;
@ -506,8 +214,8 @@ terminate: ; preds = %ehcleanup
; bar(num);
; }
; }
define void @test6() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test6
define void @test3() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @test3
entry:
%t = alloca %struct.Temp, align 1
invoke void @foo()
@ -571,8 +279,8 @@ try.cont10: ; preds = %invoke.cont3, %catc
; Tests if instructions after a call to @llvm.wasm.throw are deleted and the
; BB's dead children are deleted.
; CHECK-LABEL: @test7
define i32 @test7(i1 %b, i8* %p) {
; CHECK-LABEL: @test4
define i32 @test4(i1 %b, i8* %p) {
entry:
br i1 %b, label %bb.true, label %bb.false