Skip to content

Commit a8dd9e8

Browse files
authored
SimplifyGlobals: If all writes write the initial value, they are unneeded (#4356)
1 parent 7f24fce commit a8dd9e8

File tree

2 files changed

+175
-10
lines changed

2 files changed

+175
-10
lines changed

src/passes/SimplifyGlobals.cpp

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// * Apply the constant values of previous global.sets, in a linear
2626
// execution trace.
2727
// * Remove writes to globals that are never read from.
28+
// * Remove writes to globals that are always assigned the same value.
2829
// * Remove writes to globals that are only read from in order to write (see
2930
// below, "readOnlyToWrite").
3031
//
@@ -60,6 +61,9 @@ struct GlobalInfo {
6061
std::atomic<Index> written{0};
6162
std::atomic<Index> read{0};
6263

64+
// Whether the global is written a value different from its initial value.
65+
std::atomic<bool> nonInitWritten{false};
66+
6367
// How many times the global is "read, but only to write", that is, is used in
6468
// this pattern:
6569
//
@@ -93,7 +97,20 @@ struct GlobalUseScanner : public WalkerPass<PostWalker<GlobalUseScanner>> {
9397

9498
GlobalUseScanner* create() override { return new GlobalUseScanner(infos); }
9599

96-
void visitGlobalSet(GlobalSet* curr) { (*infos)[curr->name].written++; }
100+
void visitGlobalSet(GlobalSet* curr) {
101+
(*infos)[curr->name].written++;
102+
103+
// Check if there is a write of a value that may differ from the initial
104+
// one. If there is anything but identical constants in both the initial
105+
// value and the written value then we must assume that.
106+
auto* global = getModule()->getGlobal(curr->name);
107+
if (global->imported() || !Properties::isConstantExpression(curr->value) ||
108+
!Properties::isConstantExpression(global->init) ||
109+
Properties::getLiterals(curr->value) !=
110+
Properties::getLiterals(global->init)) {
111+
(*infos)[curr->name].nonInitWritten = true;
112+
}
113+
}
97114

98115
void visitGlobalGet(GlobalGet* curr) { (*infos)[curr->name].read++; }
99116

@@ -394,10 +411,11 @@ struct SimplifyGlobals : public Pass {
394411
bool removeUnneededWrites() {
395412
bool more = false;
396413

397-
// Globals that are not exports and not read from are unnecessary (even if
398-
// they are written to). Likewise, globals that are only read from in order
399-
// to write to themselves are unnecessary. First, find such globals.
400-
NameSet unnecessaryGlobals;
414+
// Globals that are not exports and not read from do not need their sets.
415+
// Likewise, globals that only write their initial value later also do not
416+
// need those writes. And, globals that are only read from in order to write
417+
// to themselves as well. First, find such globals.
418+
NameSet globalsNotNeedingSets;
401419
for (auto& global : module->globals) {
402420
auto& info = map[global->name];
403421

@@ -431,15 +449,15 @@ struct SimplifyGlobals : public Pass {
431449
// our logic is wrong somewhere.
432450
assert(info.written >= info.readOnlyToWrite);
433451

434-
if (!info.read || onlyReadOnlyToWrite) {
435-
unnecessaryGlobals.insert(global->name);
452+
if (!info.read || !info.nonInitWritten || onlyReadOnlyToWrite) {
453+
globalsNotNeedingSets.insert(global->name);
436454

437455
// We can now mark this global as immutable, and un-written, since we
438-
// are about to remove all the operations on it.
456+
// are about to remove all the sets on it.
439457
global->mutable_ = false;
440458
info.written = 0;
441459

442-
// Nested old-read-to-write expressions require another full iteration
460+
// Nested only-read-to-write expressions require another full iteration
443461
// to optimize, as we have:
444462
//
445463
// if (a) {
@@ -452,6 +470,10 @@ struct SimplifyGlobals : public Pass {
452470
// The first iteration can only optimize b, as the outer if's body has
453471
// more effects than we understand. After finishing the first iteration,
454472
// b will no longer exist, removing those effects.
473+
//
474+
// TODO: In principle other situations exist as well where more
475+
// iterations help, like if we remove a set that turns something
476+
// into a read-only-to-write.
455477
if (onlyReadOnlyToWrite) {
456478
more = true;
457479
}
@@ -462,7 +484,7 @@ struct SimplifyGlobals : public Pass {
462484
// then see that since the global has no writes, it is a constant, which
463485
// will lead to removal of gets, and after removing them, the global itself
464486
// will be removed as well.
465-
GlobalSetRemover(&unnecessaryGlobals, optimize).run(runner, module);
487+
GlobalSetRemover(&globalsNotNeedingSets, optimize).run(runner, module);
466488

467489
return more;
468490
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
2+
;; NOTE: This test was ported using port_test.py and could be cleaned up.
3+
4+
;; RUN: foreach %s %t wasm-opt --simplify-globals --enable-mutable-globals -S -o - | filecheck %s
5+
6+
;; A global that is written its initial value in all subsequent writes can
7+
;; remove those writes.
8+
(module
9+
;; CHECK: (type $none_=>_none (func))
10+
11+
;; CHECK: (global $global-0 i32 (i32.const 0))
12+
(global $global-0 (mut i32) (i32.const 0))
13+
;; CHECK: (global $global-1 i32 (i32.const 1))
14+
(global $global-1 (mut i32) (i32.const 1))
15+
16+
;; CHECK: (func $sets
17+
;; CHECK-NEXT: (drop
18+
;; CHECK-NEXT: (i32.const 0)
19+
;; CHECK-NEXT: )
20+
;; CHECK-NEXT: (drop
21+
;; CHECK-NEXT: (i32.const 0)
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: (drop
24+
;; CHECK-NEXT: (i32.const 1)
25+
;; CHECK-NEXT: )
26+
;; CHECK-NEXT: (drop
27+
;; CHECK-NEXT: (i32.const 1)
28+
;; CHECK-NEXT: )
29+
;; CHECK-NEXT: )
30+
(func $sets
31+
;; All these writes can be turned into drops.
32+
33+
(global.set $global-0 (i32.const 0))
34+
(global.set $global-0 (i32.const 0))
35+
36+
(global.set $global-1 (i32.const 1))
37+
(global.set $global-1 (i32.const 1))
38+
)
39+
40+
;; CHECK: (func $gets
41+
;; CHECK-NEXT: (drop
42+
;; CHECK-NEXT: (i32.const 0)
43+
;; CHECK-NEXT: )
44+
;; CHECK-NEXT: (drop
45+
;; CHECK-NEXT: (i32.const 1)
46+
;; CHECK-NEXT: )
47+
;; CHECK-NEXT: )
48+
(func $gets
49+
;; Add gets to avoid other opts from removing the sets.
50+
(drop (global.get $global-0))
51+
(drop (global.get $global-1))
52+
)
53+
)
54+
55+
;; As above, but now we write other values.
56+
(module
57+
;; CHECK: (type $i32_=>_none (func (param i32)))
58+
59+
;; CHECK: (type $none_=>_none (func))
60+
61+
;; CHECK: (global $global-0 (mut i32) (i32.const 0))
62+
(global $global-0 (mut i32) (i32.const 0))
63+
;; CHECK: (global $global-1 (mut i32) (i32.const 1))
64+
(global $global-1 (mut i32) (i32.const 1))
65+
66+
;; CHECK: (func $sets (param $unknown i32)
67+
;; CHECK-NEXT: (global.set $global-0
68+
;; CHECK-NEXT: (i32.const 0)
69+
;; CHECK-NEXT: )
70+
;; CHECK-NEXT: (global.set $global-0
71+
;; CHECK-NEXT: (i32.const 1)
72+
;; CHECK-NEXT: )
73+
;; CHECK-NEXT: (global.set $global-1
74+
;; CHECK-NEXT: (i32.const 1)
75+
;; CHECK-NEXT: )
76+
;; CHECK-NEXT: (global.set $global-1
77+
;; CHECK-NEXT: (local.get $unknown)
78+
;; CHECK-NEXT: )
79+
;; CHECK-NEXT: )
80+
(func $sets (param $unknown i32)
81+
(global.set $global-0 (i32.const 0))
82+
(global.set $global-0 (i32.const 1)) ;; a non-init value
83+
84+
(global.set $global-1 (i32.const 1))
85+
(global.set $global-1 (local.get $unknown)) ;; a totally unknown value
86+
)
87+
88+
;; CHECK: (func $gets
89+
;; CHECK-NEXT: (drop
90+
;; CHECK-NEXT: (global.get $global-0)
91+
;; CHECK-NEXT: )
92+
;; CHECK-NEXT: (drop
93+
;; CHECK-NEXT: (global.get $global-1)
94+
;; CHECK-NEXT: )
95+
;; CHECK-NEXT: )
96+
(func $gets
97+
(drop (global.get $global-0))
98+
(drop (global.get $global-1))
99+
)
100+
)
101+
102+
;; Globals without constant initial values.
103+
(module
104+
;; An imported global.
105+
;; CHECK: (type $i32_=>_none (func (param i32)))
106+
107+
;; CHECK: (type $none_=>_none (func))
108+
109+
;; CHECK: (import "env" "import_global" (global $global-0 (mut i32)))
110+
(import "env" "import_global" (global $global-0 (mut i32)))
111+
112+
;; A global that initializes with another global.
113+
;; CHECK: (global $global-1 (mut i32) (global.get $global-0))
114+
(global $global-1 (mut i32) (global.get $global-0))
115+
116+
;; CHECK: (func $sets (param $unknown i32)
117+
;; CHECK-NEXT: (global.set $global-0
118+
;; CHECK-NEXT: (i32.const 0)
119+
;; CHECK-NEXT: )
120+
;; CHECK-NEXT: (global.set $global-1
121+
;; CHECK-NEXT: (i32.const 1)
122+
;; CHECK-NEXT: )
123+
;; CHECK-NEXT: )
124+
(func $sets (param $unknown i32)
125+
(global.set $global-0 (i32.const 0))
126+
127+
(global.set $global-1 (i32.const 1))
128+
)
129+
130+
;; CHECK: (func $gets
131+
;; CHECK-NEXT: (drop
132+
;; CHECK-NEXT: (global.get $global-0)
133+
;; CHECK-NEXT: )
134+
;; CHECK-NEXT: (drop
135+
;; CHECK-NEXT: (global.get $global-1)
136+
;; CHECK-NEXT: )
137+
;; CHECK-NEXT: )
138+
(func $gets
139+
;; Add gets to avoid other opts from removing the sets.
140+
(drop (global.get $global-0))
141+
(drop (global.get $global-1))
142+
)
143+
)

0 commit comments

Comments
 (0)