Skip to content

Commit 76751bf

Browse files
authored
Polymophic stack support (#1117)
Emit valid wasm binaries even for corner cases of unreachable code. * emit an unreachable after a node that pushes a value that has unreachable type (where wasm type checking would have pushed a concrete type) * conversely, as a hack, emulate the wasm polymorphic stack mode by not emptying the stack when it has one element and that element is unreachable. this lets further pops work (all returning an unreachable element)
1 parent 4d46a7e commit 76751bf

7 files changed

Lines changed: 484 additions & 1 deletion

src/wasm/wasm-binary.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,16 @@ void WasmBinaryWriter::visitBreak(Break *curr) {
648648
if (curr->condition) recurse(curr->condition);
649649
o << int8_t(curr->condition ? BinaryConsts::BrIf : BinaryConsts::Br)
650650
<< U32LEB(getBreakIndex(curr->name));
651+
if (curr->condition && curr->type == unreachable) {
652+
// a br_if is normally none or emits a value. if it is unreachable,
653+
// then either the condition or the value is unreachable, which is
654+
// extremely rare, and may require us to make the stack polymorphic
655+
// (if the block we branch to has a value, we may lack one as we
656+
// are not a taken branch; the wasm spec on the other hand does
657+
// presume the br_if emits a value of the right type, even if it
658+
// popped unreachable)
659+
o << int8_t(BinaryConsts::Unreachable);
660+
}
651661
}
652662

653663
void WasmBinaryWriter::visitSwitch(Switch *curr) {
@@ -669,6 +679,9 @@ void WasmBinaryWriter::visitCall(Call *curr) {
669679
recurse(operand);
670680
}
671681
o << int8_t(BinaryConsts::CallFunction) << U32LEB(getFunctionIndex(curr->target));
682+
if (curr->type == unreachable) {
683+
o << int8_t(BinaryConsts::Unreachable);
684+
}
672685
}
673686

674687
void WasmBinaryWriter::visitCallImport(CallImport *curr) {
@@ -689,6 +702,9 @@ void WasmBinaryWriter::visitCallIndirect(CallIndirect *curr) {
689702
o << int8_t(BinaryConsts::CallIndirect)
690703
<< U32LEB(getFunctionTypeIndex(curr->fullType))
691704
<< U32LEB(0); // Reserved flags field
705+
if (curr->type == unreachable) {
706+
o << int8_t(BinaryConsts::Unreachable);
707+
}
692708
}
693709

694710
void WasmBinaryWriter::visitGetLocal(GetLocal *curr) {
@@ -700,6 +716,9 @@ void WasmBinaryWriter::visitSetLocal(SetLocal *curr) {
700716
if (debug) std::cerr << "zz node: Set|TeeLocal" << std::endl;
701717
recurse(curr->value);
702718
o << int8_t(curr->isTee() ? BinaryConsts::TeeLocal : BinaryConsts::SetLocal) << U32LEB(mappedLocals[curr->index]);
719+
if (curr->type == unreachable) {
720+
o << int8_t(BinaryConsts::Unreachable);
721+
}
703722
}
704723

705724
void WasmBinaryWriter::visitGetGlobal(GetGlobal *curr) {
@@ -986,6 +1005,9 @@ void WasmBinaryWriter::visitUnary(Unary *curr) {
9861005
case ReinterpretInt64: o << int8_t(BinaryConsts::F64ReinterpretI64); break;
9871006
default: abort();
9881007
}
1008+
if (curr->type == unreachable) {
1009+
o << int8_t(BinaryConsts::Unreachable);
1010+
}
9891011
}
9901012

9911013
void WasmBinaryWriter::visitBinary(Binary *curr) {
@@ -1075,6 +1097,9 @@ void WasmBinaryWriter::visitBinary(Binary *curr) {
10751097
case GeFloat64: o << int8_t(BinaryConsts::F64Ge); break;
10761098
default: abort();
10771099
}
1100+
if (curr->type == unreachable) {
1101+
o << int8_t(BinaryConsts::Unreachable);
1102+
}
10781103
}
10791104

10801105
void WasmBinaryWriter::visitSelect(Select *curr) {
@@ -1083,6 +1108,9 @@ void WasmBinaryWriter::visitSelect(Select *curr) {
10831108
recurse(curr->ifFalse);
10841109
recurse(curr->condition);
10851110
o << int8_t(BinaryConsts::Select);
1111+
if (curr->type == unreachable) {
1112+
o << int8_t(BinaryConsts::Unreachable);
1113+
}
10861114
}
10871115

10881116
void WasmBinaryWriter::visitReturn(Return *curr) {
@@ -1771,7 +1799,11 @@ Expression* WasmBinaryBuilder::popExpression() {
17711799
throw ParseException("attempted pop from empty stack");
17721800
}
17731801
auto ret = expressionStack.back();
1774-
expressionStack.pop_back();
1802+
// to simulate the wasm polymorphic stack mode, leave a final
1803+
// unreachable, don't empty the stack in that case
1804+
if (!(expressionStack.size() == 1 && ret->type == unreachable)) {
1805+
expressionStack.pop_back();
1806+
}
17751807
return ret;
17761808
}
17771809

test/polymorphic_stack.wast

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
(module
2+
(type $FUNCSIG$ii (func (param i32) (result i32)))
3+
(import "env" "table" (table 9 9 anyfunc))
4+
(func $break-and-binary (result i32)
5+
(block $x (result i32)
6+
(f32.add
7+
(br_if $x
8+
(i32.trunc_u/f64
9+
(unreachable)
10+
)
11+
(i32.trunc_u/f64
12+
(unreachable)
13+
)
14+
)
15+
(f32.const 1)
16+
)
17+
)
18+
)
19+
(func $call-and-unary (param i32) (result i32)
20+
(drop
21+
(i64.eqz
22+
(call $call-and-unary
23+
(unreachable)
24+
)
25+
)
26+
)
27+
(drop
28+
(i64.eqz
29+
(i32.eqz
30+
(unreachable)
31+
)
32+
)
33+
)
34+
(drop
35+
(i64.eqz
36+
(call_indirect $FUNCSIG$ii
37+
(unreachable)
38+
(unreachable)
39+
)
40+
)
41+
)
42+
)
43+
(func $tee (param $x i32)
44+
(local $y f32)
45+
(drop
46+
(i64.eqz
47+
(tee_local $x
48+
(unreachable)
49+
)
50+
)
51+
)
52+
(drop
53+
(tee_local $y
54+
(i64.eqz
55+
(unreachable)
56+
)
57+
)
58+
)
59+
)
60+
(func $tee2
61+
(local $0 f32)
62+
(if
63+
(i32.const 259)
64+
(set_local $0
65+
(unreachable)
66+
)
67+
)
68+
)
69+
(func $select
70+
(drop
71+
(i64.eqz
72+
(select
73+
(unreachable)
74+
(i32.const 1)
75+
(i32.const 2)
76+
)
77+
)
78+
)
79+
)
80+
(func $untaken-break-should-have-value (result i32)
81+
(block $x (result i32)
82+
(block
83+
(br_if $x ;; ok to not have a value, since an untaken branch. but must emit valid binary for wasm
84+
(unreachable)
85+
)
86+
)
87+
)
88+
)
89+
)
90+
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
(module
2+
(type $FUNCSIG$ii (func (param i32) (result i32)))
3+
(type $1 (func (result i32)))
4+
(type $2 (func (param i32)))
5+
(type $3 (func))
6+
(import "env" "table" (table 9 9 anyfunc))
7+
(memory $0 0)
8+
(func $break-and-binary (type $1) (result i32)
9+
(block $x (result i32)
10+
(f32.add
11+
(br_if $x
12+
(i32.trunc_u/f64
13+
(unreachable)
14+
)
15+
(i32.trunc_u/f64
16+
(unreachable)
17+
)
18+
)
19+
(f32.const 1)
20+
)
21+
)
22+
)
23+
(func $call-and-unary (type $FUNCSIG$ii) (param $0 i32) (result i32)
24+
(drop
25+
(i64.eqz
26+
(call $call-and-unary
27+
(unreachable)
28+
)
29+
)
30+
)
31+
(drop
32+
(i64.eqz
33+
(i32.eqz
34+
(unreachable)
35+
)
36+
)
37+
)
38+
(drop
39+
(i64.eqz
40+
(call_indirect $FUNCSIG$ii
41+
(unreachable)
42+
(unreachable)
43+
)
44+
)
45+
)
46+
)
47+
(func $tee (type $2) (param $x i32)
48+
(local $y f32)
49+
(drop
50+
(i64.eqz
51+
(tee_local $x
52+
(unreachable)
53+
)
54+
)
55+
)
56+
(drop
57+
(tee_local $y
58+
(i64.eqz
59+
(unreachable)
60+
)
61+
)
62+
)
63+
)
64+
(func $tee2 (type $3)
65+
(local $0 f32)
66+
(if
67+
(i32.const 259)
68+
(tee_local $0
69+
(unreachable)
70+
)
71+
)
72+
)
73+
(func $select (type $3)
74+
(drop
75+
(i64.eqz
76+
(select
77+
(unreachable)
78+
(i32.const 1)
79+
(i32.const 2)
80+
)
81+
)
82+
)
83+
)
84+
(func $untaken-break-should-have-value (type $1) (result i32)
85+
(block $x (result i32)
86+
(block $block
87+
(br_if $x
88+
(unreachable)
89+
)
90+
)
91+
)
92+
)
93+
)

0 commit comments

Comments
 (0)