summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/SimplifyGlobals.cpp42
-rw-r--r--test/lit/passes/simplify-globals-non-init.wast143
2 files changed, 175 insertions, 10 deletions
diff --git a/src/passes/SimplifyGlobals.cpp b/src/passes/SimplifyGlobals.cpp
index be7cc6ca1..39414abe7 100644
--- a/src/passes/SimplifyGlobals.cpp
+++ b/src/passes/SimplifyGlobals.cpp
@@ -25,6 +25,7 @@
// * Apply the constant values of previous global.sets, in a linear
// execution trace.
// * Remove writes to globals that are never read from.
+// * Remove writes to globals that are always assigned the same value.
// * Remove writes to globals that are only read from in order to write (see
// below, "readOnlyToWrite").
//
@@ -60,6 +61,9 @@ struct GlobalInfo {
std::atomic<Index> written{0};
std::atomic<Index> read{0};
+ // Whether the global is written a value different from its initial value.
+ std::atomic<bool> nonInitWritten{false};
+
// How many times the global is "read, but only to write", that is, is used in
// this pattern:
//
@@ -93,7 +97,20 @@ struct GlobalUseScanner : public WalkerPass<PostWalker<GlobalUseScanner>> {
GlobalUseScanner* create() override { return new GlobalUseScanner(infos); }
- void visitGlobalSet(GlobalSet* curr) { (*infos)[curr->name].written++; }
+ void visitGlobalSet(GlobalSet* curr) {
+ (*infos)[curr->name].written++;
+
+ // Check if there is a write of a value that may differ from the initial
+ // one. If there is anything but identical constants in both the initial
+ // value and the written value then we must assume that.
+ auto* global = getModule()->getGlobal(curr->name);
+ if (global->imported() || !Properties::isConstantExpression(curr->value) ||
+ !Properties::isConstantExpression(global->init) ||
+ Properties::getLiterals(curr->value) !=
+ Properties::getLiterals(global->init)) {
+ (*infos)[curr->name].nonInitWritten = true;
+ }
+ }
void visitGlobalGet(GlobalGet* curr) { (*infos)[curr->name].read++; }
@@ -394,10 +411,11 @@ struct SimplifyGlobals : public Pass {
bool removeUnneededWrites() {
bool more = false;
- // Globals that are not exports and not read from are unnecessary (even if
- // they are written to). Likewise, globals that are only read from in order
- // to write to themselves are unnecessary. First, find such globals.
- NameSet unnecessaryGlobals;
+ // Globals that are not exports and not read from do not need their sets.
+ // Likewise, globals that only write their initial value later also do not
+ // need those writes. And, globals that are only read from in order to write
+ // to themselves as well. First, find such globals.
+ NameSet globalsNotNeedingSets;
for (auto& global : module->globals) {
auto& info = map[global->name];
@@ -431,15 +449,15 @@ struct SimplifyGlobals : public Pass {
// our logic is wrong somewhere.
assert(info.written >= info.readOnlyToWrite);
- if (!info.read || onlyReadOnlyToWrite) {
- unnecessaryGlobals.insert(global->name);
+ if (!info.read || !info.nonInitWritten || onlyReadOnlyToWrite) {
+ globalsNotNeedingSets.insert(global->name);
// We can now mark this global as immutable, and un-written, since we
- // are about to remove all the operations on it.
+ // are about to remove all the sets on it.
global->mutable_ = false;
info.written = 0;
- // Nested old-read-to-write expressions require another full iteration
+ // Nested only-read-to-write expressions require another full iteration
// to optimize, as we have:
//
// if (a) {
@@ -452,6 +470,10 @@ struct SimplifyGlobals : public Pass {
// The first iteration can only optimize b, as the outer if's body has
// more effects than we understand. After finishing the first iteration,
// b will no longer exist, removing those effects.
+ //
+ // TODO: In principle other situations exist as well where more
+ // iterations help, like if we remove a set that turns something
+ // into a read-only-to-write.
if (onlyReadOnlyToWrite) {
more = true;
}
@@ -462,7 +484,7 @@ struct SimplifyGlobals : public Pass {
// then see that since the global has no writes, it is a constant, which
// will lead to removal of gets, and after removing them, the global itself
// will be removed as well.
- GlobalSetRemover(&unnecessaryGlobals, optimize).run(runner, module);
+ GlobalSetRemover(&globalsNotNeedingSets, optimize).run(runner, module);
return more;
}
diff --git a/test/lit/passes/simplify-globals-non-init.wast b/test/lit/passes/simplify-globals-non-init.wast
new file mode 100644
index 000000000..9e3230319
--- /dev/null
+++ b/test/lit/passes/simplify-globals-non-init.wast
@@ -0,0 +1,143 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_test.py and could be cleaned up.
+
+;; RUN: foreach %s %t wasm-opt --simplify-globals --enable-mutable-globals -S -o - | filecheck %s
+
+;; A global that is written its initial value in all subsequent writes can
+;; remove those writes.
+(module
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $global-0 i32 (i32.const 0))
+ (global $global-0 (mut i32) (i32.const 0))
+ ;; CHECK: (global $global-1 i32 (i32.const 1))
+ (global $global-1 (mut i32) (i32.const 1))
+
+ ;; CHECK: (func $sets
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $sets
+ ;; All these writes can be turned into drops.
+
+ (global.set $global-0 (i32.const 0))
+ (global.set $global-0 (i32.const 0))
+
+ (global.set $global-1 (i32.const 1))
+ (global.set $global-1 (i32.const 1))
+ )
+
+ ;; CHECK: (func $gets
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $gets
+ ;; Add gets to avoid other opts from removing the sets.
+ (drop (global.get $global-0))
+ (drop (global.get $global-1))
+ )
+)
+
+;; As above, but now we write other values.
+(module
+ ;; CHECK: (type $i32_=>_none (func (param i32)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (global $global-0 (mut i32) (i32.const 0))
+ (global $global-0 (mut i32) (i32.const 0))
+ ;; CHECK: (global $global-1 (mut i32) (i32.const 1))
+ (global $global-1 (mut i32) (i32.const 1))
+
+ ;; CHECK: (func $sets (param $unknown i32)
+ ;; CHECK-NEXT: (global.set $global-0
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $global-0
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $global-1
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $global-1
+ ;; CHECK-NEXT: (local.get $unknown)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $sets (param $unknown i32)
+ (global.set $global-0 (i32.const 0))
+ (global.set $global-0 (i32.const 1)) ;; a non-init value
+
+ (global.set $global-1 (i32.const 1))
+ (global.set $global-1 (local.get $unknown)) ;; a totally unknown value
+ )
+
+ ;; CHECK: (func $gets
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $global-0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $global-1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $gets
+ (drop (global.get $global-0))
+ (drop (global.get $global-1))
+ )
+)
+
+;; Globals without constant initial values.
+(module
+ ;; An imported global.
+ ;; CHECK: (type $i32_=>_none (func (param i32)))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (import "env" "import_global" (global $global-0 (mut i32)))
+ (import "env" "import_global" (global $global-0 (mut i32)))
+
+ ;; A global that initializes with another global.
+ ;; CHECK: (global $global-1 (mut i32) (global.get $global-0))
+ (global $global-1 (mut i32) (global.get $global-0))
+
+ ;; CHECK: (func $sets (param $unknown i32)
+ ;; CHECK-NEXT: (global.set $global-0
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $global-1
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $sets (param $unknown i32)
+ (global.set $global-0 (i32.const 0))
+
+ (global.set $global-1 (i32.const 1))
+ )
+
+ ;; CHECK: (func $gets
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $global-0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $global-1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $gets
+ ;; Add gets to avoid other opts from removing the sets.
+ (drop (global.get $global-0))
+ (drop (global.get $global-1))
+ )
+)