Skip to content

Commit 16c6b44

Browse files
authored
Support stack overflow checks in standalone mode (#2525)
In normal mode we call a JS import, but we can't import from JS in standalone mode. Instead, just trap in that case with an unreachable. (The error reporting is not as good in this case, but at least it catches all errors and halts, and the emitted wasm is valid for standalone mode.) Helps emscripten-core/emscripten#10019
1 parent 89d1cf9 commit 16c6b44

File tree

6 files changed

+372
-12
lines changed

6 files changed

+372
-12
lines changed

scripts/test/lld.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919

2020

2121
def args_for_finalize(filename):
22+
ret = ['--global-base=568']
2223
if 'safe_stack' in filename:
23-
return ['--check-stack-overflow', '--global-base=568']
24-
elif 'shared' in filename:
25-
return ['--side-module']
26-
elif 'standalone-wasm' in filename:
27-
return ['--standalone-wasm', '--global-base=568']
28-
else:
29-
return ['--global-base=568']
24+
ret += ['--check-stack-overflow']
25+
if 'shared' in filename:
26+
ret += ['--side-module']
27+
if 'standalone-wasm' in filename:
28+
ret += ['--standalone-wasm']
29+
return ret
3030

3131

3232
def test_wasm_emscripten_finalize():

src/tools/wasm-emscripten-finalize.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ int main(int argc, const char* argv[]) {
207207
}
208208

209209
EmscriptenGlueGenerator generator(wasm);
210+
generator.setStandalone(standaloneWasm);
211+
210212
generator.fixInvokeFunctionNames();
211213

212214
std::vector<Name> initializerFunctions;

src/wasm-emscripten.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class EmscriptenGlueGenerator {
3131
: wasm(wasm), builder(wasm), stackPointerOffset(stackPointerOffset),
3232
useStackPointerGlobal(stackPointerOffset == 0) {}
3333

34+
void setStandalone(bool standalone_) { standalone = standalone_; }
35+
3436
void generateRuntimeFunctions();
3537
Function* generateMemoryGrowthFunction();
3638
Function* generateAssignGOTEntriesFunction();
@@ -69,6 +71,7 @@ class EmscriptenGlueGenerator {
6971
Builder builder;
7072
Address stackPointerOffset;
7173
bool useStackPointerGlobal;
74+
bool standalone;
7275
// Used by generateDynCallThunk to track all the dynCall functions created
7376
// so far.
7477
std::unordered_set<Signature> sigs;

src/wasm/wasm-emscripten.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,25 @@ inline Expression* stackBoundsCheck(Builder& builder,
103103
Expression* value,
104104
Global* stackPointer,
105105
Global* stackLimit,
106-
Name handler) {
106+
Name handlerName) {
107107
// Add a local to store the value of the expression. We need the value twice:
108108
// once to check if it has overflowed, and again to assign to store it.
109109
auto newSP = Builder::addVar(func, stackPointer->type);
110+
// If we imported a handler, call it. That can show a nice error in JS.
111+
// Otherwise, just trap.
112+
Expression* handler;
113+
if (handlerName.is()) {
114+
handler = builder.makeCall(handlerName, {}, none);
115+
} else {
116+
handler = builder.makeUnreachable();
117+
}
110118
// (if (i32.lt_u (local.tee $newSP (...value...)) (global.get $__stack_limit))
111-
// (call $handler))
112119
auto check =
113120
builder.makeIf(builder.makeBinary(
114121
BinaryOp::LtUInt32,
115122
builder.makeLocalTee(newSP, value, stackPointer->type),
116123
builder.makeGlobalGet(stackLimit->name, stackLimit->type)),
117-
builder.makeCall(handler, {}, none));
124+
handler);
118125
// (global.set $__stack_pointer (local.get $newSP))
119126
auto newSet = builder.makeGlobalSet(
120127
stackPointer->name, builder.makeLocalGet(newSP, stackPointer->type));
@@ -530,8 +537,7 @@ void EmscriptenGlueGenerator::enforceStackLimit() {
530537
Builder::Mutable);
531538
wasm.addGlobal(stackLimit);
532539

533-
auto handler = importStackOverflowHandler();
534-
540+
Name handler = importStackOverflowHandler();
535541
StackLimitEnforcer walker(stackPointer, stackLimit, builder, handler);
536542
PassRunner runner(&wasm);
537543
walker.run(&runner, &wasm);
@@ -549,6 +555,12 @@ void EmscriptenGlueGenerator::generateSetStackLimitFunction() {
549555
}
550556

551557
Name EmscriptenGlueGenerator::importStackOverflowHandler() {
558+
// We can call an import to handle stack overflows normally, but not in
559+
// standalone mode, where we can't import from JS.
560+
if (standalone) {
561+
return Name();
562+
}
563+
552564
ImportInfo info(wasm);
553565

554566
if (auto* existing = info.getImportedFunction(ENV, STACK_OVERFLOW_IMPORT)) {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
(module
2+
(type $0 (func (param i32 i32) (result i32)))
3+
(type $1 (func))
4+
(type $2 (func (result i32)))
5+
(import "env" "printf" (func $printf (param i32 i32) (result i32)))
6+
(memory $0 2)
7+
(data (i32.const 568) "%d:%d\n\00Result: %d\n\00")
8+
(table $0 1 1 funcref)
9+
(global $global$0 (mut i32) (i32.const 66128))
10+
(global $global$1 i32 (i32.const 66128))
11+
(global $global$2 i32 (i32.const 587))
12+
(export "memory" (memory $0))
13+
(export "__wasm_call_ctors" (func $__wasm_call_ctors))
14+
(export "__heap_base" (global $global$1))
15+
(export "__data_end" (global $global$2))
16+
(export "main" (func $main))
17+
(func $__wasm_call_ctors (; 1 ;) (type $1)
18+
)
19+
(func $foo (; 2 ;) (type $0) (param $0 i32) (param $1 i32) (result i32)
20+
(local $2 i32)
21+
(global.set $global$0
22+
(local.tee $2
23+
(i32.sub
24+
(global.get $global$0)
25+
(i32.const 16)
26+
)
27+
)
28+
)
29+
(i32.store offset=4
30+
(local.get $2)
31+
(local.get $1)
32+
)
33+
(i32.store
34+
(local.get $2)
35+
(local.get $0)
36+
)
37+
(drop
38+
(call $printf
39+
(i32.const 568)
40+
(local.get $2)
41+
)
42+
)
43+
(global.set $global$0
44+
(i32.add
45+
(local.get $2)
46+
(i32.const 16)
47+
)
48+
)
49+
(i32.add
50+
(local.get $1)
51+
(local.get $0)
52+
)
53+
)
54+
(func $__original_main (; 3 ;) (type $2) (result i32)
55+
(local $0 i32)
56+
(global.set $global$0
57+
(local.tee $0
58+
(i32.sub
59+
(global.get $global$0)
60+
(i32.const 16)
61+
)
62+
)
63+
)
64+
(i32.store
65+
(local.get $0)
66+
(call $foo
67+
(i32.const 1)
68+
(i32.const 2)
69+
)
70+
)
71+
(drop
72+
(call $printf
73+
(i32.const 575)
74+
(local.get $0)
75+
)
76+
)
77+
(global.set $global$0
78+
(i32.add
79+
(local.get $0)
80+
(i32.const 16)
81+
)
82+
)
83+
(i32.const 0)
84+
)
85+
(func $main (; 4 ;) (type $0) (param $0 i32) (param $1 i32) (result i32)
86+
(call $__original_main)
87+
)
88+
)
89+

0 commit comments

Comments
 (0)