Skip to content

Commit 8e35a18

Browse files
committed
[WebAssembly] Support Emscripten EH/SjLj in Wasm64
In wasm64, the signatures of some library functions and global variables defined in Emscripten change: - `emscripten_longjmp`: `(i32, i32) -> ()` -> `(i64, i32) -> ()` This changes because the first argument is the address of a memory buffer. This in turn causes more changes below. - `setThrew`: `(i32, i32) -> ()` -> `(i64, i32) -> ()` `emscripten_longjmp` calls `setThrew` with the i64 buffer argument as the first parameter. - `__THREW__` (global var): `i32` to `i64` `setThrew`'s first argument is set to this `__THREW__` variable, so it should change to i64 as well. - `testSetjmp`: `(i32, i32*, i32) -> (i32)` -> `(i64, i32*, i32) -> (i32)` In the code transformation done in this pass, the value of `__THREW__` is passed as the first parameter of `testSetjmp`. This patch creates some helper functions to easily get types that become different depending on the wasm32/wasm64, and uses them to change various function signatures and code transformations. Also updates the tests with WASM32/WASM64 check lines. (Untested) Emscripten side patch: emscripten-core/emscripten#14108 Reviewed By: aardappel Differential Revision: https://reviews.llvm.org/D101985
1 parent ea0eec6 commit 8e35a18

File tree

4 files changed

+99
-76
lines changed

4 files changed

+99
-76
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp

Lines changed: 59 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@
4747
/// These variables are used for both exceptions and setjmp/longjmps.
4848
/// __THREW__ indicates whether an exception or a longjmp occurred or not. 0
4949
/// means nothing occurred, 1 means an exception occurred, and other numbers
50-
/// mean a longjmp occurred. In the case of longjmp, __threwValue variable
50+
/// mean a longjmp occurred. In the case of longjmp, __THREW__ variable
5151
/// indicates the corresponding setjmp buffer the longjmp corresponds to.
5252
///
5353
/// * Exception handling
5454
///
5555
/// 2) We assume the existence of setThrew and setTempRet0/getTempRet0 functions
5656
/// at link time. setThrew exists in Emscripten's compiler-rt:
5757
///
58-
/// void setThrew(int threw, int value) {
58+
/// void setThrew(uintptr_t threw, int value) {
5959
/// if (__THREW__ == 0) {
6060
/// __THREW__ = threw;
6161
/// __threwValue = value;
@@ -292,13 +292,12 @@ static bool canThrow(const Value *V) {
292292
return true;
293293
}
294294

295-
// Get a global variable with the given name. If it doesn't exist declare it,
296-
// which will generate an import and asssumes that it will exist at link time.
297-
static GlobalVariable *getGlobalVariableI32(Module &M, IRBuilder<> &IRB,
298-
WebAssemblyTargetMachine &TM,
299-
const char *Name) {
300-
auto Int32Ty = IRB.getInt32Ty();
301-
auto *GV = dyn_cast<GlobalVariable>(M.getOrInsertGlobal(Name, Int32Ty));
295+
// Get a global variable with the given name. If it doesn't exist declare it,
296+
// which will generate an import and assume that it will exist at link time.
297+
static GlobalVariable *getGlobalVariable(Module &M, Type *Ty,
298+
WebAssemblyTargetMachine &TM,
299+
const char *Name) {
300+
auto *GV = dyn_cast<GlobalVariable>(M.getOrInsertGlobal(Name, Ty));
302301
if (!GV)
303302
report_fatal_error(Twine("unable to create global: ") + Name);
304303

@@ -354,6 +353,28 @@ static Function *getEmscriptenFunction(FunctionType *Ty, const Twine &Name,
354353
return F;
355354
}
356355

356+
// Returns an integer type for the target architecture's address space.
357+
// i32 for wasm32 and i64 for wasm64.
358+
static Type *getAddrIntType(Module *M) {
359+
IRBuilder<> IRB(M->getContext());
360+
return IRB.getIntNTy(M->getDataLayout().getPointerSizeInBits());
361+
}
362+
363+
// Returns an integer pointer type for the target architecture's address space.
364+
// i32* for wasm32 and i64* for wasm64.
365+
static Type *getAddrPtrType(Module *M) {
366+
return Type::getIntNPtrTy(M->getContext(),
367+
M->getDataLayout().getPointerSizeInBits());
368+
}
369+
370+
// Returns an integer whose type is the integer type for the target's address
371+
// space. Returns (i32 C) for wasm32 and (i64 C) for wasm64, when C is the
372+
// integer.
373+
static Value *getAddrSizeInt(Module *M, uint64_t C) {
374+
IRBuilder<> IRB(M->getContext());
375+
return IRB.getIntN(M->getDataLayout().getPointerSizeInBits(), C);
376+
}
377+
357378
// Returns __cxa_find_matching_catch_N function, where N = NumClauses + 2.
358379
// This is because a landingpad instruction contains two more arguments, a
359380
// personality function and a cleanup bit, and __cxa_find_matching_catch_N
@@ -381,7 +402,8 @@ WebAssemblyLowerEmscriptenEHSjLj::getFindMatchingCatch(Module &M,
381402
// Returns %__THREW__.val, which indicates whether an exception is thrown (or
382403
// whether longjmp occurred), for future use.
383404
Value *WebAssemblyLowerEmscriptenEHSjLj::wrapInvoke(CallBase *CI) {
384-
LLVMContext &C = CI->getModule()->getContext();
405+
Module *M = CI->getModule();
406+
LLVMContext &C = M->getContext();
385407

386408
// If we are calling a function that is noreturn, we must remove that
387409
// attribute. The code we insert here does expect it to return, after we
@@ -397,7 +419,7 @@ Value *WebAssemblyLowerEmscriptenEHSjLj::wrapInvoke(CallBase *CI) {
397419

398420
// Pre-invoke
399421
// __THREW__ = 0;
400-
IRB.CreateStore(IRB.getInt32(0), ThrewGV);
422+
IRB.CreateStore(getAddrSizeInt(M, 0), ThrewGV);
401423

402424
// Invoke function wrapper in JavaScript
403425
SmallVector<Value *, 16> Args;
@@ -445,8 +467,8 @@ Value *WebAssemblyLowerEmscriptenEHSjLj::wrapInvoke(CallBase *CI) {
445467
// Post-invoke
446468
// %__THREW__.val = __THREW__; __THREW__ = 0;
447469
Value *Threw =
448-
IRB.CreateLoad(IRB.getInt32Ty(), ThrewGV, ThrewGV->getName() + ".val");
449-
IRB.CreateStore(IRB.getInt32(0), ThrewGV);
470+
IRB.CreateLoad(getAddrIntType(M), ThrewGV, ThrewGV->getName() + ".val");
471+
IRB.CreateStore(getAddrSizeInt(M, 0), ThrewGV);
450472
return Threw;
451473
}
452474

@@ -541,7 +563,8 @@ void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(
541563
Value *SetjmpTableSize, Value *&Label, Value *&LongjmpResult,
542564
BasicBlock *&EndBB) {
543565
Function *F = BB->getParent();
544-
LLVMContext &C = BB->getModule()->getContext();
566+
Module *M = F->getParent();
567+
LLVMContext &C = M->getContext();
545568
IRBuilder<> IRB(C);
546569
IRB.SetCurrentDebugLocation(DL);
547570

@@ -550,7 +573,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(
550573
BasicBlock *ThenBB1 = BasicBlock::Create(C, "if.then1", F);
551574
BasicBlock *ElseBB1 = BasicBlock::Create(C, "if.else1", F);
552575
BasicBlock *EndBB1 = BasicBlock::Create(C, "if.end", F);
553-
Value *ThrewCmp = IRB.CreateICmpNE(Threw, IRB.getInt32(0));
576+
Value *ThrewCmp = IRB.CreateICmpNE(Threw, getAddrSizeInt(M, 0));
554577
Value *ThrewValue = IRB.CreateLoad(IRB.getInt32Ty(), ThrewValueGV,
555578
ThrewValueGV->getName() + ".val");
556579
Value *ThrewValueCmp = IRB.CreateICmpNE(ThrewValue, IRB.getInt32(0));
@@ -562,10 +585,10 @@ void WebAssemblyLowerEmscriptenEHSjLj::wrapTestSetjmp(
562585
IRB.SetInsertPoint(ThenBB1);
563586
BasicBlock *ThenBB2 = BasicBlock::Create(C, "if.then2", F);
564587
BasicBlock *EndBB2 = BasicBlock::Create(C, "if.end2", F);
565-
Value *ThrewInt = IRB.CreateIntToPtr(Threw, Type::getInt32PtrTy(C),
566-
Threw->getName() + ".i32p");
567-
Value *LoadedThrew = IRB.CreateLoad(IRB.getInt32Ty(), ThrewInt,
568-
ThrewInt->getName() + ".loaded");
588+
Value *ThrewPtr =
589+
IRB.CreateIntToPtr(Threw, getAddrPtrType(M), Threw->getName() + ".p");
590+
Value *LoadedThrew = IRB.CreateLoad(getAddrIntType(M), ThrewPtr,
591+
ThrewPtr->getName() + ".loaded");
569592
Value *ThenLabel = IRB.CreateCall(
570593
TestSetjmpF, {LoadedThrew, SetjmpTable, SetjmpTableSize}, "label");
571594
Value *Cmp2 = IRB.CreateICmpEQ(ThenLabel, IRB.getInt32(0));
@@ -622,11 +645,12 @@ void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
622645
}
623646

624647
// Replace uses of longjmp with emscripten_longjmp. emscripten_longjmp takes
625-
// arguments of type {i32, i32} and longjmp takes {jmp_buf*, i32}, so we need a
626-
// ptrtoint instruction here to make the type match. jmp_buf* will eventually be
627-
// lowered to i32 in the wasm backend.
648+
// arguments of type {i32, i32} (wasm32) / {i64, i32} (wasm64) and longjmp takes
649+
// {jmp_buf*, i32}, so we need a ptrtoint instruction here to make the type
650+
// match. jmp_buf* will eventually be lowered to i32 in the wasm backend.
628651
static void replaceLongjmpWithEmscriptenLongjmp(Function *LongjmpF,
629652
Function *EmLongjmpF) {
653+
Module *M = LongjmpF->getParent();
630654
SmallVector<CallInst *, 8> ToErase;
631655
LLVMContext &C = LongjmpF->getParent()->getContext();
632656
IRBuilder<> IRB(C);
@@ -638,7 +662,7 @@ static void replaceLongjmpWithEmscriptenLongjmp(Function *LongjmpF,
638662
if (CI && CI->getCalledFunction() == LongjmpF) {
639663
IRB.SetInsertPoint(CI);
640664
Value *Jmpbuf =
641-
IRB.CreatePtrToInt(CI->getArgOperand(0), IRB.getInt32Ty(), "jmpbuf");
665+
IRB.CreatePtrToInt(CI->getArgOperand(0), getAddrIntType(M), "jmpbuf");
642666
IRB.CreateCall(EmLongjmpF, {Jmpbuf, CI->getArgOperand(1)});
643667
ToErase.push_back(CI);
644668
}
@@ -671,18 +695,15 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
671695
assert(TPC && "Expected a TargetPassConfig");
672696
auto &TM = TPC->getTM<WebAssemblyTargetMachine>();
673697

674-
if ((EnableEH || DoSjLj) &&
675-
Triple(M.getTargetTriple()).getArch() == Triple::wasm64)
676-
report_fatal_error("Emscripten EH/SjLj is not supported with wasm64 yet");
677698
if (EnableEH && TM.Options.ExceptionModel == ExceptionHandling::Wasm)
678699
report_fatal_error("-exception-model=wasm not allowed with "
679700
"-enable-emscripten-cxx-exceptions");
680701

681702
// Declare (or get) global variables __THREW__, __threwValue, and
682703
// getTempRet0/setTempRet0 function which are used in common for both
683704
// exception handling and setjmp/longjmp handling
684-
ThrewGV = getGlobalVariableI32(M, IRB, TM, "__THREW__");
685-
ThrewValueGV = getGlobalVariableI32(M, IRB, TM, "__threwValue");
705+
ThrewGV = getGlobalVariable(M, getAddrIntType(&M), TM, "__THREW__");
706+
ThrewValueGV = getGlobalVariable(M, IRB.getInt32Ty(), TM, "__threwValue");
686707
GetTempRet0Func = getEmscriptenFunction(
687708
FunctionType::get(IRB.getInt32Ty(), false), "getTempRet0", &M);
688709
SetTempRet0Func = getEmscriptenFunction(
@@ -718,7 +739,7 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
718739

719740
// Register emscripten_longjmp function
720741
FunctionType *FTy = FunctionType::get(
721-
IRB.getVoidTy(), {IRB.getInt32Ty(), IRB.getInt32Ty()}, false);
742+
IRB.getVoidTy(), {getAddrIntType(&M), IRB.getInt32Ty()}, false);
722743
EmLongjmpF = getEmscriptenFunction(FTy, "emscripten_longjmp", &M);
723744

724745
if (LongjmpF)
@@ -736,7 +757,8 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runOnModule(Module &M) {
736757
// Register testSetjmp function
737758
FTy = FunctionType::get(
738759
IRB.getInt32Ty(),
739-
{IRB.getInt32Ty(), Type::getInt32PtrTy(C), IRB.getInt32Ty()}, false);
760+
{getAddrIntType(&M), Type::getInt32PtrTy(C), IRB.getInt32Ty()},
761+
false);
740762
TestSetjmpF = getEmscriptenFunction(FTy, "testSetjmp", &M);
741763

742764
// Only traverse functions that uses setjmp in order not to insert
@@ -794,7 +816,7 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runEHOnFunction(Function &F) {
794816
ToErase.push_back(II);
795817

796818
// Insert a branch based on __THREW__ variable
797-
Value *Cmp = IRB.CreateICmpEQ(Threw, IRB.getInt32(1), "cmp");
819+
Value *Cmp = IRB.CreateICmpEQ(Threw, getAddrSizeInt(&M, 1), "cmp");
798820
IRB.CreateCondBr(Cmp, II->getUnwindDest(), II->getNormalDest());
799821

800822
} else {
@@ -1060,12 +1082,15 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
10601082
// __THREW__ = 0;
10611083
for (auto I = std::next(BasicBlock::iterator(ThrewLI)), IE = BB->end();
10621084
I != IE; ++I) {
1063-
if (auto *SI = dyn_cast<StoreInst>(I))
1064-
if (auto *GV = dyn_cast<GlobalVariable>(SI->getPointerOperand()))
1065-
if (GV == ThrewGV && SI->getValueOperand() == IRB.getInt32(0)) {
1085+
if (auto *SI = dyn_cast<StoreInst>(I)) {
1086+
if (auto *GV = dyn_cast<GlobalVariable>(SI->getPointerOperand())) {
1087+
if (GV == ThrewGV &&
1088+
SI->getValueOperand() == getAddrSizeInt(&M, 0)) {
10661089
ThrewResetSI = SI;
10671090
break;
10681091
}
1092+
}
1093+
}
10691094
}
10701095
assert(Threw && ThrewLI && "Cannot find __THREW__ load after invoke");
10711096
assert(ThrewResetSI && "Cannot find __THREW__ store after invoke");

llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-options.ll

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
; RUN: llc < %s -enable-emscripten-cxx-exceptions | FileCheck %s --check-prefix=EH
22
; RUN: llc < %s -enable-emscripten-sjlj | FileCheck %s --check-prefix=SJLJ
33
; RUN: llc < %s | FileCheck %s --check-prefix=NONE
4-
; RUN: not --crash llc < %s -enable-emscripten-cxx-exceptions -mtriple=wasm64-unknown-unknown 2>&1 | FileCheck %s --check-prefix=WASM64-EH
5-
; RUN: not --crash llc < %s -enable-emscripten-sjlj -mtriple=wasm64-unknown-unknown 2>&1 | FileCheck %s --check-prefix=WASM64-SJLJ
64
; RUN: not --crash llc < %s -enable-emscripten-cxx-exceptions -exception-model=wasm 2>&1 | FileCheck %s --check-prefix=WASM-EH-EM-EH
75

86
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
@@ -103,6 +101,4 @@ attributes #2 = { nounwind }
103101
; SJLJ-NOT: .import_module emscripten_longjmp_jmpbuf
104102
; SJLJ-NOT: .import_name emscripten_longjmp_jmpbuf
105103

106-
; WASM64-EH: LLVM ERROR: Emscripten EH/SjLj is not supported with wasm64 yet
107-
; WASM64-SJLJ: LLVM ERROR: Emscripten EH/SjLj is not supported with wasm64 yet
108104
; WASM-EH-EM-EH: LLVM ERROR: -exception-model=wasm not allowed with -enable-emscripten-cxx-exceptions

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
; RUN: opt < %s -wasm-lower-em-ehsjlj -S | FileCheck %s --check-prefixes=CHECK,NO-TLS
2-
; RUN: opt < %s -wasm-lower-em-ehsjlj -S --mattr=+atomics,+bulk-memory | FileCheck %s --check-prefixes=CHECK,TLS
1+
; RUN: opt < %s -wasm-lower-em-ehsjlj -S | FileCheck %s --check-prefixes=CHECK,NO-TLS -DPTR=i32
2+
; RUN: opt < %s -wasm-lower-em-ehsjlj -S --mattr=+atomics,+bulk-memory | FileCheck %s --check-prefixes=CHECK,TLS -DPTR=i32
3+
; RUN: opt < %s -wasm-lower-em-ehsjlj --mtriple=wasm64-unknown-unknown -data-layout="e-m:e-p:64:64-i64:64-n32:64-S128" -S | FileCheck %s --check-prefixes=CHECK -DPTR=i64
34

45
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
56
target triple = "wasm32-unknown-unknown"
67

78
@_ZTIi = external constant i8*
89
@_ZTIc = external constant i8*
9-
; NO-TLS-DAG: __THREW__ = external global i32
10+
; NO-TLS-DAG: __THREW__ = external global [[PTR]]
1011
; NO-TLS-DAG: __threwValue = external global i32
11-
; TLS-DAG: __THREW__ = external thread_local(localexec) global i32
12+
; TLS-DAG: __THREW__ = external thread_local(localexec) global [[PTR]]
1213
; TLS-DAG: __threwValue = external thread_local(localexec) global i32
1314

1415
; Test invoke instruction with clauses (try-catch block)
@@ -18,11 +19,11 @@ entry:
1819
invoke void @foo(i32 3)
1920
to label %invoke.cont unwind label %lpad
2021
; CHECK: entry:
21-
; CHECK-NEXT: store i32 0, i32* @__THREW__
22+
; CHECK-NEXT: store [[PTR]] 0, [[PTR]]* @__THREW__
2223
; CHECK-NEXT: call cc{{.*}} void @__invoke_void_i32(void (i32)* @foo, i32 3)
23-
; CHECK-NEXT: %[[__THREW__VAL:.*]] = load i32, i32* @__THREW__
24-
; CHECK-NEXT: store i32 0, i32* @__THREW__
25-
; CHECK-NEXT: %cmp = icmp eq i32 %[[__THREW__VAL]], 1
24+
; CHECK-NEXT: %[[__THREW__VAL:.*]] = load [[PTR]], [[PTR]]* @__THREW__
25+
; CHECK-NEXT: store [[PTR]] 0, [[PTR]]* @__THREW__
26+
; CHECK-NEXT: %cmp = icmp eq [[PTR]] %[[__THREW__VAL]], 1
2627
; CHECK-NEXT: br i1 %cmp, label %lpad, label %invoke.cont
2728

2829
invoke.cont: ; preds = %entry
@@ -74,11 +75,11 @@ entry:
7475
invoke void @foo(i32 3)
7576
to label %invoke.cont unwind label %lpad
7677
; CHECK: entry:
77-
; CHECK-NEXT: store i32 0, i32* @__THREW__
78+
; CHECK-NEXT: store [[PTR]] 0, [[PTR]]* @__THREW__
7879
; CHECK-NEXT: call cc{{.*}} void @__invoke_void_i32(void (i32)* @foo, i32 3)
79-
; CHECK-NEXT: %[[__THREW__VAL:.*]] = load i32, i32* @__THREW__
80-
; CHECK-NEXT: store i32 0, i32* @__THREW__
81-
; CHECK-NEXT: %cmp = icmp eq i32 %[[__THREW__VAL]], 1
80+
; CHECK-NEXT: %[[__THREW__VAL:.*]] = load [[PTR]], [[PTR]]* @__THREW__
81+
; CHECK-NEXT: store [[PTR]] 0, [[PTR]]* @__THREW__
82+
; CHECK-NEXT: %cmp = icmp eq [[PTR]] %[[__THREW__VAL]], 1
8283
; CHECK-NEXT: br i1 %cmp, label %lpad, label %invoke.cont
8384

8485
invoke.cont: ; preds = %entry
@@ -125,7 +126,7 @@ entry:
125126
%0 = invoke noalias i8* @bar(i8 signext 1, i8 zeroext 2)
126127
to label %invoke.cont unwind label %lpad
127128
; CHECK: entry:
128-
; CHECK-NEXT: store i32 0, i32* @__THREW__
129+
; CHECK-NEXT: store [[PTR]] 0, [[PTR]]* @__THREW__
129130
; CHECK-NEXT: %0 = call cc{{.*}} noalias i8* @"__invoke_i8*_i8_i8"(i8* (i8, i8)* @bar, i8 signext 1, i8 zeroext 2)
130131

131132
invoke.cont: ; preds = %entry

0 commit comments

Comments
 (0)