diff options
-rw-r--r-- | src/ir/LocalStructuralDominance.cpp | 30 | ||||
-rw-r--r-- | src/ir/type-updating.cpp | 75 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 9 | ||||
-rw-r--r-- | test/lit/passes/coalesce-locals-gc.wast | 141 | ||||
-rw-r--r-- | test/lit/passes/simplify-locals-gc-nn.wast | 60 | ||||
-rw-r--r-- | test/lit/validation/bad-non-nullable-locals.wast | 11 |
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) + ) + ) +) |