Skip to content

Commit 6a6966e

Browse files
committed
[WebAssembly] Don't return multivalue for invokes
Emscripten EH/SjLj uses invoke wrappers in JS, such as `invoke_vi`, from which user functions are called indirectly, to emulate exceptions and setjmp/longjmp. But in case the invoked function returns multiple values or a 128-bit value, its the JS invoke wrappers cannot return multivalue because JS doesn't support that. So we should not enable multivalue returns for the JS invoke wrappers and also the functions called by those JS wrappers because their signature has to match with the JS wrapper. For example, if `func` returns `{i32, i32}` and we have ```ll invoke {i32, i32} @func() ... ``` while LowerEmscriptenEHSjLj will lower it down to something like ```ll %0 = call { i32, i32 } @"__invoke_{i32.i32}_void"(ptr @func) ... ``` we should eventually lower both the invoke wrapper (whose name will be changed later to `invoke_vi`) and `func` down to a signature that indirectly returns multiple values by memory parameter, because JS invoke wrappers do support multiple return values. So we need to disable multivalue returns for JS invoke wrappers and functions called by them. I think we have three ways to do that: 1. Make a set and add all functions that are invoked by JS invoke wrappers in LowerEmscriptenEHSjLj and pass it to the backend using an auxiliary data structure. We have a precedent of this kind of structure already, which is used for Wasm EH: https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/CodeGen/WasmEHFuncInfo.h We can even add this set to `WasmEHFuncInfo` maybe, given that this is also a way of handling exceptions in Wasm. - Pros: Most precise - Cons: Auxiliary structure needed 2. Unless we record the invoked functions in LowerEmscriptenEHSjLj like 1, we don't have a way of precisely knowing the set of invoked functions. But they are all indirectly invoked, so in the backend we can check whether a given function is ever indirectly used (i.e., its pointer taken) by traversing its `users()` in the IR and if it is, we don't allow multivalue returns for them. - Pros: No auxiliary structure - Cons: IR checking overhead. More conservative than 1. 3. Disallow all multivalue returns when Emscripten EH or SjLj is enabled. - Pros: Simplest - Cons: Most conservative. This PR is doing 3. While it is the most conservative and possibly disallowing multivalue returns from more functions than needed, I chose this because it is the simplest, and given that hopefully more people will adopt Wasm EH going forward, I don't think there will be many people who would use multivalue and Emscripten EH/SjLj together and want the whatever performance benefit that multivalue return can bring, given that Emscripten EH/SjLj has already a huge performance cost. This is separate from whether we should make the multivalue return dependent on the multivalue feature or something else like a clang flag, which is being partly discussed in WebAssembly/tool-conventions#223. Whichever way we decide on that front, we still need to disable multivalue returns in case Emscripten EH/SjLj is used.
1 parent 2c80a9a commit 6a6966e

7 files changed

+61
-21
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,19 +151,14 @@ static std::string getEmscriptenInvokeSymbolName(wasm::WasmSignature *Sig) {
151151
//===----------------------------------------------------------------------===//
152152

153153
MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction(
154-
const Function *F, bool EnableEmEH, wasm::WasmSignature *Sig,
154+
const Function *F, bool EnableEmEHSjLj, wasm::WasmSignature *Sig,
155155
bool &InvokeDetected) {
156156
MCSymbolWasm *WasmSym = nullptr;
157-
if (EnableEmEH && isEmscriptenInvokeName(F->getName())) {
157+
if (EnableEmEHSjLj && isEmscriptenInvokeName(F->getName())) {
158158
assert(Sig);
159159
InvokeDetected = true;
160-
if (Sig->Returns.size() > 1) {
161-
std::string Msg =
162-
"Emscripten EH/SjLj does not support multivalue returns: " +
163-
std::string(F->getName()) + ": " +
164-
WebAssembly::signatureToString(Sig);
165-
report_fatal_error(Twine(Msg));
166-
}
160+
// When Emscripten EH/SjLj is enabled, we don't enable multivalue returns
161+
assert(Sig->Returns.size() <= 1);
167162
WasmSym = cast<MCSymbolWasm>(
168163
GetExternalSymbolSymbol(getEmscriptenInvokeSymbolName(Sig)));
169164
} else {

llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyAsmPrinter final : public AsmPrinter {
8181
MVT getRegType(unsigned RegNo) const;
8282
std::string regToString(const MachineOperand &MO);
8383
WebAssemblyTargetStreamer *getTargetStreamer();
84-
MCSymbolWasm *getMCSymbolForFunction(const Function *F, bool EnableEmEH,
84+
MCSymbolWasm *getMCSymbolForFunction(const Function *F, bool EnableEmEHSjLj,
8585
wasm::WasmSignature *Sig,
8686
bool &InvokeDetected);
8787
MCSymbol *getOrCreateWasmSymbol(StringRef Name);

llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,15 +1288,15 @@ bool WebAssemblyTargetLowering::CanLowerReturn(
12881288
const SmallVectorImpl<ISD::OutputArg> &Outs,
12891289
LLVMContext & /*Context*/) const {
12901290
// WebAssembly can only handle returning tuples with multivalue enabled
1291-
return Subtarget->hasMultivalue() || Outs.size() <= 1;
1291+
return WebAssembly::canLowerReturn(Outs.size(), Subtarget);
12921292
}
12931293

12941294
SDValue WebAssemblyTargetLowering::LowerReturn(
12951295
SDValue Chain, CallingConv::ID CallConv, bool /*IsVarArg*/,
12961296
const SmallVectorImpl<ISD::OutputArg> &Outs,
12971297
const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL,
12981298
SelectionDAG &DAG) const {
1299-
assert((Subtarget->hasMultivalue() || Outs.size() <= 1) &&
1299+
assert(WebAssembly::canLowerReturn(Outs.size(), Subtarget) &&
13001300
"MVP WebAssembly can only return up to one value");
13011301
if (!callingConvSupported(CallConv))
13021302
fail(DL, DAG, "WebAssembly doesn't support non-C calling conventions");

llvm/lib/Target/WebAssembly/WebAssemblyMachineFunctionInfo.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Utils/WebAssemblyTypeUtilities.h"
1818
#include "WebAssemblyISelLowering.h"
1919
#include "WebAssemblySubtarget.h"
20+
#include "WebAssemblyUtilities.h"
2021
#include "llvm/CodeGen/Analysis.h"
2122
#include "llvm/CodeGen/WasmEHFuncInfo.h"
2223
#include "llvm/Target/TargetMachine.h"
@@ -70,12 +71,12 @@ void llvm::computeSignatureVTs(const FunctionType *Ty,
7071
computeLegalValueVTs(ContextFunc, TM, Ty->getReturnType(), Results);
7172

7273
MVT PtrVT = MVT::getIntegerVT(TM.createDataLayout().getPointerSizeInBits());
73-
if (Results.size() > 1 &&
74-
!TM.getSubtarget<WebAssemblySubtarget>(ContextFunc).hasMultivalue()) {
74+
if (!WebAssembly::canLowerReturn(
75+
Results.size(),
76+
&TM.getSubtarget<WebAssemblySubtarget>(ContextFunc))) {
7577
// WebAssembly can't lower returns of multiple values without demoting to
76-
// sret unless multivalue is enabled (see
77-
// WebAssemblyTargetLowering::CanLowerReturn). So replace multiple return
78-
// values with a poitner parameter.
78+
// sret unless multivalue is enabled (see WebAssembly::canLowerReturn). So
79+
// replace multiple return values with a poitner parameter.
7980
Results.clear();
8081
Params.push_back(PtrVT);
8182
}

llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,3 +179,17 @@ unsigned WebAssembly::getCopyOpcodeForRegClass(const TargetRegisterClass *RC) {
179179
llvm_unreachable("Unexpected register class");
180180
}
181181
}
182+
183+
bool WebAssembly::canLowerReturn(size_t ResultSize,
184+
const WebAssemblySubtarget *Subtarget) {
185+
// We don't return multivalues when either of Emscripten EH or Emscripten SjLj
186+
// is used. JS invoke wrapper functions, which are used in Emscripten EH/SjLj,
187+
// don't support multiple return values, and the functions indirectly called
188+
// from those JS invoke wrapper functions can't be lowered using multivalue
189+
// results because they have to match the signature of the JS invoke wrappers.
190+
// We can come up with a precise set of functions that are called from the JS
191+
// wrappers if we do some bookeeping, but given that Emscripten EH/SjLj is an
192+
// old feature whose use is expected to decline, we don't think it's worth it.
193+
return ResultSize <= 1 || (Subtarget->hasMultivalue() &&
194+
!WebAssembly::WasmEnableEmEH && !WasmEnableEmSjLj);
195+
}

llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ MachineInstr *findCatch(MachineBasicBlock *EHPad);
6363
/// Returns the appropriate copy opcode for the given register class.
6464
unsigned getCopyOpcodeForRegClass(const TargetRegisterClass *RC);
6565

66+
/// Returns true if the function's return value(s) can be lowered directly,
67+
/// i.e., not indirectly via a pointer parameter that points to the value in
68+
/// memory.
69+
bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget);
70+
6671
} // end namespace WebAssembly
6772

6873
} // end namespace llvm

llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj-multi-return.ll

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
; RUN: not --crash llc < %s -enable-emscripten-cxx-exceptions -mattr=+multivalue 2>&1 | FileCheck %s --check-prefix=EH
2-
; RUN: not --crash llc < %s -enable-emscripten-sjlj -mattr=+multivalue 2>&1 | FileCheck %s --check-prefix=SJLJ
1+
; RUN: llc < %s -enable-emscripten-cxx-exceptions -enable-emscripten-sjlj -mattr=+multivalue | FileCheck %s
32

4-
; Currently multivalue returning functions are not supported in Emscripten EH /
5-
; SjLj. Make sure they error out.
3+
; Even with multivalue feature enabled, we don't enable multivalue returns when
4+
; Emscripten EH/SjLj is used. Also the invoke wrappers' (e.g. invoke_vi)
5+
; signature should not change.
66

77
target triple = "wasm32-unknown-unknown"
88

@@ -35,7 +35,32 @@ entry:
3535
unreachable
3636
}
3737

38+
define void @invoke_i128() personality ptr @__gxx_personality_v0 {
39+
entry:
40+
invoke i128 @get_i128()
41+
to label %try.cont unwind label %lpad
42+
43+
lpad: ; preds = %entry
44+
%1 = landingpad { ptr, i32 }
45+
catch ptr null
46+
%2 = extractvalue { ptr, i32 } %1, 0
47+
%3 = extractvalue { ptr, i32 } %1, 1
48+
%4 = call ptr @__cxa_begin_catch(ptr %2)
49+
call void @__cxa_end_catch()
50+
br label %try.cont
51+
52+
try.cont: ; preds = %entry, %lpad
53+
ret void
54+
}
55+
3856
declare {i32, i32} @foo(i32)
57+
; CHECK-DAG: .functype foo (i32, i32) -> ()
58+
; CHECK-DAG: .functype invoke_vi (i32, i32) -> ()
59+
60+
declare i128 @get_i128()
61+
; CHECK-DAG: .functype get_i128 (i32) -> ()
62+
; CHECK-DAG: .functype invoke_vii (i32, i32, i32) -> ()
63+
3964
declare i32 @__gxx_personality_v0(...)
4065
declare ptr @__cxa_begin_catch(ptr)
4166
declare void @__cxa_end_catch()

0 commit comments

Comments
 (0)