diff options
-rw-r--r-- | src/passes/SimplifyGlobals.cpp | 61 | ||||
-rw-r--r-- | test/lit/passes/simplify-globals-single_use.wast | 332 |
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)) +) |