diff options
-rw-r--r-- | src/passes/GlobalStructInference.cpp | 31 | ||||
-rw-r--r-- | test/lit/passes/gsi.wast | 93 | ||||
-rw-r--r-- | test/lit/passes/gsi_vacuum_precompute.wast | 97 |
3 files changed, 196 insertions, 25 deletions
diff --git a/src/passes/GlobalStructInference.cpp b/src/passes/GlobalStructInference.cpp index 4d39768e2..39761c1d6 100644 --- a/src/passes/GlobalStructInference.cpp +++ b/src/passes/GlobalStructInference.cpp @@ -16,7 +16,8 @@ // // Finds types which are only created in assignments to immutable globals. For -// such types we can replace a struct.get with this pattern: +// such types we can replace a struct.get with a global.get when there is a +// single possible global, or if there are two then with this pattern: // // (struct.get $foo i // (..ref..)) @@ -44,6 +45,8 @@ // comparison. But we can compare structs, so if the function references are in // vtables, and the vtables follow the above pattern, then we can optimize. // +// TODO: Only do the case with a select when shrinkLevel == 0? +// #include "ir/find_all.h" #include "ir/module-utils.h" @@ -210,6 +213,25 @@ struct GlobalStructInference : public Pass { return; } + const auto& globals = iter->second; + if (globals.size() == 0) { + return; + } + + auto& wasm = *getModule(); + + if (globals.size() == 1) { + // Leave it to other passes to infer the constant value of the field, + // if there is one: just change the reference to the global, which + // will unlock those other optimizations. + auto global = globals[0]; + Builder builder(wasm); + curr->ref = builder.makeSequence( + builder.makeDrop(curr->ref), + builder.makeGlobalGet(global, wasm.getGlobal(globals[0])->type)); + return; + } + // We are looking for the case where we can pick between two values // using a single comparison. More than two values, or more than a // single comparison, add tradeoffs that may not be worth it, and a @@ -229,10 +251,6 @@ struct GlobalStructInference : public Pass { // (i32.const 1337) // (i32.const 42) // (ref.eq (ref) $global2)) - const auto& globals = iter->second; - if (globals.size() < 2) { - return; - } // Find the constant values and which globals correspond to them. // TODO: SmallVectors? @@ -240,7 +258,6 @@ struct GlobalStructInference : public Pass { std::vector<std::vector<Name>> globalsForValue; // Check if the relevant fields contain constants. - auto& wasm = *getModule(); auto fieldType = field.type; for (Index i = 0; i < globals.size(); i++) { Name global = globals[i]; @@ -279,6 +296,8 @@ struct GlobalStructInference : public Pass { // value. And we have already exited if we have more than 2, so that // only leaves 1 and 2. We are looking for the case of 2 here, since // other passes (ConstantFieldPropagation) can handle 1. + // TODO: We can perhaps do better than CFP, as we know the structs are + // created in globals. if (values.size() == 1) { return; } diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast index 9b0315df3..ae54796d6 100644 --- a/test/lit/passes/gsi.wast +++ b/test/lit/passes/gsi.wast @@ -88,31 +88,70 @@ ) ) -;; Just one global. We do not optimize here - we let other passes do that. +;; Just one global. (module - ;; CHECK: (type $struct (struct_subtype (field i32) data)) - (type $struct (struct i32)) + ;; CHECK: (type $struct1 (struct_subtype (field i32) data)) + (type $struct1 (struct i32)) - ;; CHECK: (type $ref?|$struct|_=>_none (func_subtype (param (ref null $struct)) func)) + ;; CHECK: (type $struct2 (struct_subtype (field i32) data)) + (type $struct2 (struct i32)) - ;; CHECK: (global $global1 (ref $struct) (struct.new $struct + + ;; CHECK: (type $ref?|$struct1|_ref?|$struct2|_=>_none (func_subtype (param (ref null $struct1) (ref null $struct2)) func)) + + ;; CHECK: (import "a" "b" (global $imported i32)) + (import "a" "b" (global $imported i32)) + + ;; CHECK: (global $global1 (ref $struct1) (struct.new $struct1 + ;; CHECK-NEXT: (global.get $imported) + ;; CHECK-NEXT: )) + (global $global1 (ref $struct1) (struct.new $struct1 + (global.get $imported) + )) + + ;; CHECK: (global $global2 (ref $struct2) (struct.new $struct2 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: )) - (global $global1 (ref $struct) (struct.new $struct + (global $global2 (ref $struct2) (struct.new $struct2 (i32.const 42) )) - ;; CHECK: (func $test (type $ref?|$struct|_=>_none) (param $struct (ref null $struct)) + ;; CHECK: (func $test1 (type $ref?|$struct1|_ref?|$struct2|_=>_none) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (struct.get $struct1 0 + ;; CHECK-NEXT: (block (result (ref $struct1)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct2 0 + ;; CHECK-NEXT: (block (result (ref $struct2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $test (param $struct (ref null $struct)) + (func $test1 (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) + ;; We can infer that this get must reference $global1 and make the reference + ;; point to that. Note that we do not infer the value of 42 here, but leave + ;; it for other passes to do. (drop - (struct.get $struct 0 - (local.get $struct) + (struct.get $struct1 0 + (local.get $struct1) + ) + ) + ;; Even though the value here is not known at compile time - it reads an + ;; imported global - we can still infer that we are reading from $global2. + (drop + (struct.get $struct2 0 + (local.get $struct2) ) ) ) @@ -805,7 +844,12 @@ ;; CHECK: (func $test (type $ref?|$struct|_ref?|$super-struct|_=>_none) (param $struct (ref null $struct)) (param $super-struct (ref null $super-struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 0 - ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop @@ -822,9 +866,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (param $struct (ref null $struct)) (param $super-struct (ref null $super-struct)) - ;; We cannot optimize the first - it has just one global - but the second - ;; will consider the struct and sub-struct, find 2 possible values, and - ;; optimize. + ;; The first has just one global, which we switch the reference to, while + ;; the second will consider the struct and sub-struct, find 2 possible + ;; values, and optimize. (drop (struct.get $struct 0 (local.get $struct) @@ -929,12 +973,22 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct1 0 - ;; CHECK-NEXT: (local.get $struct1) + ;; CHECK-NEXT: (block (result (ref $struct1)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global1) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct2 0 - ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: (block (result (ref $struct2)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $struct2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.get $global2) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -946,7 +1000,8 @@ (local.get $super-struct) ) ) - ;; These each have one possible value, so we also do not optimize. + ;; These each have one possible value, which we can switch the references + ;; to. (drop (struct.get $struct1 0 (local.get $struct1) diff --git a/test/lit/passes/gsi_vacuum_precompute.wast b/test/lit/passes/gsi_vacuum_precompute.wast new file mode 100644 index 000000000..404de1a7e --- /dev/null +++ b/test/lit/passes/gsi_vacuum_precompute.wast @@ -0,0 +1,97 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: foreach %s %t wasm-opt --nominal --gsi --vacuum --precompute -all -S -o - | filecheck %s + +;; Test a common pattern in j2wasm where itables are differentiated by type, but +;; vtables are not. For example, the vtable might be "hashable" and provide a +;; method "getHash()" which all itables implement by an instance of that vtable. +;; Despite all those vtables having the same type, we can infer things because +;; those vtables are created as part of the immutable itables in global vars. +;; The specific optimizations used to achieve that are: +;; +;; * --gsi : Infers that a reference to a particular itable type must be a +;; global.get of the single itable defined using that type (since no +;; other object of that type is ever created). +;; * --vacuum : Cleans up some dropped stuff to enable the subsequent pass. +;; * --precompute : Represents immutable globals in a way that we can start +;; from a global.get of an itable, get a vtable, and get a +;; field in the vtable, and we end up optimizing to produce +;; the final value in that vtable. + +(module + ;; CHECK: (type $vtable (struct_subtype (field funcref) data)) + + ;; CHECK: (type $itable1 (struct_subtype (field (ref $vtable)) data)) + (type $itable1 (struct_subtype (field (ref $vtable)) data)) + ;; CHECK: (type $itable2 (struct_subtype (field (ref $vtable)) data)) + (type $itable2 (struct_subtype (field (ref $vtable)) data)) + (type $vtable (struct_subtype (field funcref) data)) + + ;; Two $vtable instances are created, in separate enclosing objects. + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $ref|$itable1|_=>_funcref (func_subtype (param (ref $itable1)) (result funcref) func)) + + ;; CHECK: (type $ref|$itable2|_=>_funcref (func_subtype (param (ref $itable2)) (result funcref) func)) + + ;; CHECK: (global $itable1 (ref $itable1) (struct.new $itable1 + ;; CHECK-NEXT: (struct.new $vtable + ;; CHECK-NEXT: (ref.func $func1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: )) + (global $itable1 (ref $itable1) (struct.new $itable1 + (struct.new $vtable + (ref.func $func1) + ) + )) + + ;; CHECK: (global $itable2 (ref $itable2) (struct.new $itable2 + ;; CHECK-NEXT: (struct.new $vtable + ;; CHECK-NEXT: (ref.func $func2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: )) + (global $itable2 (ref $itable2) (struct.new $itable2 + (struct.new $vtable + (ref.func $func2) + ) + )) + + ;; CHECK: (elem declare func $func1 $func2) + + ;; CHECK: (export "test-A" (func $test-A)) + + ;; CHECK: (export "test-B" (func $test-B)) + + ;; CHECK: (func $test-A (type $ref|$itable1|_=>_funcref) (param $ref (ref $itable1)) (result funcref) + ;; CHECK-NEXT: (ref.func $func1) + ;; CHECK-NEXT: ) + (func $test-A (export "test-A") (param $ref (ref $itable1)) (result funcref) + (struct.get $vtable 0 ;; this is a reference to $func1 + (struct.get $itable1 0 ;; this is the sub-object in the global $itable1 + (local.get $ref) ;; this can be inferred to be the global $itable1 + ) + ) + ) + + ;; CHECK: (func $test-B (type $ref|$itable2|_=>_funcref) (param $ref (ref $itable2)) (result funcref) + ;; CHECK-NEXT: (ref.func $func2) + ;; CHECK-NEXT: ) + (func $test-B (export "test-B") (param $ref (ref $itable2)) (result funcref) + (struct.get $vtable 0 ;; this is a reference to $func2 + (struct.get $itable2 0 ;; this is the sub-object in the global $itable2 + (local.get $ref) ;; this can be inferred to be the global $itable2 + ) + ) + ) + + ;; CHECK: (func $func1 (type $none_=>_none) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func1) + + ;; CHECK: (func $func2 (type $none_=>_none) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $func2) +) + |