Skip to content

Commit 750b3dd

Browse files
committed
[WebAssembly] Handle indirect uses of longjmp
In LowerEmscriptenEHSjLj, `longjmp` used to be replaced with `emscripten_longjmp_jmpbuf(jmp_buf*, i32)`, which will eventually be lowered to `emscripten_longjmp(i32, i32)`. The reason we used two different names was because they had different signatures in the IR pass. D88697 fixed this by only using `emscripten_longjmp(i32, i32)` and adding a `ptrtoint` cast to its first argument, so ``` longjmp(buf, 0) ``` becomes ``` emscripten_longjmp((i32)buf, 0) ``` But this assumed all uses of `longjmp` was a direct call to it, which was not the case. This patch handles indirect uses of `longjmp` by replacing ``` longjmp ``` with ``` (i32(*)(jmp_buf*, i32))emscripten_longjmp ``` Reviewed By: tlively Differential Revision: https://reviews.llvm.org/D89032
1 parent d421e04 commit 750b3dd

File tree

2 files changed

+50
-8
lines changed

2 files changed

+50
-8
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -644,19 +644,29 @@ static void replaceLongjmpWithEmscriptenLongjmp(Function *LongjmpF,
644644
SmallVector<CallInst *, 8> ToErase;
645645
LLVMContext &C = LongjmpF->getParent()->getContext();
646646
IRBuilder<> IRB(C);
647+
648+
// For calls to longjmp, replace it with emscripten_longjmp and cast its first
649+
// argument (jmp_buf*) to int
647650
for (User *U : LongjmpF->users()) {
648651
auto *CI = dyn_cast<CallInst>(U);
649-
if (!CI)
650-
report_fatal_error("Does not support indirect calls to longjmp");
651-
IRB.SetInsertPoint(CI);
652-
Value *Jmpbuf =
653-
IRB.CreatePtrToInt(CI->getArgOperand(0), IRB.getInt32Ty(), "jmpbuf");
654-
IRB.CreateCall(EmLongjmpF, {Jmpbuf, CI->getArgOperand(1)});
655-
ToErase.push_back(CI);
652+
if (CI && CI->getCalledFunction() == LongjmpF) {
653+
IRB.SetInsertPoint(CI);
654+
Value *Jmpbuf =
655+
IRB.CreatePtrToInt(CI->getArgOperand(0), IRB.getInt32Ty(), "jmpbuf");
656+
IRB.CreateCall(EmLongjmpF, {Jmpbuf, CI->getArgOperand(1)});
657+
ToErase.push_back(CI);
658+
}
656659
}
657-
658660
for (auto *I : ToErase)
659661
I->eraseFromParent();
662+
663+
// If we have any remaining uses of longjmp's function pointer, replace it
664+
// with (int(*)(jmp_buf*, int))emscripten_longjmp.
665+
if (!LongjmpF->uses().empty()) {
666+
Value *EmLongjmp =
667+
IRB.CreateBitCast(EmLongjmpF, LongjmpF->getType(), "em_longjmp");
668+
LongjmpF->replaceAllUsesWith(EmLongjmp);
669+
}
660670
}
661671

662672
bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {

llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ target triple = "wasm32-unknown-unknown"
1111
; NO-TLS-DAG: __threwValue = external global i32
1212
; TLS-DAG: __THREW__ = external thread_local(localexec) global i32
1313
; TLS-DAG: __threwValue = external thread_local(localexec) global i32
14+
@global_longjmp_ptr = global void (%struct.__jmp_buf_tag*, i32)* @longjmp, align 4
15+
; CHECK-DAG: @global_longjmp_ptr = global void (%struct.__jmp_buf_tag*, i32)* bitcast (void (i32, i32)* @emscripten_longjmp to void (%struct.__jmp_buf_tag*, i32)*)
1416

1517
; Test a simple setjmp - longjmp sequence
1618
define void @setjmp_longjmp() {
@@ -250,6 +252,36 @@ for.inc: ; preds = %for.cond
250252
; CHECK: %var[[VARNO]] = phi i32 [ %var, %for.inc ]
251253
}
252254

255+
; Tests cases where longjmp function pointer is used in other ways than direct
256+
; calls. longjmps should be replaced with
257+
; (int(*)(jmp_buf*, int))emscripten_longjmp.
258+
declare void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* %arg_ptr)
259+
define void @indirect_longjmp() {
260+
; CHECK-LABEL: @indirect_longjmp
261+
entry:
262+
%local_longjmp_ptr = alloca void (%struct.__jmp_buf_tag*, i32)*, align 4
263+
%buf0 = alloca [1 x %struct.__jmp_buf_tag], align 16
264+
%buf1 = alloca [1 x %struct.__jmp_buf_tag], align 16
265+
266+
; Store longjmp in a local variable, load it, and call it
267+
store void (%struct.__jmp_buf_tag*, i32)* @longjmp, void (%struct.__jmp_buf_tag*, i32)** %local_longjmp_ptr, align 4
268+
; CHECK: store void (%struct.__jmp_buf_tag*, i32)* bitcast (void (i32, i32)* @emscripten_longjmp to void (%struct.__jmp_buf_tag*, i32)*), void (%struct.__jmp_buf_tag*, i32)** %local_longjmp_ptr, align 4
269+
%longjmp_from_local_ptr = load void (%struct.__jmp_buf_tag*, i32)*, void (%struct.__jmp_buf_tag*, i32)** %local_longjmp_ptr, align 4
270+
%arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf0, i32 0, i32 0
271+
call void %longjmp_from_local_ptr(%struct.__jmp_buf_tag* %arraydecay, i32 0)
272+
273+
; Load longjmp from a global variable and call it
274+
%longjmp_from_global_ptr = load void (%struct.__jmp_buf_tag*, i32)*, void (%struct.__jmp_buf_tag*, i32)** @global_longjmp_ptr, align 4
275+
%arraydecay1 = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf1, i32 0, i32 0
276+
call void %longjmp_from_global_ptr(%struct.__jmp_buf_tag* %arraydecay1, i32 0)
277+
278+
; Pass longjmp as a function argument. This is a call but longjmp is not a
279+
; callee but an argument.
280+
call void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* @longjmp)
281+
; CHECK: call void @take_longjmp(void (%struct.__jmp_buf_tag*, i32)* bitcast (void (i32, i32)* @emscripten_longjmp to void (%struct.__jmp_buf_tag*, i32)*))
282+
ret void
283+
}
284+
253285
declare void @foo()
254286
; Function Attrs: returns_twice
255287
declare i32 @setjmp(%struct.__jmp_buf_tag*) #0

0 commit comments

Comments
 (0)