diff options
-rw-r--r-- | src/passes/SimplifyGlobals.cpp | 42 | ||||
-rw-r--r-- | test/lit/passes/simplify-globals-non-init.wast | 143 |
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)) + ) +) |