summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2022-12-07 10:01:07 -0800
committerGitHub <noreply@github.com>2022-12-07 10:01:07 -0800
commit3253df552729e771a31decf0e29a775f888b1e6f (patch)
tree8a4855b386015078ab438c093d6909bfd4da5c88
parent01fbf2287ed7f2d0f31f8c255b02ae1fc77ad9ca (diff)
downloadbinaryen-3253df552729e771a31decf0e29a775f888b1e6f.tar.gz
binaryen-3253df552729e771a31decf0e29a775f888b1e6f.tar.bz2
binaryen-3253df552729e771a31decf0e29a775f888b1e6f.zip
[Wasm GC] Add array support to TypeSSA (#5327)
Previously it only handled structs.
-rw-r--r--src/passes/TypeSSA.cpp116
-rw-r--r--test/lit/passes/type-ssa.wast230
2 files changed, 309 insertions, 37 deletions
diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp
index 75c2d4651..e25795cdc 100644
--- a/src/passes/TypeSSA.cpp
+++ b/src/passes/TypeSSA.cpp
@@ -57,17 +57,16 @@ namespace wasm {
namespace {
-struct News {
- std::vector<StructNew*> structNews;
- // TODO: arrayNews of all kinds
-};
+// A vector of struct.new or one of the variations on array.new.
+using News = std::vector<Expression*>;
struct NewFinder : public PostWalker<NewFinder> {
News news;
- void visitStructNew(StructNew* curr) { news.structNews.push_back(curr); }
-
- // TODO arrays
+ void visitStructNew(StructNew* curr) { news.push_back(curr); }
+ void visitArrayNew(ArrayNew* curr) { news.push_back(curr); }
+ void visitArrayNewSeg(ArrayNewSeg* curr) { news.push_back(curr); }
+ void visitArrayInit(ArrayInit* curr) { news.push_back(curr); }
};
struct TypeSSA : public Pass {
@@ -118,16 +117,15 @@ struct TypeSSA : public Pass {
Index nameCounter = 0;
void processNews(const News& news) {
- for (auto* curr : news.structNews) {
+ for (auto* curr : news) {
if (isInteresting(curr)) {
- newsToModify.structNews.push_back(curr);
+ newsToModify.push_back(curr);
}
}
}
void modifyNews() {
- auto& structNews = newsToModify.structNews;
- auto num = structNews.size();
+ auto num = newsToModify.size();
if (num == 0) {
return;
}
@@ -148,9 +146,13 @@ struct TypeSSA : public Pass {
TypeBuilder builder(num);
for (Index i = 0; i < num; i++) {
- auto* curr = structNews[i];
+ auto* curr = newsToModify[i];
auto oldType = curr->type.getHeapType();
- builder[i] = oldType.getStruct();
+ if (oldType.isStruct()) {
+ builder[i] = oldType.getStruct();
+ } else {
+ builder[i] = oldType.getArray();
+ }
builder[i].subTypeOf(oldType);
}
builder.createRecGroup(0, num);
@@ -192,7 +194,7 @@ struct TypeSSA : public Pass {
}
for (Index i = 0; i < num; i++) {
- auto* curr = structNews[i];
+ auto* curr = newsToModify[i];
auto oldType = curr->type.getHeapType();
auto newType = newTypes[i];
curr->type = Type(newType, NonNullable);
@@ -210,41 +212,28 @@ struct TypeSSA : public Pass {
existingTypeNames.insert(newName);
}
}
-
- // TODO: arrays
}
- // An interesting StructNew, which we think is worth creating a new type for,
- // is one that can be optimized better with a new type. That means it must
- // have something interesting for optimizations to work with.
+ // An interesting *.new, which we think is worth creating a new type for, is
+ // one that can be optimized better with a new type. That means it must have
+ // something interesting for optimizations to work with.
//
// TODO: We may add new optimizations in the future that can benefit from more
// things, so it may be interesting to experiment with considering all
// news as "interesting" when we add major new type-based optimization
// passes.
- bool isInteresting(StructNew* curr) {
+ bool isInteresting(Expression* curr) {
if (curr->type == Type::unreachable) {
// This is dead code anyhow.
return false;
}
- if (curr->isWithDefault()) {
- // This starts with all default values - zeros and nulls - and that might
- // be useful.
- //
- // (A struct whose fields are all bottom types only has a single possible
- // value in each field anyhow, so that is not interesting, but also
- // unreasonable to occur in practice as other optimizations should handle
- // it.)
- return true;
- }
-
- // Look for at least one interesting operand.
- auto& fields = curr->type.getHeapType().getStruct().fields;
- for (Index i = 0; i < fields.size(); i++) {
- assert(i <= curr->operands.size());
- auto* operand = curr->operands[i];
- if (operand->type != fields[i].type) {
+ // Look for at least one interesting operand. We will consider each operand
+ // against the declared type, that is, the type declared for where it is
+ // stored. If it has more information than the declared type then it is
+ // interesting.
+ auto isInterestingRelevantTo = [&](Expression* operand, Type declaredType) {
+ if (operand->type != declaredType) {
// Excellent, this field has an interesting type - more refined than the
// declared type, and which optimizations might benefit from.
//
@@ -261,6 +250,59 @@ struct TypeSSA : public Pass {
// This is a constant that passes may benefit from.
return true;
}
+
+ return false;
+ };
+
+ auto type = curr->type.getHeapType();
+
+ if (auto* structNew = curr->dynCast<StructNew>()) {
+ if (structNew->isWithDefault()) {
+ // This starts with all default values - zeros and nulls - and that
+ // might be useful.
+ //
+ // (An item whose fields are all bottom types only has a single possible
+ // value in each field anyhow, so that is not interesting, but also
+ // unreasonable to occur in practice as other optimizations should
+ // handle it.)
+ return true;
+ }
+
+ auto& fields = type.getStruct().fields;
+ for (Index i = 0; i < fields.size(); i++) {
+ assert(i <= structNew->operands.size());
+ if (isInterestingRelevantTo(structNew->operands[i], fields[i].type)) {
+ return true;
+ }
+ }
+ } else if (auto* arrayNew = curr->dynCast<ArrayNew>()) {
+ if (arrayNew->isWithDefault()) {
+ return true;
+ }
+
+ auto element = type.getArray().element;
+ if (isInterestingRelevantTo(arrayNew->init, element.type)) {
+ return true;
+ }
+ } else if (curr->is<ArrayNewSeg>()) {
+ // TODO: If the element segment is immutable perhaps we could inspect it.
+ return true;
+ } else if (auto* arrayInit = curr->dynCast<ArrayInit>()) {
+ // All the items must be interesting for us to consider this interesting,
+ // as we only track a single value for all indexes in the array, so one
+ // boring value means it is all boring.
+ //
+ // Note that we consider the empty array to be interesting (though atm no
+ // pass tracks the length - we might add one later though).
+ auto element = type.getArray().element;
+ for (auto* value : arrayInit->values) {
+ if (!isInterestingRelevantTo(value, element.type)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ WASM_UNREACHABLE("unknown new");
}
// Nothing interesting.
diff --git a/test/lit/passes/type-ssa.wast b/test/lit/passes/type-ssa.wast
index f799cd871..790c6488a 100644
--- a/test/lit/passes/type-ssa.wast
+++ b/test/lit/passes/type-ssa.wast
@@ -219,3 +219,233 @@
)
)
)
+
+(module
+ ;; CHECK: (type $array (array (mut anyref)))
+ ;; NOMNL: (type $array (array (mut anyref)))
+ (type $array (array (mut (ref null any))))
+
+ ;; CHECK: (type $ref|i31|_anyref_=>_none (func (param (ref i31) anyref)))
+
+ ;; CHECK: (type $array-func (array (mut funcref)))
+ ;; NOMNL: (type $ref|i31|_anyref_=>_none (func (param (ref i31) anyref)))
+
+ ;; NOMNL: (type $array$1 (array_subtype (mut anyref) $array))
+
+ ;; NOMNL: (type $array$2 (array_subtype (mut anyref) $array))
+
+ ;; NOMNL: (type $array$3 (array_subtype (mut anyref) $array))
+
+ ;; NOMNL: (type $none_=>_none (func))
+
+ ;; NOMNL: (type $array-func (array (mut funcref)))
+ (type $array-func (array (mut funcref)))
+
+
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $array$1 (array_subtype (mut anyref) $array))
+
+ ;; CHECK: (type $array$2 (array_subtype (mut anyref) $array))
+
+ ;; CHECK: (type $array$3 (array_subtype (mut anyref) $array))
+
+ ;; CHECK: (type $array-func$4 (array_subtype (mut funcref) $array-func))
+
+ ;; CHECK: (type $array$5 (array_subtype (mut anyref) $array))
+
+ ;; CHECK: (type $array$6 (array_subtype (mut anyref) $array))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (elem func $array.new)
+ ;; NOMNL: (type $array-func$4 (array_subtype (mut funcref) $array-func))
+
+ ;; NOMNL: (type $array$5 (array_subtype (mut anyref) $array))
+
+ ;; NOMNL: (type $array$6 (array_subtype (mut anyref) $array))
+
+ ;; NOMNL: (elem func $array.new)
+ (elem func $array.new)
+
+ ;; CHECK: (func $array.new (type $ref|i31|_anyref_=>_none) (param $refined (ref i31)) (param $null-any anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.new_default $array$1
+ ;; CHECK-NEXT: (i32.const 5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.new $array$2
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: (i32.const 5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.new $array$3
+ ;; CHECK-NEXT: (local.get $refined)
+ ;; CHECK-NEXT: (i32.const 5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.new $array
+ ;; CHECK-NEXT: (local.get $null-any)
+ ;; CHECK-NEXT: (i32.const 5)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $array.new (type $ref|i31|_anyref_=>_none) (param $refined (ref i31)) (param $null-any anyref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.new_default $array$1
+ ;; NOMNL-NEXT: (i32.const 5)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.new $array$2
+ ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: (i32.const 5)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.new $array$3
+ ;; NOMNL-NEXT: (local.get $refined)
+ ;; NOMNL-NEXT: (i32.const 5)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.new $array
+ ;; NOMNL-NEXT: (local.get $null-any)
+ ;; NOMNL-NEXT: (i32.const 5)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $array.new (param $refined (ref i31)) (param $null-any (ref null any))
+ ;; Default null, an interesting value, so we get a new type.
+ (drop
+ (array.new_default $array
+ (i32.const 5)
+ )
+ )
+ ;; Given null, also interesting.
+ (drop
+ (array.new $array
+ (ref.null none)
+ (i32.const 5)
+ )
+ )
+ ;; More refined type, interesting.
+ (drop
+ (array.new $array
+ (local.get $refined)
+ (i32.const 5)
+ )
+ )
+ ;; Same type as declared - boring, no new type.
+ (drop
+ (array.new $array
+ (local.get $null-any)
+ (i32.const 5)
+ )
+ )
+ )
+
+ ;; CHECK: (func $array.new_seg (type $none_=>_none)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.new_elem $array-func$4 0
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $array.new_seg (type $none_=>_none)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.new_elem $array-func$4 0
+ ;; NOMNL-NEXT: (i32.const 0)
+ ;; NOMNL-NEXT: (i32.const 3)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $array.new_seg
+ ;; We consider all new_elem to be interesting as we don't look at the elem
+ ;; data yet.
+ (drop
+ (array.new_elem $array-func 0
+ (i32.const 0)
+ (i32.const 3)
+ )
+ )
+ )
+
+ ;; CHECK: (func $array.init (type $ref|i31|_anyref_=>_none) (param $refined (ref i31)) (param $null-any anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.init_static $array$5
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.init_static $array$6
+ ;; CHECK-NEXT: (local.get $refined)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.init_static $array
+ ;; CHECK-NEXT: (local.get $null-any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (array.init_static $array
+ ;; CHECK-NEXT: (local.get $refined)
+ ;; CHECK-NEXT: (local.get $null-any)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; NOMNL: (func $array.init (type $ref|i31|_anyref_=>_none) (param $refined (ref i31)) (param $null-any anyref)
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.init_static $array$5
+ ;; NOMNL-NEXT: (ref.null none)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.init_static $array$6
+ ;; NOMNL-NEXT: (local.get $refined)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.init_static $array
+ ;; NOMNL-NEXT: (local.get $null-any)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: (drop
+ ;; NOMNL-NEXT: (array.init_static $array
+ ;; NOMNL-NEXT: (local.get $refined)
+ ;; NOMNL-NEXT: (local.get $null-any)
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ ;; NOMNL-NEXT: )
+ (func $array.init (param $refined (ref i31)) (param $null-any (ref null any))
+ ;; Null, interesting, so we get a new type.
+ (drop
+ (array.init_static $array
+ (ref.null none)
+ )
+ )
+ ;; More refined type, interesting.
+ (drop
+ (array.init_static $array
+ (local.get $refined)
+ )
+ )
+ ;; Same type as declared - boring, no new type.
+ (drop
+ (array.init_static $array
+ (local.get $null-any)
+ )
+ )
+ ;; Mixture of boring and interesting => boring (since we infer a single type
+ ;; for the entire array).
+ (drop
+ (array.init_static $array
+ (local.get $refined)
+ (local.get $null-any)
+ )
+ )
+ )
+)