diff options
-rwxr-xr-x | scripts/gen-s-parser.py | 5 | ||||
-rw-r--r-- | src/gen-s-parser.inc | 42 | ||||
-rw-r--r-- | src/ir/ReFinalize.cpp | 2 | ||||
-rw-r--r-- | src/ir/branch-utils.h | 2 | ||||
-rw-r--r-- | src/ir/gc-type-utils.h | 20 | ||||
-rw-r--r-- | src/passes/Print.cpp | 15 | ||||
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 13 | ||||
-rw-r--r-- | src/wasm-binary.h | 5 | ||||
-rw-r--r-- | src/wasm-interpreter.h | 78 | ||||
-rw-r--r-- | src/wasm.h | 10 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 20 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 2 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 15 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 4 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 49 | ||||
-rw-r--r-- | test/heap-types.wast | 44 | ||||
-rw-r--r-- | test/heap-types.wast.from-wast | 55 | ||||
-rw-r--r-- | test/heap-types.wast.fromBinary | 55 | ||||
-rw-r--r-- | test/heap-types.wast.fromBinary.noDebugInfo | 55 | ||||
-rw-r--r-- | test/lit/passes/remove-unused-brs-gc.wast | 54 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.txt | 130 | ||||
-rw-r--r-- | test/passes/Oz_fuzz-exec_all-features.wast | 115 | ||||
-rw-r--r-- | test/passes/remove-unused-brs_all-features.txt | 16 | ||||
-rw-r--r-- | test/passes/remove-unused-brs_all-features.wast | 9 |
24 files changed, 746 insertions, 69 deletions
diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 731d50fd7..f7ff7a1fb 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -539,10 +539,15 @@ instructions = [ ("ref.test", "makeRefTest(s)"), ("ref.cast", "makeRefCast(s)"), ("br_on_null", "makeBrOn(s, BrOnNull)"), + ("br_on_non_null", "makeBrOn(s, BrOnNonNull)"), ("br_on_cast", "makeBrOn(s, BrOnCast)"), + ("br_on_cast_fail", "makeBrOn(s, BrOnCastFail)"), ("br_on_func", "makeBrOn(s, BrOnFunc)"), + ("br_on_non_func", "makeBrOn(s, BrOnNonFunc)"), ("br_on_data", "makeBrOn(s, BrOnData)"), + ("br_on_non_data", "makeBrOn(s, BrOnNonData)"), ("br_on_i31", "makeBrOn(s, BrOnI31)"), + ("br_on_non_i31", "makeBrOn(s, BrOnNonI31)"), ("rtt.canon", "makeRttCanon(s)"), ("rtt.sub", "makeRttSub(s)"), ("struct.new_with_rtt", "makeStructNew(s, false)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 7aba3faf1..2eca900cc 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -76,9 +76,17 @@ switch (op[0]) { goto parse_error; case 'o': { switch (op[6]) { - case 'c': - if (strcmp(op, "br_on_cast") == 0) { return makeBrOn(s, BrOnCast); } - goto parse_error; + case 'c': { + switch (op[10]) { + case '\0': + if (strcmp(op, "br_on_cast") == 0) { return makeBrOn(s, BrOnCast); } + goto parse_error; + case '_': + if (strcmp(op, "br_on_cast_fail") == 0) { return makeBrOn(s, BrOnCastFail); } + goto parse_error; + default: goto parse_error; + } + } case 'd': if (strcmp(op, "br_on_data") == 0) { return makeBrOn(s, BrOnData); } goto parse_error; @@ -88,9 +96,31 @@ switch (op[0]) { case 'i': if (strcmp(op, "br_on_i31") == 0) { return makeBrOn(s, BrOnI31); } goto parse_error; - case 'n': - if (strcmp(op, "br_on_null") == 0) { return makeBrOn(s, BrOnNull); } - goto parse_error; + case 'n': { + switch (op[7]) { + case 'o': { + switch (op[10]) { + case 'd': + if (strcmp(op, "br_on_non_data") == 0) { return makeBrOn(s, BrOnNonData); } + goto parse_error; + case 'f': + if (strcmp(op, "br_on_non_func") == 0) { return makeBrOn(s, BrOnNonFunc); } + goto parse_error; + case 'i': + if (strcmp(op, "br_on_non_i31") == 0) { return makeBrOn(s, BrOnNonI31); } + goto parse_error; + case 'n': + if (strcmp(op, "br_on_non_null") == 0) { return makeBrOn(s, BrOnNonNull); } + goto parse_error; + default: goto parse_error; + } + } + case 'u': + if (strcmp(op, "br_on_null") == 0) { return makeBrOn(s, BrOnNull); } + goto parse_error; + default: goto parse_error; + } + } default: goto parse_error; } } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 1dcb55f61..b024c94be 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -153,7 +153,7 @@ void ReFinalize::visitBrOn(BrOn* curr) { if (curr->type == Type::unreachable) { replaceUntaken(curr->ref, nullptr); } else { - updateBreakValueType(curr->name, curr->getCastType()); + updateBreakValueType(curr->name, curr->getSentType()); } } void ReFinalize::visitRttCanon(RttCanon* curr) { curr->finalize(); } diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 662f70725..87f7813e7 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -81,7 +81,7 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) { } else if (auto* sw = expr->dynCast<Switch>()) { func(name, sw->value ? sw->value->type : Type::none); } else if (auto* br = expr->dynCast<BrOn>()) { - func(name, br->getCastType()); + func(name, br->getSentType()); } else { assert(expr->is<Try>() || expr->is<Rethrow>()); // delegate or rethrow } diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h index d59193193..70db7b178 100644 --- a/src/ir/gc-type-utils.h +++ b/src/ir/gc-type-utils.h @@ -46,19 +46,33 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) { Kind expected; Expression* child; + // Some operations flip the condition. + bool flip = false; + if (auto* br = curr->dynCast<BrOn>()) { switch (br->op) { // We don't check nullability here. case BrOnNull: + case BrOnNonNull: // Casts can only be known at runtime using RTTs. case BrOnCast: + case BrOnCastFail: return Unknown; + case BrOnNonFunc: + flip = true; + [[fallthrough]]; case BrOnFunc: expected = Func; break; + case BrOnNonData: + flip = true; + [[fallthrough]]; case BrOnData: expected = Data; break; + case BrOnNonI31: + flip = true; + [[fallthrough]]; case BrOnI31: expected = I31; break; @@ -120,7 +134,11 @@ inline EvaluationResult evaluateKindCheck(Expression* curr) { return Unknown; } - return actual == expected ? Success : Failure; + auto success = actual == expected; + if (flip) { + success = !success; + } + return success ? Success : Failure; } } // namespace GCTypeUtils diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index df408fdf3..47db029ab 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1888,18 +1888,33 @@ struct PrintExpressionContents case BrOnNull: printMedium(o, "br_on_null "); break; + case BrOnNonNull: + printMedium(o, "br_on_non_null "); + break; case BrOnCast: printMedium(o, "br_on_cast "); break; + case BrOnCastFail: + printMedium(o, "br_on_cast_fail "); + break; case BrOnFunc: printMedium(o, "br_on_func "); break; + case BrOnNonFunc: + printMedium(o, "br_on_non_func "); + break; case BrOnData: printMedium(o, "br_on_data "); break; + case BrOnNonData: + printMedium(o, "br_on_non_data "); + break; case BrOnI31: printMedium(o, "br_on_i31 "); break; + case BrOnNonI31: + printMedium(o, "br_on_non_i31 "); + break; default: WASM_UNREACHABLE("invalid ref.is_*"); } diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 88a87ba1f..5631c9d5f 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -389,10 +389,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // First, check for a possible null which would prevent all other // optimizations. - // (Note: if the spec had BrOnNonNull, instead of BrOnNull, then we could - // replace a br_on_func whose input is (ref null func) with br_on_non_null, - // as only the null check would be needed. But as things are, we cannot do - // such a thing.) + // TODO: Look into using BrOnNonNull here, to replace a br_on_func whose + // input is (ref null func) with br_on_non_null (as only the null check + // would be needed). auto refType = curr->ref->type; if (refType.isNullable()) { return; @@ -405,6 +404,12 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { anotherCycle = true; return; } + if (curr->op == BrOnNonNull) { + // This cannot be null, so the br is always taken. + replaceCurrent(Builder(*getModule()).makeBreak(curr->name, curr->ref)); + anotherCycle = true; + return; + } // Check if the type is the kind we are checking for. auto result = GCTypeUtils::evaluateKindCheck(curr); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 00c45ef60..408aafc63 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1019,6 +1019,7 @@ enum ASTNodes { RefFunc = 0xd2, RefAsNonNull = 0xd3, BrOnNull = 0xd4, + BrOnNonNull = 0xd6, // exception handling opcodes @@ -1060,6 +1061,7 @@ enum ASTNodes { RefTest = 0x40, RefCast = 0x41, BrOnCast = 0x42, + BrOnCastFail = 0x43, RefIsFunc = 0x50, RefIsData = 0x51, RefIsI31 = 0x52, @@ -1069,6 +1071,9 @@ enum ASTNodes { BrOnFunc = 0x60, BrOnData = 0x61, BrOnI31 = 0x62, + BrOnNonFunc = 0x63, + BrOnNonData = 0x64, + BrOnNonI31 = 0x65, }; enum MemoryAccess { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 86d868ac8..94230b747 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1500,17 +1500,25 @@ public: } Flow visitBrOn(BrOn* curr) { NOTE_ENTER("BrOn"); - // BrOnCast uses the casting infrastructure, so handle it first. - if (curr->op == BrOnCast) { + // BrOnCast* uses the casting infrastructure, so handle it first. + if (curr->op == BrOnCast || curr->op == BrOnCastFail) { auto cast = doCast(curr); if (cast.outcome == cast.Break) { return cast.breaking; } if (cast.outcome == cast.Null || cast.outcome == cast.Failure) { - return cast.originalRef; + if (curr->op == BrOnCast) { + return cast.originalRef; + } else { + return Flow(curr->name, cast.originalRef); + } } assert(cast.outcome == cast.Success); - return Flow(curr->name, cast.castRef); + if (curr->op == BrOnCast) { + return Flow(curr->name, cast.castRef); + } else { + return cast.castRef; + } } // The others do a simpler check for the type. Flow flow = visit(curr->ref); @@ -1528,30 +1536,54 @@ public: // If the branch is not taken, we return the non-null value. return {value}; } + if (curr->op == BrOnNonNull) { + // Unlike the others, BrOnNonNull does not return a value if it does not + // take the branch. + if (value.isNull()) { + return Flow(); + } + // If the branch is taken, we send the non-null value. + return Flow(curr->name, value); + } + // See if the input is the right kind (ignoring the flipping behavior of + // BrOn*). + bool isRightKind; if (value.isNull()) { - return {value}; + // A null is never the right kind. + isRightKind = false; + } else { + switch (curr->op) { + case BrOnNonFunc: + case BrOnFunc: + isRightKind = value.type.isFunction(); + break; + case BrOnNonData: + case BrOnData: + isRightKind = value.isData(); + break; + case BrOnNonI31: + case BrOnI31: + isRightKind = value.type.getHeapType() == HeapType::i31; + break; + default: + WASM_UNREACHABLE("invalid br_on_*"); + } } + // The Non* operations require us to flip the normal behavior. switch (curr->op) { - case BrOnFunc: - if (!value.type.isFunction()) { - return {value}; - } - break; - case BrOnData: - if (!value.isData()) { - return {value}; - } + case BrOnNonFunc: + case BrOnNonData: + case BrOnNonI31: + isRightKind = !isRightKind; break; - case BrOnI31: - if (value.type.getHeapType() != HeapType::i31) { - return {value}; - } - break; - default: - WASM_UNREACHABLE("invalid br_on_*"); + default: { + } + } + if (isRightKind) { + // Take the branch. + return Flow(curr->name, value); } - // No problems: take the branch. - return Flow(curr->name, value); + return {value}; } Flow visitRttCanon(RttCanon* curr) { return Literal(curr->type); } Flow visitRttSub(RttSub* curr) { diff --git a/src/wasm.h b/src/wasm.h index baea1fabe..08f039451 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -545,10 +545,15 @@ enum RefAsOp { enum BrOnOp { BrOnNull, + BrOnNonNull, BrOnCast, + BrOnCastFail, BrOnFunc, + BrOnNonFunc, BrOnData, + BrOnNonData, BrOnI31, + BrOnNonI31, }; // @@ -1374,7 +1379,7 @@ public: Name name; Expression* ref; - // BrOnCast has an rtt that is used in the cast. + // BrOnCast* has an rtt that is used in the cast. Expression* rtt; // TODO: BrOnNull also has an optional extra value in the spec, which we do @@ -1385,7 +1390,8 @@ public: void finalize(); - Type getCastType(); + // Returns the type sent on the branch, if it is taken. + Type getSentType(); }; class RttCanon : public SpecificExpression<Expression::RttCanonId> { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 12aa5f214..35bc468b1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -3377,6 +3377,9 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { case BinaryConsts::BrOnNull: maybeVisitBrOn(curr, code); break; + case BinaryConsts::BrOnNonNull: + maybeVisitBrOn(curr, code); + break; case BinaryConsts::Try: visitTryOrTryInBlock(curr); break; @@ -6333,24 +6336,39 @@ bool WasmBinaryBuilder::maybeVisitBrOn(Expression*& out, uint32_t code) { case BinaryConsts::BrOnNull: op = BrOnNull; break; + case BinaryConsts::BrOnNonNull: + op = BrOnNonNull; + break; case BinaryConsts::BrOnCast: op = BrOnCast; break; + case BinaryConsts::BrOnCastFail: + op = BrOnCastFail; + break; case BinaryConsts::BrOnFunc: op = BrOnFunc; break; + case BinaryConsts::BrOnNonFunc: + op = BrOnNonFunc; + break; case BinaryConsts::BrOnData: op = BrOnData; break; + case BinaryConsts::BrOnNonData: + op = BrOnNonData; + break; case BinaryConsts::BrOnI31: op = BrOnI31; break; + case BinaryConsts::BrOnNonI31: + op = BrOnNonI31; + break; default: return false; } auto name = getBreakTarget(getU32LEB()).name; Expression* rtt = nullptr; - if (op == BrOnCast) { + if (op == BrOnCast || op == BrOnCastFail) { rtt = popNonVoidExpression(); } auto* ref = popNonVoidExpression(); diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 25db9a993..9c2d028a4 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -2573,7 +2573,7 @@ Expression* SExpressionWasmBuilder::makeBrOn(Element& s, BrOnOp op) { auto name = getLabel(*s[1]); auto* ref = parseExpression(*s[2]); Expression* rtt = nullptr; - if (op == BrOnCast) { + if (op == BrOnCast || op == BrOnCastFail) { rtt = parseExpression(*s[3]); } return ValidatingBuilder(wasm, s.line, s.col) diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 12c999c0d..70a593bec 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1954,18 +1954,33 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { case BrOnNull: o << int8_t(BinaryConsts::BrOnNull); break; + case BrOnNonNull: + o << int8_t(BinaryConsts::BrOnNonNull); + break; case BrOnCast: o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCast); break; + case BrOnCastFail: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnCastFail); + break; case BrOnFunc: o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnFunc); break; + case BrOnNonFunc: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnNonFunc); + break; case BrOnData: o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnData); break; + case BrOnNonData: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnNonData); + break; case BrOnI31: o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnI31); break; + case BrOnNonI31: + o << int8_t(BinaryConsts::GCPrefix) << U32LEB(BinaryConsts::BrOnNonI31); + break; default: WASM_UNREACHABLE("invalid br_on_*"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 10b60e0a7..bbc92f09c 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2245,14 +2245,14 @@ void FunctionValidator::visitBrOn(BrOn* curr) { shouldBeTrue( curr->ref->type.isRef(), curr, "br_on_cast ref must have ref type"); } - if (curr->op == BrOnCast) { + if (curr->op == BrOnCast || curr->op == BrOnCastFail) { // Note that an unreachable rtt is not supported: the text and binary // formats do not provide the type, so if it's unreachable we should not // even create a br_on_cast in such a case, as we'd have no idea what it // casts to. shouldBeTrue( curr->rtt->type.isRtt(), curr, "br_on_cast rtt must have rtt type"); - noteBreak(curr->name, curr->getCastType(), curr); + noteBreak(curr->name, curr->getSentType(), curr); } else { shouldBeTrue(curr->rtt == nullptr, curr, "non-cast BrOn must not have rtt"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index f0d130d6b..b41f34ddf 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -916,22 +916,52 @@ void BrOn::finalize() { if (ref->type == Type::unreachable || (rtt && rtt->type == Type::unreachable)) { type = Type::unreachable; - } else { - if (op == BrOnNull) { - // If BrOnNull does not branch, it flows out the existing value as - // non-null. + return; + } + switch (op) { + case BrOnNull: + // If we do not branch, we flow out the existing value as non-null. type = Type(ref->type.getHeapType(), NonNullable); - } else { + break; + case BrOnNonNull: + // If we do not branch, we flow out nothing (the spec could also have had + // us flow out the null, but it does not). + type = Type::none; + break; + case BrOnCast: + case BrOnFunc: + case BrOnData: + case BrOnI31: + // If we do not branch, we return the input in this case. type = ref->type; - } + break; + case BrOnCastFail: + // If we do not branch, the cast worked, and we have something of the cast + // type. + type = Type(rtt->type.getHeapType(), NonNullable); + break; + case BrOnNonFunc: + type = Type(HeapType::func, NonNullable); + break; + case BrOnNonData: + type = Type(HeapType::data, NonNullable); + break; + case BrOnNonI31: + type = Type(HeapType::i31, NonNullable); + break; + default: + WASM_UNREACHABLE("invalid br_on_*"); } } -Type BrOn::getCastType() { +Type BrOn::getSentType() { switch (op) { case BrOnNull: // BrOnNull does not send a value on the branch. return Type::none; + case BrOnNonNull: + // BrOnNonNull sends the non-nullable type on the branch. + return Type(ref->type.getHeapType(), NonNullable); case BrOnCast: return Type(rtt->type.getHeapType(), NonNullable); case BrOnFunc: @@ -940,6 +970,11 @@ Type BrOn::getCastType() { return Type::dataref; case BrOnI31: return Type::i31ref; + case BrOnCastFail: + case BrOnNonFunc: + case BrOnNonData: + case BrOnNonI31: + return ref->type; default: WASM_UNREACHABLE("invalid br_on_*"); } diff --git a/test/heap-types.wast b/test/heap-types.wast index 35284bef6..0445c97d5 100644 --- a/test/heap-types.wast +++ b/test/heap-types.wast @@ -194,6 +194,7 @@ (func $rtt-param-without-depth (param $rtt (rtt $parent))) (func $rtt-operations (local $temp.A (ref null $struct.A)) + (local $temp.B (ref null $struct.B)) (drop (ref.test (ref.null $struct.A) (rtt.canon $struct.B)) ) @@ -213,6 +214,16 @@ (unreachable) ) ) + (drop + (block $out2 (result (ref null $struct.A)) + ;; set the value to a local with type $struct.A, showing that the value + ;; flowing out has the right type + (local.set $temp.B + (br_on_cast_fail $out2 (ref.null $struct.A) (rtt.canon $struct.B)) + ) + (ref.null $struct.A) + ) + ) ) (func $ref.is_X (param $x anyref) (if (ref.is_func (local.get $x)) (unreachable)) @@ -228,6 +239,9 @@ (func $br_on_X (param $x anyref) (local $y anyref) (local $z (ref null any)) + (local $temp-func (ref null func)) + (local $temp-data (ref null data)) + (local $temp-i31 (ref null i31)) (block $null (local.set $z (br_on_null $null (local.get $x)) @@ -257,6 +271,36 @@ (ref.null i31) ) ) + (drop + (block $non-null (result (ref any)) + (br_on_non_null $non-null (local.get $x)) + (unreachable) + ) + ) + (drop + (block $non-func (result anyref) + (local.set $temp-func + (br_on_non_func $non-func (local.get $x)) + ) + (ref.null any) + ) + ) + (drop + (block $non-data (result anyref) + (local.set $temp-data + (br_on_non_data $non-data (local.get $x)) + ) + (ref.null any) + ) + ) + (drop + (block $non-i31 (result anyref) + (local.set $temp-i31 + (br_on_non_i31 $non-i31 (local.get $x)) + ) + (ref.null any) + ) + ) ) (func $unreachables-1 (drop diff --git a/test/heap-types.wast.from-wast b/test/heap-types.wast.from-wast index e9dfc9a25..dfb8b622a 100644 --- a/test/heap-types.wast.from-wast +++ b/test/heap-types.wast.from-wast @@ -1,8 +1,8 @@ (module (type $struct.A (struct (field i32) (field f32) (field $named f64))) + (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A))))) (type $vector (array (mut f64))) (type $none_=>_none (func)) - (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A))))) (type $grandchild (struct (field i32) (field i64))) (type $struct.C (struct (field $named-mut (mut f32)))) (type $matrix (array (mut (ref null $vector)))) @@ -195,6 +195,7 @@ ) (func $rtt-operations (local $temp.A (ref null $struct.A)) + (local $temp.B (ref null $struct.B)) (drop (ref.test (ref.null $struct.A) @@ -224,6 +225,17 @@ (unreachable) ) ) + (drop + (block $out2 (result (ref null $struct.A)) + (local.set $temp.B + (br_on_cast_fail $out2 + (ref.null $struct.A) + (rtt.canon $struct.B) + ) + ) + (ref.null $struct.A) + ) + ) ) (func $ref.is_X (param $x anyref) (if @@ -270,6 +282,9 @@ (func $br_on_X (param $x anyref) (local $y anyref) (local $z anyref) + (local $temp-func funcref) + (local $temp-data (ref null data)) + (local $temp-i31 (ref null i31)) (block $null (local.set $z (br_on_null $null @@ -307,6 +322,44 @@ (ref.null i31) ) ) + (drop + (block $non-null (result (ref any)) + (br_on_non_null $non-null + (local.get $x) + ) + (unreachable) + ) + ) + (drop + (block $non-func (result anyref) + (local.set $temp-func + (br_on_non_func $non-func + (local.get $x) + ) + ) + (ref.null any) + ) + ) + (drop + (block $non-data (result anyref) + (local.set $temp-data + (br_on_non_data $non-data + (local.get $x) + ) + ) + (ref.null any) + ) + ) + (drop + (block $non-i31 (result anyref) + (local.set $temp-i31 + (br_on_non_i31 $non-i31 + (local.get $x) + ) + ) + (ref.null any) + ) + ) ) (func $unreachables-1 (drop diff --git a/test/heap-types.wast.fromBinary b/test/heap-types.wast.fromBinary index dbe080996..314b3c29e 100644 --- a/test/heap-types.wast.fromBinary +++ b/test/heap-types.wast.fromBinary @@ -1,8 +1,8 @@ (module (type $struct.A (struct (field i32) (field f32) (field $named f64))) + (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A))))) (type $vector (array (mut f64))) (type $none_=>_none (func)) - (type $struct.B (struct (field i8) (field (mut i16)) (field (ref $struct.A)) (field (mut (ref $struct.A))))) (type $grandchild (struct (field i32) (field i64))) (type $matrix (array (mut (ref null $vector)))) (type $struct.C (struct (field $named-mut (mut f32)))) @@ -195,6 +195,7 @@ ) (func $rtt-operations (local $temp.A (ref null $struct.A)) + (local $temp.B (ref null $struct.B)) (drop (ref.test (ref.null $struct.A) @@ -223,6 +224,17 @@ ) ) ) + (drop + (block $label$3 (result (ref null $struct.A)) + (local.set $temp.B + (br_on_cast_fail $label$3 + (ref.null $struct.A) + (rtt.canon $struct.B) + ) + ) + (ref.null $struct.A) + ) + ) ) (func $ref.is_X (param $x anyref) (if @@ -269,6 +281,9 @@ (func $br_on_X (param $x anyref) (local $y anyref) (local $z anyref) + (local $temp-func funcref) + (local $temp-data (ref null data)) + (local $temp-i31 (ref null i31)) (block $label$1 (local.set $z (br_on_null $label$1 @@ -306,6 +321,44 @@ (ref.null i31) ) ) + (drop + (block $label$5 (result (ref any)) + (br_on_non_null $label$5 + (local.get $x) + ) + (unreachable) + ) + ) + (drop + (block $label$6 (result anyref) + (local.set $temp-func + (br_on_non_func $label$6 + (local.get $x) + ) + ) + (ref.null any) + ) + ) + (drop + (block $label$7 (result anyref) + (local.set $temp-data + (br_on_non_data $label$7 + (local.get $x) + ) + ) + (ref.null any) + ) + ) + (drop + (block $label$8 (result anyref) + (local.set $temp-i31 + (br_on_non_i31 $label$8 + (local.get $x) + ) + ) + (ref.null any) + ) + ) ) (func $unreachables-1 (unreachable) diff --git a/test/heap-types.wast.fromBinary.noDebugInfo b/test/heap-types.wast.fromBinary.noDebugInfo index 2fb57aacb..bb6ff9165 100644 --- a/test/heap-types.wast.fromBinary.noDebugInfo +++ b/test/heap-types.wast.fromBinary.noDebugInfo @@ -1,8 +1,8 @@ (module (type ${i32_f32_f64} (struct (field i32) (field f32) (field f64))) + (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64}))))) (type $[mut:f64] (array (mut f64))) (type $none_=>_none (func)) - (type ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|} (struct (field i8) (field (mut i16)) (field (ref ${i32_f32_f64})) (field (mut (ref ${i32_f32_f64}))))) (type ${i32_i64} (struct (field i32) (field i64))) (type $[mut:ref?|[mut:f64]|] (array (mut (ref null $[mut:f64])))) (type ${mut:f32} (struct (field (mut f32)))) @@ -195,6 +195,7 @@ ) (func $4 (local $0 (ref null ${i32_f32_f64})) + (local $1 (ref null ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|})) (drop (ref.test (ref.null ${i32_f32_f64}) @@ -223,6 +224,17 @@ ) ) ) + (drop + (block $label$3 (result (ref null ${i32_f32_f64})) + (local.set $1 + (br_on_cast_fail $label$3 + (ref.null ${i32_f32_f64}) + (rtt.canon ${i8_mut:i16_ref|{i32_f32_f64}|_mut:ref|{i32_f32_f64}|}) + ) + ) + (ref.null ${i32_f32_f64}) + ) + ) ) (func $5 (param $0 anyref) (if @@ -269,6 +281,9 @@ (func $7 (param $0 anyref) (local $1 anyref) (local $2 anyref) + (local $3 funcref) + (local $4 (ref null data)) + (local $5 (ref null i31)) (block $label$1 (local.set $2 (br_on_null $label$1 @@ -306,6 +321,44 @@ (ref.null i31) ) ) + (drop + (block $label$5 (result (ref any)) + (br_on_non_null $label$5 + (local.get $0) + ) + (unreachable) + ) + ) + (drop + (block $label$6 (result anyref) + (local.set $3 + (br_on_non_func $label$6 + (local.get $0) + ) + ) + (ref.null any) + ) + ) + (drop + (block $label$7 (result anyref) + (local.set $4 + (br_on_non_data $label$7 + (local.get $0) + ) + ) + (ref.null any) + ) + ) + (drop + (block $label$8 (result anyref) + (local.set $5 + (br_on_non_i31 $label$8 + (local.get $0) + ) + ) + (ref.null any) + ) + ) ) (func $8 (unreachable) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast new file mode 100644 index 000000000..7fe579c5a --- /dev/null +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -0,0 +1,54 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - \ +;; RUN: | filecheck %s + +(module + ;; CHECK: (func $br_on_non_data-1 + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $any (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br $any + ;; CHECK-NEXT: (ref.func $br_on_non_data-1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_non_data-1 + (drop + (block $any (result anyref) + (drop + ;; A function is not data, and so we should branch. + (br_on_non_data $any + (ref.func $br_on_non_data-1) + ) + ) + (ref.null any) + ) + ) + ) + ;; CHECK: (func $br_on_non_data-2 (param $data dataref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $any (result anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null any) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_non_data-2 (param $data (ref data)) + (drop + (block $any (result anyref) + (drop + ;; Data is provided here, and so we will not branch. + (br_on_non_data $any + (local.get $data) + ) + ) + (ref.null any) + ) + ) + ) +) diff --git a/test/passes/Oz_fuzz-exec_all-features.txt b/test/passes/Oz_fuzz-exec_all-features.txt index 359311e41..2ce8cfa00 100644 --- a/test/passes/Oz_fuzz-exec_all-features.txt +++ b/test/passes/Oz_fuzz-exec_all-features.txt @@ -21,10 +21,23 @@ [fuzz-exec] calling br_on_cast [LoggingExternalInterface logging 3] [trap unreachable] +[fuzz-exec] calling br_on_failed_cast-1 +[LoggingExternalInterface logging 1] +[fuzz-exec] calling br_on_failed_cast-2 +[LoggingExternalInterface logging 1] +[LoggingExternalInterface logging 999] [fuzz-exec] calling cast-null-anyref-to-gc [LoggingExternalInterface logging 0] [fuzz-exec] calling br_on_data [LoggingExternalInterface logging 1] +[fuzz-exec] calling br_on_non_data-null +[fuzz-exec] calling br_on_non_data-data +[LoggingExternalInterface logging 1] +[fuzz-exec] calling br_on_non_data-other +[fuzz-exec] calling br-on_non_null +[fuzz-exec] calling br-on_non_null-2 +[LoggingExternalInterface logging 1] +[trap unreachable] [fuzz-exec] calling rtt-and-cast-on-func [LoggingExternalInterface logging 0] [LoggingExternalInterface logging 1] @@ -45,10 +58,10 @@ [LoggingExternalInterface logging 0] [LoggingExternalInterface logging 10] (module - (type $struct (struct (field (mut i32)))) (type $void_func (func)) - (type $bytes (array (mut i8))) + (type $struct (struct (field (mut i32)))) (type $extendedstruct (struct (field (mut i32)) (field f64))) + (type $bytes (array (mut i8))) (type $int_func (func (result i32))) (type $i32_=>_none (func (param i32))) (type $anyref_=>_none (func (param anyref))) @@ -59,13 +72,20 @@ (export "arrays" (func $1)) (export "rtts" (func $2)) (export "br_on_cast" (func $3)) - (export "cast-null-anyref-to-gc" (func $4)) - (export "br_on_data" (func $6)) - (export "rtt-and-cast-on-func" (func $8)) + (export "br_on_failed_cast-1" (func $4)) + (export "br_on_failed_cast-2" (func $5)) + (export "cast-null-anyref-to-gc" (func $6)) + (export "br_on_data" (func $8)) + (export "br_on_non_data-null" (func $9)) + (export "br_on_non_data-data" (func $10)) + (export "br_on_non_data-other" (func $9)) + (export "br-on_non_null" (func $9)) + (export "br-on_non_null-2" (func $13)) + (export "rtt-and-cast-on-func" (func $15)) (export "array-alloc-failure" (func $9)) - (export "init-array-packed" (func $10)) - (export "cast-func-to-struct" (func $12)) - (export "array-copy" (func $13)) + (export "init-array-packed" (func $17)) + (export "cast-func-to-struct" (func $19)) + (export "array-copy" (func $20)) (func $0 (; has Stack IR ;) (local $0 i32) (call $log @@ -221,11 +241,61 @@ (unreachable) ) (func $4 (; has Stack IR ;) + (local $0 anyref) + (local.set $0 + (struct.new_default_with_rtt $struct + (rtt.canon $struct) + ) + ) + (drop + (block $any (result anyref) + (call $log + (i32.const 1) + ) + (drop + (br_on_cast_fail $any + (local.get $0) + (rtt.canon $extendedstruct) + ) + ) + (call $log + (i32.const 999) + ) + (ref.null any) + ) + ) + ) + (func $5 (; has Stack IR ;) + (local $0 anyref) + (local.set $0 + (struct.new_default_with_rtt $extendedstruct + (rtt.canon $extendedstruct) + ) + ) + (drop + (block $any (result anyref) + (call $log + (i32.const 1) + ) + (drop + (br_on_cast_fail $any + (local.get $0) + (rtt.canon $extendedstruct) + ) + ) + (call $log + (i32.const 999) + ) + (ref.null any) + ) + ) + ) + (func $6 (; has Stack IR ;) (call $log (i32.const 0) ) ) - (func $6 (; has Stack IR ;) (param $0 anyref) + (func $8 (; has Stack IR ;) (param $0 anyref) (drop (block $data (result dataref) (drop @@ -242,12 +312,30 @@ ) ) ) + (func $9 (; has Stack IR ;) + (nop) + ) + (func $10 (; has Stack IR ;) + (call $log + (i32.const 1) + ) + ) + (func $13 (; has Stack IR ;) + (drop + (block + (call $log + (i32.const 1) + ) + (unreachable) + ) + ) + ) (func $a-void-func (; has Stack IR ;) (call $log (i32.const 1337) ) ) - (func $8 (; has Stack IR ;) + (func $15 (; has Stack IR ;) (call $log (i32.const 0) ) @@ -273,10 +361,7 @@ (i32.const 4) ) ) - (func $9 (; has Stack IR ;) - (nop) - ) - (func $10 (; has Stack IR ;) (result i32) + (func $17 (; has Stack IR ;) (result i32) (array.get_u $bytes (array.new_with_rtt $bytes (i32.const -43) @@ -289,7 +374,7 @@ (func $call-target (; has Stack IR ;) (param $0 eqref) (nop) ) - (func $12 (; has Stack IR ;) + (func $19 (; has Stack IR ;) (drop (ref.cast (ref.func $call-target) @@ -297,7 +382,7 @@ ) ) ) - (func $13 (; has Stack IR ;) + (func $20 (; has Stack IR ;) (local $0 (ref null $bytes)) (local $1 (ref null $bytes)) (array.set $bytes @@ -378,10 +463,23 @@ [fuzz-exec] calling br_on_cast [LoggingExternalInterface logging 3] [trap unreachable] +[fuzz-exec] calling br_on_failed_cast-1 +[LoggingExternalInterface logging 1] +[fuzz-exec] calling br_on_failed_cast-2 +[LoggingExternalInterface logging 1] +[LoggingExternalInterface logging 999] [fuzz-exec] calling cast-null-anyref-to-gc [LoggingExternalInterface logging 0] [fuzz-exec] calling br_on_data [LoggingExternalInterface logging 1] +[fuzz-exec] calling br_on_non_data-null +[fuzz-exec] calling br_on_non_data-data +[LoggingExternalInterface logging 1] +[fuzz-exec] calling br_on_non_data-other +[fuzz-exec] calling br-on_non_null +[fuzz-exec] calling br-on_non_null-2 +[LoggingExternalInterface logging 1] +[trap unreachable] [fuzz-exec] calling rtt-and-cast-on-func [LoggingExternalInterface logging 0] [LoggingExternalInterface logging 1] diff --git a/test/passes/Oz_fuzz-exec_all-features.wast b/test/passes/Oz_fuzz-exec_all-features.wast index 11a9f798d..93935d5c4 100644 --- a/test/passes/Oz_fuzz-exec_all-features.wast +++ b/test/passes/Oz_fuzz-exec_all-features.wast @@ -180,6 +180,50 @@ ) ) ) + (func "br_on_failed_cast-1" + (local $any anyref) + ;; create a simple $struct, store it in an anyref + (local.set $any + (struct.new_default_with_rtt $struct (rtt.canon $struct)) + ) + (drop + (block $any (result (ref null any)) + (call $log (i32.const 1)) + (drop + ;; try to cast our simple $struct to an extended, which will fail, and + ;; so we will branch, skipping the next logging. + (br_on_cast_fail $any + (local.get $any) + (rtt.canon $extendedstruct) + ) + ) + (call $log (i32.const 999)) ;; we should skip this + (ref.null any) + ) + ) + ) + (func "br_on_failed_cast-2" + (local $any anyref) + ;; create an $extendedstruct, store it in an anyref + (local.set $any + (struct.new_default_with_rtt $extendedstruct (rtt.canon $extendedstruct)) + ) + (drop + (block $any (result (ref null any)) + (call $log (i32.const 1)) + (drop + ;; try to cast our simple $struct to an extended, which will succeed, and + ;; so we will continue to the next logging. + (br_on_cast_fail $any + (local.get $any) + (rtt.canon $extendedstruct) + ) + ) + (call $log (i32.const 999)) + (ref.null any) + ) + ) + ) (func "cast-null-anyref-to-gc" ;; a null anyref is a literal which is not even of GC data, as it's not an ;; array or a struct, so our casting code should not assume it is. it is ok @@ -208,6 +252,77 @@ ) ) ) + (func "br_on_non_data-null" + (local $x anyref) + (drop + (block $any (result anyref) + (drop + (br_on_non_data $any (local.get $x)) + ) + ;; $x is a null, and so it is not data, and the branch will be taken, and no + ;; logging will occur. + (call $log (i32.const 1)) + (ref.null any) + ) + ) + ) + (func "br_on_non_data-data" + (local $x anyref) + ;; set x to valid data + (local.set $x + (struct.new_default_with_rtt $struct + (rtt.canon $struct) + ) + ) + (drop + (block $any (result anyref) + (drop + (br_on_non_data $any (local.get $x)) + ) + ;; $x refers to valid data, and so we will not branch, and will log. + (call $log (i32.const 1)) + (ref.null any) + ) + ) + ) + (func "br_on_non_data-other" + (local $x anyref) + ;; set x to something that is not null, but also not data + (local.set $x + (ref.func $a-void-func) + ) + (drop + (block $any (result anyref) + (drop + (br_on_non_data $any (local.get $x)) + ) + ;; $x refers to a function, so we will branch, and not log + (call $log (i32.const 1)) + (ref.null any) + ) + ) + ) + (func "br-on_non_null" + (drop + (block $non-null (result (ref any)) + (br_on_non_null $non-null (ref.func $a-void-func)) + ;; $x refers to a function, which is not null, so we will branch, and not + ;; log + (call $log (i32.const 1)) + (unreachable) + ) + ) + ) + (func "br-on_non_null-2" + (drop + (block $non-null (result (ref any)) + (br_on_non_null $non-null (ref.null any)) + ;; $x is null, and so we will not branch, and log and then trap + (call $log (i32.const 1)) + (unreachable) + ) + ) + ) (func $a-void-func (call $log (i32.const 1337)) ) diff --git a/test/passes/remove-unused-brs_all-features.txt b/test/passes/remove-unused-brs_all-features.txt index 6f68797cf..abfcc3e76 100644 --- a/test/passes/remove-unused-brs_all-features.txt +++ b/test/passes/remove-unused-brs_all-features.txt @@ -1,8 +1,8 @@ (module (type $vector (array (mut i32))) (type $struct (struct (field (ref null $vector)))) - (type $i32_=>_none (func (param i32))) (type $ref|func|_=>_none (func (param (ref func)))) + (type $i32_=>_none (func (param i32))) (type $none_=>_ref?|$struct| (func (result (ref null $struct)))) (type $none_=>_f64 (func (result f64))) (type $none_=>_i32 (func (result i32))) @@ -123,6 +123,20 @@ ) ) ) + (call $log + (i32.const 8) + ) + (drop + (block $non-null (result (ref $ref|func|_=>_none)) + (br $non-null + (ref.func $br_on-to-br) + ) + (call $log + (i32.const 9) + ) + (ref.func $br_on-to-br) + ) + ) ) (func $br_on-to-flow (drop diff --git a/test/passes/remove-unused-brs_all-features.wast b/test/passes/remove-unused-brs_all-features.wast index 4ee676589..9614f7f5c 100644 --- a/test/passes/remove-unused-brs_all-features.wast +++ b/test/passes/remove-unused-brs_all-features.wast @@ -107,6 +107,15 @@ (i31.new (i32.const 1337)) ) ) + (call $log (i32.const 8)) + (drop + (block $non-null (result (ref func)) + ;; a non-null reference is not null, and the br is always taken + (br_on_non_null $non-null (ref.func $br_on-to-br)) + (call $log (i32.const 9)) + (ref.func $br_on-to-br) + ) + ) ) ;; a br_on of the obviously incorrect kind can just flow out the value as the |