Skip to content

Commit 71ddfcf

Browse files
authored
[Stack Switching] RemoveUnusedModuleElements: Fix closed world analysis of continuation functions (WebAssembly#7841)
cont.new is a way to execute code, so we need to be aware of that, otherwise we might think a function will never be called, and turn its body unreachable.
1 parent 2ffe187 commit 71ddfcf

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

scripts/test/fuzzing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
'cont_export.wast',
120120
'cont_export_throw.wast',
121121
'type-merging-cont.wast',
122+
'remove-unused-module-elements-cont.wast',
122123
# TODO: fix split_wast() on tricky escaping situations like a string ending
123124
# in \\" (the " is not escaped - there is an escaped \ before it)
124125
'string-lifting-section.wast',

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,16 @@ struct Noter : public PostWalker<Noter, UnifiedExpressionVisitor<Noter>> {
190190
auto type = curr->ref->type.getHeapType();
191191
noteStructField(StructField{type, curr->index});
192192
}
193+
194+
void visitContNew(ContNew* curr) {
195+
// The function reference that is passed in here will be called, just as if
196+
// we were a call_ref, except at a potentially later time.
197+
if (!curr->func->type.isRef()) {
198+
return;
199+
}
200+
201+
noteCallRef(curr->func->type.getHeapType());
202+
}
193203
};
194204

195205
// Analyze a module to find what things are referenced and what things are used.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
3+
;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements --closed-world -all -S -o - | filecheck %s
4+
;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements -all -S -o - | filecheck %s --check-prefix OPEN_WORLD
5+
6+
;; Test both open world (default) and closed world. In a closed world we can do
7+
;; more with function refs, as we assume nothing calls them on the outside, so
8+
;; if no calls exist to the right type, the function is not reached.
9+
10+
(module
11+
;; CHECK: (type $func (func))
12+
;; OPEN_WORLD: (type $func (func))
13+
(type $func (func))
14+
;; CHECK: (type $cont (cont $func))
15+
;; OPEN_WORLD: (type $cont (cont $func))
16+
(type $cont (cont $func))
17+
18+
;; CHECK: (elem declare func $func)
19+
20+
;; CHECK: (tag $tag (type $func))
21+
;; OPEN_WORLD: (elem declare func $func)
22+
23+
;; OPEN_WORLD: (tag $tag (type $func))
24+
(tag $tag (type $func))
25+
26+
;; CHECK: (export "run" (func $run))
27+
28+
;; CHECK: (func $func (type $func)
29+
;; CHECK-NEXT: (nop)
30+
;; CHECK-NEXT: )
31+
;; OPEN_WORLD: (export "run" (func $run))
32+
33+
;; OPEN_WORLD: (func $func (type $func)
34+
;; OPEN_WORLD-NEXT: (nop)
35+
;; OPEN_WORLD-NEXT: )
36+
(func $func
37+
;; This function is only ever referred to by a ref.func that is passed into
38+
;; a cont.new. We should not modify this to unreachable, which we would do if
39+
;; we didn't realize it will execute (in closed world).
40+
(nop)
41+
)
42+
43+
;; CHECK: (func $run (type $func)
44+
;; CHECK-NEXT: (drop
45+
;; CHECK-NEXT: (block $block (result (ref $cont))
46+
;; CHECK-NEXT: (resume $cont (on $tag $block)
47+
;; CHECK-NEXT: (cont.new $cont
48+
;; CHECK-NEXT: (ref.func $func)
49+
;; CHECK-NEXT: )
50+
;; CHECK-NEXT: )
51+
;; CHECK-NEXT: (return)
52+
;; CHECK-NEXT: )
53+
;; CHECK-NEXT: )
54+
;; CHECK-NEXT: )
55+
;; OPEN_WORLD: (func $run (type $func)
56+
;; OPEN_WORLD-NEXT: (drop
57+
;; OPEN_WORLD-NEXT: (block $block (result (ref $cont))
58+
;; OPEN_WORLD-NEXT: (resume $cont (on $tag $block)
59+
;; OPEN_WORLD-NEXT: (cont.new $cont
60+
;; OPEN_WORLD-NEXT: (ref.func $func)
61+
;; OPEN_WORLD-NEXT: )
62+
;; OPEN_WORLD-NEXT: )
63+
;; OPEN_WORLD-NEXT: (return)
64+
;; OPEN_WORLD-NEXT: )
65+
;; OPEN_WORLD-NEXT: )
66+
;; OPEN_WORLD-NEXT: )
67+
(func $run (export "run")
68+
;; No changes are expected here.
69+
(drop
70+
(block $block (result (ref $cont))
71+
(resume $cont (on $tag $block)
72+
(cont.new $cont
73+
(ref.func $func)
74+
)
75+
)
76+
(return)
77+
)
78+
)
79+
)
80+
)
81+
82+
(module
83+
;; CHECK: (type $func (func))
84+
;; OPEN_WORLD: (type $func (func))
85+
(type $func (func))
86+
;; CHECK: (type $cont (cont $func))
87+
;; OPEN_WORLD: (type $cont (cont $func))
88+
(type $cont (cont $func))
89+
90+
;; CHECK: (tag $tag (type $func))
91+
;; OPEN_WORLD: (tag $tag (type $func))
92+
(tag $tag (type $func))
93+
94+
;; CHECK: (export "run" (func $run))
95+
96+
;; CHECK: (func $run (type $func)
97+
;; CHECK-NEXT: (drop
98+
;; CHECK-NEXT: (block $block (result (ref $cont))
99+
;; CHECK-NEXT: (resume $cont (on $tag $block)
100+
;; CHECK-NEXT: (cont.new $cont
101+
;; CHECK-NEXT: (ref.null nofunc)
102+
;; CHECK-NEXT: )
103+
;; CHECK-NEXT: )
104+
;; CHECK-NEXT: (return)
105+
;; CHECK-NEXT: )
106+
;; CHECK-NEXT: )
107+
;; CHECK-NEXT: )
108+
;; OPEN_WORLD: (export "run" (func $run))
109+
110+
;; OPEN_WORLD: (func $run (type $func)
111+
;; OPEN_WORLD-NEXT: (drop
112+
;; OPEN_WORLD-NEXT: (block $block (result (ref $cont))
113+
;; OPEN_WORLD-NEXT: (resume $cont (on $tag $block)
114+
;; OPEN_WORLD-NEXT: (cont.new $cont
115+
;; OPEN_WORLD-NEXT: (ref.null nofunc)
116+
;; OPEN_WORLD-NEXT: )
117+
;; OPEN_WORLD-NEXT: )
118+
;; OPEN_WORLD-NEXT: (return)
119+
;; OPEN_WORLD-NEXT: )
120+
;; OPEN_WORLD-NEXT: )
121+
;; OPEN_WORLD-NEXT: )
122+
(func $run (export "run")
123+
;; We should not error on a null function reference in the cont.new.
124+
(drop
125+
(block $block (result (ref $cont))
126+
(resume $cont (on $tag $block)
127+
(cont.new $cont
128+
(ref.null $func)
129+
)
130+
)
131+
(return)
132+
)
133+
)
134+
)
135+
)
136+

0 commit comments

Comments
 (0)