Skip to content

Commit

Permalink
[WebAssembly] Nullify unnecessary setjmp invokes
Browse files Browse the repository at this point in the history
This is similar to D116619, but now it handles `invoke`s. The reason we
didn't handle `invoke`s back then was we didn't support Wasm EH + Wasm
SjLj together, and the only case SjLj transformation will see `invoke`s
is when we are using Wasm EH. (In Emscripten EH, they would have been
transformed to `call`s to invoke wrappers.)

But after D117610 we support Wasm EH + Wasm SjLj together and we can
nullify `invoke`s to `setjmp` when there is no other longjmpable calls
within the function. Actually this is very unlikely to happen in
practice, because we treat destructors as longjmpable and also treat
`__cxa_end_catch` as longjmpable even if it is not.

Reviewed By: dschuff

Differential Revision: https://reviews.llvm.org/D118408
  • Loading branch information
aheejin committed Jan 28, 2022
1 parent 20c1d9c commit 4f1244d
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 7 deletions.
16 changes: 9 additions & 7 deletions llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
Expand Up @@ -874,15 +874,17 @@ static void nullifySetjmp(Function *F) {
Function *SetjmpF = M.getFunction("setjmp");
SmallVector<Instruction *, 1> ToErase;

for (User *U : SetjmpF->users()) {
auto *CI = dyn_cast<CallInst>(U);
// FIXME 'invoke' to setjmp can happen when we use Wasm EH + Wasm SjLj, but
// we don't support two being used together yet.
if (!CI)
report_fatal_error("Wasm EH + Wasm SjLj is not fully supported yet");
BasicBlock *BB = CI->getParent();
for (User *U : make_early_inc_range(SetjmpF->users())) {
auto *CB = dyn_cast<CallBase>(U);
BasicBlock *BB = CB->getParent();
if (BB->getParent() != F) // in other function
continue;
CallInst *CI = nullptr;
// setjmp cannot throw. So if it is an invoke, lower it to a call
if (auto *II = dyn_cast<InvokeInst>(CB))
CI = llvm::changeToCall(II);
else
CI = cast<CallInst>(CB);
ToErase.push_back(CI);
CI->replaceAllUsesWith(IRB.getInt32(0));
}
Expand Down
3 changes: 3 additions & 0 deletions llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll
Expand Up @@ -140,10 +140,13 @@ entry:
; free cannot longjmp
call void @free(i8* %ptr)
ret i32 %call
; CHECK: entry:
; CHECK-NOT: @malloc
; CHECK-NOT: %setjmpTable
; CHECK-NOT: @saveSetjmp
; CHECK-NOT: @testSetjmp
; The remaining setjmp call is converted to constant 0, because setjmp returns 0
; when called directly.
; CHECK: ret i32 0
}

Expand Down
23 changes: 23 additions & 0 deletions llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj.ll
Expand Up @@ -271,6 +271,29 @@ ehcleanup: ; preds = %entry
cleanupret from %0 unwind to caller
}

; This case was adapted from @cleanuppad_no_parent by removing allocas and
; destructor calls, to generate a situation that there's only 'invoke @setjmp'
; and no other longjmpable calls.
define i32 @setjmp_only() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
; CHECK-LABEL: @setjmp_only
entry:
%buf = alloca [1 x %struct.__jmp_buf_tag], align 16
%arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
%call = invoke i32 @setjmp(%struct.__jmp_buf_tag* noundef %arraydecay) #0
to label %invoke.cont unwind label %ehcleanup

invoke.cont: ; preds = %entry
ret i32 %call
; CHECK: invoke.cont:
; The remaining setjmp call is converted to constant 0, because setjmp returns 0
; when called directly.
; CHECK: ret i32 0

ehcleanup: ; preds = %entry
%0 = cleanuppad within none []
cleanupret from %0 unwind to caller
}

declare void @foo()
; Function Attrs: nounwind
declare %struct.Temp* @_ZN4TempD2Ev(%struct.Temp* %this) #2
Expand Down

0 comments on commit 4f1244d

Please sign in to comment.