Skip to content

Commit ec1d0b6

Browse files
authored
[Custom Descriptors] Keep trapping initializers (#7693)
Update RemoveUnusedModuleElements to preserve traps in global and element segment initializers due to null or possibly-null descriptors passed to struct.new.
1 parent 1f56a15 commit ec1d0b6

File tree

2 files changed

+148
-10
lines changed

2 files changed

+148
-10
lines changed

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
//
3737

3838
#include <memory>
39+
#include <vector>
3940

4041
#include "ir/element-utils.h"
4142
#include "ir/find_all.h"
@@ -45,8 +46,11 @@
4546
#include "ir/subtypes.h"
4647
#include "ir/utils.h"
4748
#include "pass.h"
49+
#include "support/insert_ordered.h"
4850
#include "support/stdckdint.h"
51+
#include "support/utilities.h"
4952
#include "wasm-builder.h"
53+
#include "wasm-traversal.h"
5054
#include "wasm.h"
5155

5256
namespace wasm {
@@ -712,6 +716,27 @@ struct RemoveUnusedModuleElements : public Pass {
712716
roots.emplace_back(ModuleElementKind::Function, name);
713717
});
714718

719+
// Just as out-of-bound segments may cause observable traps at instantiation
720+
// time, so can struct.new instructions with null descriptors cause traps in
721+
// global or element segment initializers.
722+
if (!getPassOptions().trapsNeverHappen) {
723+
for (auto& segment : module->elementSegments) {
724+
for (auto* init : segment->data) {
725+
if (isMaybeTrappingInit(*module, init)) {
726+
roots.emplace_back(ModuleElementKind::ElementSegment,
727+
segment->name);
728+
break;
729+
}
730+
}
731+
}
732+
for (auto& global : module->globals) {
733+
if (auto* init = global->init;
734+
init && isMaybeTrappingInit(*module, init)) {
735+
roots.emplace_back(ModuleElementKind::Global, global->name);
736+
}
737+
}
738+
}
739+
715740
// Analyze the module.
716741
auto& options = getPassOptions();
717742
Analyzer analyzer(module, options, roots);
@@ -832,6 +857,27 @@ struct RemoveUnusedModuleElements : public Pass {
832857
}
833858
}
834859
}
860+
861+
bool isMaybeTrappingInit(Module& wasm, Expression* root) {
862+
// Traverse the expression, looking for nullable descriptors passed to
863+
// struct.new. The descriptors might be null, which is the only situations
864+
// (beyond exceeded implementation limits, which we don't model) that can
865+
// lead a constant expression to trap. We depend on other optimizations to
866+
// make the descriptors non-nullable if we can determine that they are not
867+
// null.
868+
struct NullDescFinder : PostWalker<NullDescFinder> {
869+
bool mayTrap = false;
870+
void visitStructNew(StructNew* curr) {
871+
if (curr->desc && curr->desc->type.isNullable()) {
872+
mayTrap = true;
873+
}
874+
}
875+
};
876+
877+
NullDescFinder finder;
878+
finder.walk(root);
879+
return finder.mayTrap;
880+
}
835881
};
836882

837883
Pass* createRemoveUnusedModuleElementsPass() {

test/lit/passes/remove-unused-module-elements-refs-descriptors.wast

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,124 @@
66
(module
77
(rec
88
;; CHECK: (rec
9-
;; CHECK-NEXT: (type $B (sub (descriptor $A (struct))))
10-
(type $B (sub (descriptor $A (struct))))
11-
;; CHECK: (type $A (sub (describes $B (struct))))
12-
(type $A (sub (describes $B (struct))))
9+
;; CHECK-NEXT: (type $struct (descriptor $desc (struct)))
10+
(type $struct (descriptor $desc (struct)))
11+
;; CHECK: (type $desc (describes $struct (struct)))
12+
(type $desc (describes $struct (struct)))
1313
)
1414

1515
;; CHECK: (type $2 (func))
1616

17-
;; CHECK: (global $global (ref (exact $A)) (struct.new_default $A))
18-
(global $global (ref (exact $A)) (struct.new $A))
17+
;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc))
18+
(global $desc (ref (exact $desc)) (struct.new $desc))
1919

2020
;; CHECK: (export "export" (func $export))
2121

2222
;; CHECK: (func $export (type $2)
2323
;; CHECK-NEXT: (drop
24-
;; CHECK-NEXT: (struct.new_default $B
25-
;; CHECK-NEXT: (global.get $global)
24+
;; CHECK-NEXT: (struct.new_default $struct
25+
;; CHECK-NEXT: (global.get $desc)
2626
;; CHECK-NEXT: )
2727
;; CHECK-NEXT: )
2828
;; CHECK-NEXT: )
2929
(func $export (export "export")
3030
(drop
31-
(struct.new $B
32-
(global.get $global)
31+
(struct.new $struct
32+
(global.get $desc)
3333
)
3434
)
3535
)
3636
)
3737

38+
;; We cannot optimize out globals whose initializers might trap due to a null
39+
;; descriptor (or conservatively even just a nullable descriptor).
40+
(module
41+
(rec
42+
;; CHECK: (rec
43+
;; CHECK-NEXT: (type $struct (descriptor $desc (struct)))
44+
(type $struct (descriptor $desc (struct)))
45+
;; CHECK: (type $desc (describes $struct (struct)))
46+
(type $desc (describes $struct (struct)))
47+
;; CHECK: (type $list (struct (field (ref $struct)) (field (ref null $list))))
48+
(type $list (struct (field (ref $struct)) (field (ref null $list))))
49+
)
50+
51+
;; CHECK: (global $null nullref (ref.null none))
52+
(global $null nullref (ref.null none))
53+
;; CHECK: (global $nullable-desc (ref null (exact $desc)) (struct.new_default $desc))
54+
(global $nullable-desc (ref null (exact $desc)) (struct.new $desc))
55+
;; CHECK: (global $desc (ref (exact $desc)) (struct.new_default $desc))
56+
(global $desc (ref (exact $desc)) (struct.new $desc))
57+
58+
;; Trapping globals must be kept, but non-trapping globals can be removed.
59+
;; CHECK: (global $trap (ref $struct) (struct.new_default $struct
60+
;; CHECK-NEXT: (ref.null none)
61+
;; CHECK-NEXT: ))
62+
(global $trap (ref $struct) (struct.new $struct (ref.null none)))
63+
(global $no-trap (ref $struct) (struct.new $struct (struct.new $desc)))
64+
65+
;; CHECK: (global $trap-get-null (ref $struct) (struct.new_default $struct
66+
;; CHECK-NEXT: (global.get $null)
67+
;; CHECK-NEXT: ))
68+
(global $trap-get-null (ref $struct) (struct.new $struct (global.get $null)))
69+
;; CHECK: (global $trap-get-nullable (ref $struct) (struct.new_default $struct
70+
;; CHECK-NEXT: (global.get $nullable-desc)
71+
;; CHECK-NEXT: ))
72+
(global $trap-get-nullable (ref $struct) (struct.new $struct (global.get $nullable-desc)))
73+
(global $no-trap-get (ref $struct) (struct.new $struct (global.get $desc)))
74+
75+
;; CHECK: (global $trap-nested (ref $list) (struct.new $list
76+
;; CHECK-NEXT: (struct.new_default $struct
77+
;; CHECK-NEXT: (struct.new_default $desc)
78+
;; CHECK-NEXT: )
79+
;; CHECK-NEXT: (struct.new $list
80+
;; CHECK-NEXT: (struct.new_default $struct
81+
;; CHECK-NEXT: (global.get $desc)
82+
;; CHECK-NEXT: )
83+
;; CHECK-NEXT: (struct.new $list
84+
;; CHECK-NEXT: (struct.new_default $struct
85+
;; CHECK-NEXT: (ref.null none)
86+
;; CHECK-NEXT: )
87+
;; CHECK-NEXT: (ref.null none)
88+
;; CHECK-NEXT: )
89+
;; CHECK-NEXT: )
90+
;; CHECK-NEXT: ))
91+
(global $trap-nested (ref $list)
92+
(struct.new $list
93+
(struct.new $struct (struct.new $desc))
94+
(struct.new $list
95+
(struct.new $struct (global.get $desc))
96+
(struct.new $list
97+
(struct.new $struct (ref.null none))
98+
(ref.null none)
99+
)
100+
)
101+
)
102+
)
103+
104+
(global $no-trap-nested (ref $list)
105+
(struct.new $list
106+
(struct.new $struct (struct.new $desc))
107+
(struct.new $list
108+
(struct.new $struct (global.get $desc))
109+
(ref.null none)
110+
)
111+
)
112+
)
113+
114+
;; CHECK: (elem $trap anyref (item (struct.new_default $struct
115+
;; CHECK-NEXT: (ref.null none)
116+
;; CHECK-NEXT: )))
117+
(elem $trap anyref (item (struct.new $struct (ref.null none))))
118+
(elem $no-trap anyref (item (struct.new $struct (struct.new $desc))))
119+
;; CHECK: (elem $trap-get-null anyref (item (struct.new_default $struct
120+
;; CHECK-NEXT: (global.get $null)
121+
;; CHECK-NEXT: )))
122+
(elem $trap-get-null anyref (item (struct.new $struct (global.get $null))))
123+
;; CHECK: (elem $trap-get-nullable anyref (item (struct.new_default $struct
124+
;; CHECK-NEXT: (global.get $nullable-desc)
125+
;; CHECK-NEXT: )))
126+
(elem $trap-get-nullable anyref (item (struct.new $struct (global.get $nullable-desc))))
127+
(elem $no-trap-get anyref (item (struct.new $struct (global.get $desc))))
128+
)
129+

0 commit comments

Comments
 (0)