summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/LocalStructuralDominance.cpp30
-rw-r--r--src/ir/type-updating.cpp75
-rw-r--r--src/wasm/wasm-validator.cpp9
-rw-r--r--test/lit/passes/coalesce-locals-gc.wast141
-rw-r--r--test/lit/passes/simplify-locals-gc-nn.wast60
-rw-r--r--test/lit/validation/bad-non-nullable-locals.wast11
6 files changed, 287 insertions, 39 deletions
diff --git a/src/ir/LocalStructuralDominance.cpp b/src/ir/LocalStructuralDominance.cpp
index cfb75e006..183ca7b3f 100644
--- a/src/ir/LocalStructuralDominance.cpp
+++ b/src/ir/LocalStructuralDominance.cpp
@@ -30,9 +30,11 @@ LocalStructuralDominance::LocalStructuralDominance(Function* func,
bool hasRefVar = false;
for (auto var : func->vars) {
- if (var.isRef()) {
- hasRefVar = true;
- break;
+ for (auto type : var) {
+ if (type.isRef()) {
+ hasRefVar = true;
+ break;
+ }
}
}
if (!hasRefVar) {
@@ -42,10 +44,13 @@ LocalStructuralDominance::LocalStructuralDominance(Function* func,
if (mode == NonNullableOnly) {
bool hasNonNullableVar = false;
for (auto var : func->vars) {
- // Check if we have any non-nullable vars at all.
- if (var.isNonNullable()) {
- hasNonNullableVar = true;
- break;
+ for (auto type : var) {
+ // Check if we have any non-nullable vars (or tuple vars with
+ // non-nullable elements) at all.
+ if (type.isNonNullable()) {
+ hasNonNullableVar = true;
+ break;
+ }
}
}
if (!hasNonNullableVar) {
@@ -70,10 +75,17 @@ LocalStructuralDominance::LocalStructuralDominance(Function* func,
}
for (Index i = func->getNumParams(); i < func->getNumLocals(); i++) {
- auto type = func->getLocalType(i);
+ auto localType = func->getLocalType(i);
+ bool interesting = false;
+ for (auto type : localType) {
+ if (type.isRef() && (mode == All || type.isNonNullable())) {
+ interesting = true;
+ break;
+ }
+ }
// Mark locals we don't need to care about as "set". We never do any
// work for such a local.
- if (!type.isRef() || (mode == NonNullableOnly && type.isNullable())) {
+ if (!interesting) {
localsSet[i] = true;
}
}
diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp
index 85ead1e9e..ac1f4b3af 100644
--- a/src/ir/type-updating.cpp
+++ b/src/ir/type-updating.cpp
@@ -302,9 +302,8 @@ Type GlobalTypeRewriter::getTempTupleType(Tuple tuple) {
namespace TypeUpdating {
bool canHandleAsLocal(Type type) {
- // Defaultable types are always ok. For non-nullable types, we can handle them
- // using defaultable ones + ref.as_non_nulls.
- return type.isDefaultable() || type.isRef();
+ // TODO: Inline this into its callers.
+ return type.isConcrete();
}
void handleNonDefaultableLocals(Function* func, Module& wasm) {
@@ -317,10 +316,12 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
return;
}
bool hasNonNullable = false;
- for (auto type : func->vars) {
- if (type.isNonNullable()) {
- hasNonNullable = true;
- break;
+ for (auto varType : func->vars) {
+ for (auto type : varType) {
+ if (type.isNonNullable()) {
+ hasNonNullable = true;
+ break;
+ }
}
}
if (!hasNonNullable) {
@@ -340,7 +341,8 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
// LocalStructuralDominance should have only looked at non-nullable indexes
// since we told it to ignore nullable ones. Also, params always dominate
// and should not appear here.
- assert(func->getLocalType(index).isNonNullable());
+ assert(func->getLocalType(index).isNonNullable() ||
+ func->getLocalType(index).isTuple());
assert(!func->isParam(index));
}
if (badIndexes.empty()) {
@@ -371,8 +373,24 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
}
if (badIndexes.count(set->index)) {
auto type = func->getLocalType(set->index);
- set->type = Type(type.getHeapType(), Nullable);
- *setp = builder.makeRefAs(RefAsNonNull, set);
+ auto validType = getValidLocalType(type, wasm.features);
+ if (type.isRef()) {
+ set->type = validType;
+ *setp = builder.makeRefAs(RefAsNonNull, set);
+ } else {
+ assert(type.isTuple());
+ set->makeSet();
+ std::vector<Expression*> elems(type.size());
+ for (size_t i = 0, size = type.size(); i < size; ++i) {
+ elems[i] = builder.makeTupleExtract(
+ builder.makeLocalGet(set->index, validType), i);
+ if (type[i].isNonNullable()) {
+ elems[i] = builder.makeRefAs(RefAsNonNull, elems[i]);
+ }
+ }
+ *setp =
+ builder.makeSequence(set, builder.makeTupleMake(std::move(elems)));
+ }
}
}
@@ -385,21 +403,48 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) {
}
Type getValidLocalType(Type type, FeatureSet features) {
- // TODO: this should handle tuples with a non-nullable item
- assert(canHandleAsLocal(type));
- if (type.isNonNullable() && !features.hasGCNNLocals()) {
- type = Type(type.getHeapType(), Nullable);
+ assert(type.isConcrete());
+ if (features.hasGCNNLocals()) {
+ return type;
+ }
+ if (type.isNonNullable()) {
+ return Type(type.getHeapType(), Nullable);
+ }
+ if (type.isTuple()) {
+ std::vector<Type> elems(type.size());
+ for (size_t i = 0, size = type.size(); i < size; ++i) {
+ elems[i] = getValidLocalType(type[i], features);
+ }
+ return Type(std::move(elems));
}
return type;
}
Expression* fixLocalGet(LocalGet* get, Module& wasm) {
- if (get->type.isNonNullable() && !wasm.features.hasGCNNLocals()) {
+ if (wasm.features.hasGCNNLocals()) {
+ return get;
+ }
+ if (get->type.isNonNullable()) {
// The get should now return a nullable value, and a ref.as_non_null
// fixes that up.
get->type = getValidLocalType(get->type, wasm.features);
return Builder(wasm).makeRefAs(RefAsNonNull, get);
}
+ if (get->type.isTuple()) {
+ auto type = get->type;
+ get->type = getValidLocalType(type, wasm.features);
+ std::vector<Expression*> elems(type.size());
+ Builder builder(wasm);
+ for (Index i = 0, size = type.size(); i < size; ++i) {
+ auto* elemGet =
+ i == 0 ? get : builder.makeLocalGet(get->index, get->type);
+ elems[i] = builder.makeTupleExtract(elemGet, i);
+ if (type[i].isNonNullable()) {
+ elems[i] = builder.makeRefAs(RefAsNonNull, elems[i]);
+ }
+ }
+ return builder.makeTupleMake(std::move(elems));
+ }
return get;
}
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 2039fb987..289d10b96 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -3200,9 +3200,12 @@ void FunctionValidator::visitFunction(Function* curr) {
// that is, a set allows gets until the end of the block.
LocalStructuralDominance info(curr, *getModule());
for (auto index : info.nonDominatingIndices) {
- shouldBeTrue(!curr->getLocalType(index).isNonNullable(),
- index,
- "non-nullable local's sets must dominate gets");
+ auto localType = curr->getLocalType(index);
+ for (auto type : localType) {
+ shouldBeTrue(!type.isNonNullable(),
+ index,
+ "non-nullable local's sets must dominate gets");
+ }
}
} else {
// With the special GCNNLocals feature, we allow gets anywhere, so long as
diff --git a/test/lit/passes/coalesce-locals-gc.wast b/test/lit/passes/coalesce-locals-gc.wast
index ae639c7f9..fdd2b0e60 100644
--- a/test/lit/passes/coalesce-locals-gc.wast
+++ b/test/lit/passes/coalesce-locals-gc.wast
@@ -1,12 +1,7 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
-;; RUN: wasm-opt %s --remove-unused-names --coalesce-locals -all -S -o - \
+;; RUN: wasm-opt %s --coalesce-locals -all -S -o - \
;; RUN: | filecheck %s
-;; --remove-unused-names is run to avoid adding names to blocks. Block names
-;; can prevent non-nullable local validation (we emit named blocks in the binary
-;; format, if we need them, but never emit unnamed ones), which affects some
-;; testcases.
-
(module
;; CHECK: (type $A (struct (field structref)))
@@ -21,7 +16,16 @@
;; CHECK: (global $global (ref null $array) (ref.null none))
(global $global (ref null $array) (ref.null $array))
- ;; CHECK: (func $test-dead-get-non-nullable (type $5) (param $0 (ref struct))
+ ;; CHECK: (global $nn-tuple-global (mut ((ref any) i32)) (tuple.make
+ ;; CHECK-NEXT: (i31.new
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: ))
+ (global $nn-tuple-global (mut ((ref any) i32)) (tuple.make (i31.new (i32.const 0)) (i32.const 1)))
+
+
+ ;; CHECK: (func $test-dead-get-non-nullable (type $6) (param $0 (ref struct))
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result (ref struct))
@@ -39,7 +43,7 @@
)
)
- ;; CHECK: (func $br_on_null (type $6) (param $0 (ref null $array)) (result (ref null $array))
+ ;; CHECK: (func $br_on_null (type $7) (param $0 (ref null $array)) (result (ref null $array))
;; CHECK-NEXT: (block $label$1 (result (ref null $array))
;; CHECK-NEXT: (block $label$2
;; CHECK-NEXT: (br $label$1
@@ -176,7 +180,7 @@
)
)
- ;; CHECK: (func $remove-tee-refinalize (type $4) (param $0 (ref null $A)) (param $1 (ref null $B)) (result structref)
+ ;; CHECK: (func $remove-tee-refinalize (type $5) (param $0 (ref null $A)) (param $1 (ref null $B)) (result structref)
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (block (result (ref null $A))
;; CHECK-NEXT: (local.get $1)
@@ -197,7 +201,7 @@
)
)
- ;; CHECK: (func $remove-tee-refinalize-2 (type $4) (param $0 (ref null $A)) (param $1 (ref null $B)) (result structref)
+ ;; CHECK: (func $remove-tee-refinalize-2 (type $5) (param $0 (ref null $A)) (param $1 (ref null $B)) (result structref)
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (block (result (ref null $A))
;; CHECK-NEXT: (local.get $1)
@@ -219,7 +223,7 @@
)
)
- ;; CHECK: (func $replace-i31-local (type $7) (result i32)
+ ;; CHECK: (func $replace-i31-local (type $8) (result i32)
;; CHECK-NEXT: (local $0 i31ref)
;; CHECK-NEXT: (i32.add
;; CHECK-NEXT: (unreachable)
@@ -251,7 +255,7 @@
)
)
- ;; CHECK: (func $replace-struct-param (type $8) (param $0 f64) (param $1 (ref null $A)) (result f32)
+ ;; CHECK: (func $replace-struct-param (type $9) (param $0 f64) (param $1 (ref null $A)) (result f32)
;; CHECK-NEXT: (call $replace-struct-param
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (unreachable)
@@ -278,4 +282,117 @@
)
)
)
+
+ ;; CHECK: (func $test (type $10) (param $0 (ref any)) (result (ref any) i32)
+ ;; CHECK-NEXT: (local $1 (anyref i32))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (if
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (local.get $0)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.set $nn-tuple-global
+ ;; CHECK-NEXT: (block (result (ref any) i32)
+ ;; CHECK-NEXT: (local.set $1
+ ;; CHECK-NEXT: (if (result (ref any) i32)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (tuple.extract 0
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 1
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (tuple.extract 0
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 1
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (tuple.extract 0
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 1
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (tuple.extract 0
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 1
+ ;; CHECK-NEXT: (local.get $1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $any (ref any)) (result (ref any) i32)
+ (local $x ((ref any) i32))
+ (local $y ((ref any) i32))
+ ;; This store is dead and will be removed.
+ (local.set $x
+ (tuple.make
+ (local.get $any)
+ (i32.const 0)
+ )
+ )
+ (if
+ (i32.const 0)
+ ;; These two sets will remain.
+ (local.set $x
+ (tuple.make
+ (local.get $any)
+ (i32.const 1)
+ )
+ )
+ (local.set $x
+ (tuple.make
+ (local.get $any)
+ (i32.const 2)
+ )
+ )
+ )
+ (global.set $nn-tuple-global
+ ;; This tee will have to be fixed up for the new type.
+ (local.tee $y
+ (if (result (ref any) i32)
+ (i32.const 0)
+ ;; These gets will be invalid, so the local will have to be made nullable.
+ (local.get $x)
+ (local.get $x)
+ )
+ )
+ )
+ (local.get $y)
+ )
)
diff --git a/test/lit/passes/simplify-locals-gc-nn.wast b/test/lit/passes/simplify-locals-gc-nn.wast
index 170bc1553..2575ac807 100644
--- a/test/lit/passes/simplify-locals-gc-nn.wast
+++ b/test/lit/passes/simplify-locals-gc-nn.wast
@@ -49,6 +49,66 @@
)
)
+ ;; CHECK: (func $test-nn-tuple (type $0)
+ ;; CHECK-NEXT: (local $nn (i32 anyref i64))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: (try $try
+ ;; CHECK-NEXT: (do
+ ;; CHECK-NEXT: (local.set $nn
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (ref.null none)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (catch_all
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (tuple.make
+ ;; CHECK-NEXT: (tuple.extract 0
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (tuple.extract 1
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (tuple.extract 2
+ ;; CHECK-NEXT: (local.get $nn)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test-nn-tuple
+ ;; Same as above, but now the local is a tuple containing a non-nullable element
+ (local $nn (i32 (ref any) i64))
+ (local.set $nn
+ (tuple.make
+ (i32.const 0)
+ (ref.as_non_null
+ (ref.null any)
+ )
+ (i64.const 0)
+ )
+ )
+ (try
+ (do
+ (drop
+ (local.get $nn)
+ )
+ )
+ (catch_all
+ (drop
+ (local.get $nn)
+ )
+ )
+ )
+ )
+
;; CHECK: (func $test-nullable (type $0)
;; CHECK-NEXT: (local $nullable anyref)
;; CHECK-NEXT: (nop)
diff --git a/test/lit/validation/bad-non-nullable-locals.wast b/test/lit/validation/bad-non-nullable-locals.wast
index f22771591..9268e9b3a 100644
--- a/test/lit/validation/bad-non-nullable-locals.wast
+++ b/test/lit/validation/bad-non-nullable-locals.wast
@@ -59,3 +59,14 @@
(func $helper)
)
+;; CHECK: non-nullable local's sets must dominate gets
+(module
+ (func $tuple
+ ;; Since this tuple local has a non-nullable element, it is subject to the
+ ;; non-nullability rules.
+ (local $x (i32 (ref any) i64))
+ (drop
+ (local.get $x)
+ )
+ )
+)