summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/GlobalStructInference.cpp31
-rw-r--r--test/lit/passes/gsi.wast93
-rw-r--r--test/lit/passes/gsi_vacuum_precompute.wast97
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)
+)
+