summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/SimplifyGlobals.cpp61
-rw-r--r--test/lit/passes/simplify-globals-single_use.wast332
2 files changed, 393 insertions, 0 deletions
diff --git a/src/passes/SimplifyGlobals.cpp b/src/passes/SimplifyGlobals.cpp
index 02e685ca7..0e66b5926 100644
--- a/src/passes/SimplifyGlobals.cpp
+++ b/src/passes/SimplifyGlobals.cpp
@@ -477,6 +477,11 @@ struct SimplifyGlobals : public Pass {
bool iteration() {
analyze();
+ // Fold single uses first, as it is simple to update the info from analyze()
+ // in this code (and harder to do in the things we do later, which is why we
+ // call analyze from scratch in each iteration).
+ foldSingleUses();
+
// Removing unneeded writes can in some cases lead to more optimizations
// that we need an entire additional iteration to perform, see below.
bool more = removeUnneededWrites();
@@ -672,6 +677,62 @@ struct SimplifyGlobals : public Pass {
}
ConstantGlobalApplier(&constantGlobals, optimize)
.run(getPassRunner(), module);
+ // Note that we don't need to run on module code here, since we already
+ // handle applying constants in globals in propagateConstantsToGlobals (and
+ // in a more sophisticated manner, which takes into account that no sets of
+ // globals are possible during global instantiation).
+ }
+
+ // If we have a global that has a single use in the entire program, we can
+ // fold it into that use, if it is global. For example:
+ //
+ // var x = { foo: 5 };
+ // var y = { bar: x };
+ //
+ // This can become:
+ //
+ // var y = { bar: { foo: 5 } };
+ //
+ // If there is more than one use, or the use is in a function (where it might
+ // execute more than once) then we can't do this.
+ void foldSingleUses() {
+ struct Folder : public PostWalker<Folder> {
+ Module& wasm;
+ GlobalInfoMap& infos;
+
+ Folder(Module& wasm, GlobalInfoMap& infos) : wasm(wasm), infos(infos) {}
+
+ void visitGlobalGet(GlobalGet* curr) {
+ // If this is a get of a global with a single get and no sets, then we
+ // can fold that code into here.
+ auto name = curr->name;
+ auto& info = infos[name];
+ if (info.written == 0 && info.read == 1) {
+ auto* global = wasm.getGlobal(name);
+ if (global->init) {
+ // Copy that global's code. For simplicity we copy it as we have to
+ // keep that global valid for the operations that happen after us,
+ // even though that global will be removed later (we could remove it
+ // here, but it would add more complexity than seems worth it).
+ replaceCurrent(ExpressionManipulator::copy(global->init, wasm));
+
+ // Update info for later parts of this pass: we are removing a
+ // global.get, which is a read, so now there are 0 reads (we also
+ // have 0 writes, so no other work is needed here, but update to
+ // avoid confusion when debugging, and for possible future changes).
+ info.read = 0;
+ }
+ }
+ }
+ };
+
+ Folder folder(*module, map);
+
+ for (auto& global : module->globals) {
+ if (global->init) {
+ folder.walk(global->init);
+ }
+ }
}
};
diff --git a/test/lit/passes/simplify-globals-single_use.wast b/test/lit/passes/simplify-globals-single_use.wast
new file mode 100644
index 000000000..daba2588d
--- /dev/null
+++ b/test/lit/passes/simplify-globals-single_use.wast
@@ -0,0 +1,332 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; NOTE: This test was ported using port_passes_tests_to_lit.py and could be cleaned up.
+
+;; RUN: foreach %s %t wasm-opt -all --simplify-globals -S -o - | filecheck %s
+
+;; $single-use has a single use, and we can fold a copy of that code into its
+;; use. That global then becomes unused.
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (global $single-use anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ ))
+
+ ;; CHECK: (global $other (mut anyref) (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other (mut anyref) (global.get $single-use))
+
+ ;; CHECK: (func $user (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $other)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $other
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $user
+ ;; Uses of $other do not affect optimization
+ (drop
+ (global.get $other)
+ )
+ (global.set $other
+ (ref.null any)
+ )
+ )
+)
+
+;; As above, but now there is a second use, so we do nothing.
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (global $single-use anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ ))
+
+ ;; CHECK: (global $other anyref (global.get $single-use))
+ (global $other anyref (global.get $single-use))
+
+ ;; CHECK: (global $other2 anyref (global.get $single-use))
+ (global $other2 anyref (global.get $single-use))
+)
+
+;; As the first testcase, but now there is a second use in function code, so
+;; again we do nothing.
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (global $single-use anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ ))
+
+ ;; CHECK: (global $other anyref (global.get $single-use))
+ (global $other anyref (global.get $single-use))
+
+ ;; CHECK: (func $user (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $single-use)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $user
+ (drop
+ (global.get $single-use)
+ )
+ )
+)
+
+;; As the first testcase, but now $single-use is imported, so there is no code
+;; to fold.
+(module
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (import "a" "b" (global $single-use anyref))
+ (import "a" "b" (global $single-use anyref))
+
+ ;; CHECK: (global $other anyref (global.get $single-use))
+ (global $other anyref (global.get $single-use))
+)
+
+;; As the first testcase, but here the single use is in function code, so
+;; we do nothing (as it could be executed more than once).
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (type $1 (func))
+
+ ;; CHECK: (global $single-use anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ ))
+
+ ;; CHECK: (func $user (type $1)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $single-use)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $user
+ (drop
+ (global.get $single-use)
+ )
+ )
+)
+
+;; As the first testcase, but now the single use is nested in other code. We
+;; can still optimize.
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (global $single-use anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ ))
+
+ ;; CHECK: (global $other anyref (struct.new $A
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other anyref (struct.new $A
+ (global.get $single-use)
+ ))
+)
+
+;; Test multiple separate optimizations in one module.
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (global $single-use1 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use1 anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ ))
+
+ ;; CHECK: (global $single-use2 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use2 anyref (struct.new $A
+ (i31.new
+ (i32.const 1337)
+ )
+ ))
+
+ ;; CHECK: (global $other1 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other1 anyref (global.get $single-use1))
+
+ ;; CHECK: (global $single-use3 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 99999)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use3 anyref (struct.new $A
+ (i31.new
+ (i32.const 99999)
+ )
+ ))
+
+ ;; CHECK: (global $other2 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other2 anyref (global.get $single-use2))
+
+ ;; CHECK: (global $other3 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 99999)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other3 anyref (global.get $single-use3))
+)
+
+;; Test multiple related optimizations in one module: more than one nesting into
+;; one global
+(module
+ ;; CHECK: (type $A (struct (field anyref) (field anyref)))
+ (type $A (struct (field anyref) (field anyref)))
+
+ ;; CHECK: (global $single-use1 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: ))
+ (global $single-use1 anyref (struct.new $A
+ (i31.new
+ (i32.const 42)
+ )
+ (ref.null any)
+ ))
+
+ ;; CHECK: (global $single-use2 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $single-use2 anyref (struct.new $A
+ (ref.null any)
+ (i31.new
+ (i32.const 1337)
+ )
+ ))
+
+ ;; CHECK: (global $other anyref (struct.new $A
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other anyref (struct.new $A
+ (global.get $single-use1)
+ (global.get $single-use2)
+ ))
+)
+
+;; Test multiple related optimizations in one module: a chain.
+(module
+ ;; CHECK: (type $A (struct (field anyref)))
+ (type $A (struct (field anyref)))
+
+ ;; CHECK: (global $single-use1 anyref (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: ))
+ (global $single-use1 anyref (i31.new
+ (i32.const 42)
+ ))
+
+ ;; CHECK: (global $other1 anyref (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other1 anyref (struct.new $A
+ (global.get $single-use1)
+ ))
+
+ ;; CHECK: (global $other2 anyref (struct.new $A
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other2 anyref (struct.new $A
+ (global.get $other1)
+ ))
+
+ ;; CHECK: (global $other3 anyref (struct.new $A
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (ref.i31
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $other3 anyref (global.get $other2))
+)