diff options
-rw-r--r-- | README.md | 10 | ||||
-rwxr-xr-x | scripts/fuzz_opt.py | 19 | ||||
-rw-r--r-- | src/wasm-stack.h | 15 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 159 | ||||
-rw-r--r-- | test/lit/basic/reference-types.wast | 154 | ||||
-rw-r--r-- | test/lit/cast-and-recast-tuple.wast | 444 | ||||
-rw-r--r-- | test/lit/cast-and-recast.wast | 185 | ||||
-rw-r--r-- | test/lit/reftypes-without-gc.wast | 30 |
8 files changed, 949 insertions, 67 deletions
@@ -147,6 +147,16 @@ There are a few differences between Binaryen IR and the WebAssembly language: much about this when writing Binaryen passes. For more details see the `requiresNonNullableLocalFixups()` hook in `pass.h` and the `LocalStructuralDominance` class. + * `br_if` output types are more refined in Binaryen IR: they have the type of + the value, when a value flows in. In the wasm spec the type is that of the + branch target, which may be less refined. Using the more refined type here + ensures that we optimize in the best way possible, using all the type + information, but it does mean that some roundtripping operations may look a + little different. In particular, when we emit a `br_if` whose type is more + refined in Binaryen IR then we emit a cast right after it, so that the + output has the right type in the wasm spec. That may cause a few bytes of + extra size in rare cases (we avoid this overhead in the common case where + the `br_if` value is unused). * Strings * Binaryen allows string views (`stringview_wtf16` etc.) to be cast using `ref.cast`. This simplifies the IR, as it allows `ref.cast` to always be diff --git a/scripts/fuzz_opt.py b/scripts/fuzz_opt.py index e91190d62..da221b94e 100755 --- a/scripts/fuzz_opt.py +++ b/scripts/fuzz_opt.py @@ -308,7 +308,24 @@ INITIAL_CONTENTS_IGNORE = [ 'fannkuch3_manyopts_dwarf.wasm', 'fib2_emptylocspan_dwarf.wasm', 'fannkuch3_dwarf.wasm', + 'dwarf-local-order.wasm', + 'strip-producers.wasm', 'multi_unit_abbrev_noprint.wasm', + 'reverse_dwarf_abbrevs.wasm', + 'print_g.wasm', + 'print_g_strip-dwarf.wasm', + 'fannkuch0_dwarf.wasm', + 'dwarfdump_roundtrip_dwarfdump.wasm', + 'dwarfdump.wasm', + 'fannkuch3_dwarf.wasm', + 'dwarf-local-order.wasm', + 'dwarf_unit_with_no_abbrevs_noprint.wasm', + 'strip-debug.wasm', + 'multi_line_table_dwarf.wasm', + 'dwarf_with_exceptions.wasm', + 'strip-dwarf.wasm', + 'ignore_missing_func_dwarf.wasm', + 'print.wasm', # TODO fuzzer support for multimemory 'multi-memories-atomics64.wast', 'multi-memories-basics.wast', @@ -1183,7 +1200,7 @@ def filter_exports(wasm, output, keep): f.write(json.dumps(graph)) # prune the exports - run([in_bin('wasm-metadce'), wasm, '-o', output, '--graph-file', 'graph.json', '-all']) + run([in_bin('wasm-metadce'), wasm, '-o', output, '--graph-file', 'graph.json'] + FEATURE_OPTS) # Fuzz the interpreter with --fuzz-exec -tnh. The tricky thing with traps-never- diff --git a/src/wasm-stack.h b/src/wasm-stack.h index e51e5eb91..85003f9ea 100644 --- a/src/wasm-stack.h +++ b/src/wasm-stack.h @@ -164,6 +164,21 @@ private: // and StringSliceWTF if their non-string operands are already LocalGets. // Record those LocalGets here. std::unordered_set<LocalGet*> deferredGets; + + // Track which br_ifs need handling of their output values, which is the case + // when they have a value that is more refined than the wasm type system + // allows atm (and they are not dropped, in which case the type would not + // matter). See https://github.com/WebAssembly/binaryen/pull/6390 for more on + // the difference. As a result of the difference, we will insert extra casts + // to ensure validation in the wasm spec. The wasm spec will hopefully improve + // to use the more refined type as well, which would remove the need for this + // hack. + // + // Each br_if present as a key here is mapped to the unrefined type for it. + // That is, the br_if has a type in Binaryen IR that is too refined, and the + // map contains the unrefined one (which we need to know the local types, as + // we'll stash the unrefined values and then cast them). + std::unordered_map<Break*, Type> brIfsNeedingHandling; }; // Takes binaryen IR and converts it to something else (binary or stack IR) diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 1d7fe5255..50c13da65 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -65,6 +65,56 @@ void BinaryInstWriter::visitLoop(Loop* curr) { void BinaryInstWriter::visitBreak(Break* curr) { o << int8_t(curr->condition ? BinaryConsts::BrIf : BinaryConsts::Br) << U32LEB(getBreakIndex(curr->name)); + + // See comment on |brIfsNeedingHandling| for the extra casts we need to emit + // here for certain br_ifs. + auto iter = brIfsNeedingHandling.find(curr); + if (iter != brIfsNeedingHandling.end()) { + auto unrefinedType = iter->second; + auto type = curr->type; + assert(type.size() == unrefinedType.size()); + + assert(curr->type.hasRef()); + + auto emitCast = [&](Type to) { + // Shim a tiny bit of IR, just enough to get visitRefCast to see what we + // are casting, and to emit the proper thing. + RefCast cast; + cast.type = to; + cast.ref = nullptr; + visitRefCast(&cast); + }; + + if (!type.isTuple()) { + // Simple: Just emit a cast, and then the type matches Binaryen IR's. + emitCast(type); + } else { + // Tuples are trickier to handle, and we need to use scratch locals. Stash + // all the values on the stack to those locals, then reload them, casting + // as we go. + // + // We must track how many scratch locals we've used from each type as we + // go, as a type might appear multiple times in the tuple. We allocated + // enough for each, in a contiguous range, so we just increment as we go. + std::unordered_map<Type, Index> scratchTypeUses; + for (Index i = 0; i < unrefinedType.size(); i++) { + auto t = unrefinedType[unrefinedType.size() - i - 1]; + assert(scratchLocals.find(t) != scratchLocals.end()); + auto localIndex = scratchLocals[t] + scratchTypeUses[t]++; + o << int8_t(BinaryConsts::LocalSet) << U32LEB(localIndex); + } + for (Index i = 0; i < unrefinedType.size(); i++) { + auto t = unrefinedType[i]; + auto localIndex = scratchLocals[t] + --scratchTypeUses[t]; + o << int8_t(BinaryConsts::LocalGet) << U32LEB(localIndex); + if (t.isRef()) { + // Note that we cast all types here, when perhaps only some of the + // tuple's lanes need that. This is simpler. + emitCast(type[i]); + } + } + } + } } void BinaryInstWriter::visitSwitch(Switch* curr) { @@ -2664,11 +2714,116 @@ InsertOrderedMap<Type, Index> BinaryInstWriter::countScratchLocals() { auto& count = scratches[Type::i32]; count = std::max(count, numScratches); } - }; - ScratchLocalFinder finder(*this); + // As mentioned in BinaryInstWriter::visitBreak, the type of br_if with a + // value may be more refined in Binaryen IR compared to the wasm spec, as we + // give it the type of the value, while the spec gives it the type of the + // block it targets. To avoid problems we must handle the case where a br_if + // has a value, the value is more refined then the target, and the value is + // not dropped (the last condition is very rare in real-world wasm, making + // all of this a quite unusual situation). First, detect such situations by + // seeing if we have br_ifs that return reference types at all. We do so by + // counting them, and as we go we ignore ones that are dropped, since a + // dropped value is not a problem for us. + // + // Note that we do not check all the conditions here, such as if the type + // matches the break target, or if the parent is a cast, which we leave for + // a more expensive analysis later, which we only run if we see something + // suspicious here. + Index numDangerousBrIfs = 0; + + void visitBreak(Break* curr) { + if (curr->type.hasRef()) { + numDangerousBrIfs++; + } + } + + void visitDrop(Drop* curr) { + if (curr->value->is<Break>() && curr->value->type.hasRef()) { + // The value is exactly a br_if of a ref, that we just visited before + // us. Undo the ++ from there as it can be ignored. + assert(numDangerousBrIfs > 0); + numDangerousBrIfs--; + } + } + } finder(*this); finder.walk(func->body); + if (!finder.numDangerousBrIfs || !parent.getModule()->features.hasGC()) { + // Nothing more to do: either no such br_ifs, or GC is not enabled. + // + // The explicit check for GC is here because if only reference types are + // enabled then we still may seem to need a fixup here, e.g. if a ref.func + // is br_if'd to a block of type funcref. But that only appears that way + // because in Binaryen IR we allow non-nullable types even without GC (and + // if GC is not enabled then we always emit nullable types in the binary). + // That is, even if we see a type difference without GC, it will vanish in + // the binary format; there is never a need to add any ref.casts without GC + // being enabled. + return std::move(finder.scratches); + } + + // There are dangerous-looking br_ifs, so we must do the harder work to + // actually investigate them in detail, including tracking block types. By + // being fully precise here, we'll only emit casts when absolutely necessary, + // which avoids repeated roundtrips adding more and more code. + struct RefinementScanner : public ExpressionStackWalker<RefinementScanner> { + BinaryInstWriter& writer; + ScratchLocalFinder& finder; + + RefinementScanner(BinaryInstWriter& writer, ScratchLocalFinder& finder) + : writer(writer), finder(finder) {} + + void visitBreak(Break* curr) { + // See if this is one of the dangerous br_ifs we must handle. + if (!curr->type.hasRef()) { + // Not even a reference. + return; + } + auto* parent = getParent(); + if (parent) { + if (parent->is<Drop>()) { + // It is dropped anyhow. + return; + } + if (auto* cast = parent->dynCast<RefCast>()) { + if (Type::isSubType(cast->type, curr->type)) { + // It is cast to the same type or a better one. In particular this + // handles the case of repeated roundtripping: After the first + // roundtrip we emit a cast that we'll identify here, and not emit + // an additional one. + return; + } + } + } + auto* breakTarget = findBreakTarget(curr->name); + auto unrefinedType = breakTarget->type; + if (unrefinedType == curr->type) { + // It has the proper type anyhow. + return; + } + + // Mark the br_if as needing handling, and add the type to the set of + // types we need scratch tuple locals for (if relevant). + writer.brIfsNeedingHandling[curr] = unrefinedType; + + if (unrefinedType.isTuple()) { + // We must allocate enough scratch locals for this tuple. Note that we + // may need more than one per type in the tuple, if a type appears more + // than once, so we count their appearances. + InsertOrderedMap<Type, Index> scratchTypeUses; + for (auto t : unrefinedType) { + scratchTypeUses[t]++; + } + for (auto& [type, uses] : scratchTypeUses) { + auto& count = finder.scratches[type]; + count = std::max(count, uses); + } + } + } + } refinementScanner(*this, finder); + refinementScanner.walk(func->body); + return std::move(finder.scratches); } diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index c4bfe8a45..7f9ba4219 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -25,7 +25,9 @@ ;; CHECK-BIN: (type $sig_funcref (func (param funcref))) - ;; CHECK-BIN: (type $3 (func (result funcref))) + ;; CHECK-BIN: (type $3 (func)) + + ;; CHECK-BIN: (type $4 (func (result funcref))) ;; CHECK-BIN: (type $sig_eqref (func (param eqref))) (type $sig_eqref (func (param eqref))) @@ -43,8 +45,6 @@ ;; CHECK-TEXT: (import "env" "import_global" (global $import_global eqref)) ;; CHECK-TEXT: (import "env" "import_func" (func $import_func (type $8) (param eqref) (result funcref))) - ;; CHECK-BIN: (type $5 (func)) - ;; CHECK-BIN: (type $6 (func (result eqref))) ;; CHECK-BIN: (type $7 (func (param i32))) @@ -128,7 +128,7 @@ ;; CHECK-TEXT: (func $foo (type $5) ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $foo (type $5) + ;; CHECK-BIN: (func $foo (type $3) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $foo) @@ -140,11 +140,11 @@ ;; CHECK-BIN-NODEBUG: (type $2 (func (param funcref))) - ;; CHECK-BIN-NODEBUG: (type $3 (func (result funcref))) + ;; CHECK-BIN-NODEBUG: (type $3 (func)) - ;; CHECK-BIN-NODEBUG: (type $4 (func (param eqref))) + ;; CHECK-BIN-NODEBUG: (type $4 (func (result funcref))) - ;; CHECK-BIN-NODEBUG: (type $5 (func)) + ;; CHECK-BIN-NODEBUG: (type $5 (func (param eqref))) ;; CHECK-BIN-NODEBUG: (type $6 (func (result eqref))) @@ -722,7 +722,7 @@ ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $test (type $5) + ;; CHECK-BIN: (func $test (type $3) ;; CHECK-BIN-NEXT: (local $local_eqref eqref) ;; CHECK-BIN-NEXT: (local $local_funcref funcref) ;; CHECK-BIN-NEXT: (local $local_anyref anyref) @@ -913,9 +913,11 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $label$3 (result eqref) - ;; CHECK-BIN-NEXT: (br_if $label$3 - ;; CHECK-BIN-NEXT: (ref.null none) - ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (br_if $label$3 + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -937,17 +939,21 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $label$6 (result funcref) - ;; CHECK-BIN-NEXT: (br_if $label$6 - ;; CHECK-BIN-NEXT: (ref.null nofunc) - ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: (ref.cast nullfuncref + ;; CHECK-BIN-NEXT: (br_if $label$6 + ;; CHECK-BIN-NEXT: (ref.null nofunc) + ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $label$7 (result funcref) - ;; CHECK-BIN-NEXT: (br_if $label$7 - ;; CHECK-BIN-NEXT: (ref.func $foo) - ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: (ref.cast (ref $3) + ;; CHECK-BIN-NEXT: (br_if $label$7 + ;; CHECK-BIN-NEXT: (ref.func $foo) + ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -969,25 +975,31 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $label$10 (result anyref) - ;; CHECK-BIN-NEXT: (br_if $label$10 - ;; CHECK-BIN-NEXT: (ref.null none) - ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (br_if $label$10 + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $label$11 (result anyref) - ;; CHECK-BIN-NEXT: (br_if $label$11 - ;; CHECK-BIN-NEXT: (local.get $local_eqref) - ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: (ref.cast eqref + ;; CHECK-BIN-NEXT: (br_if $label$11 + ;; CHECK-BIN-NEXT: (local.get $local_eqref) + ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $label$12 (result anyref) - ;; CHECK-BIN-NEXT: (br_if $label$12 - ;; CHECK-BIN-NEXT: (ref.null none) - ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (br_if $label$12 + ;; CHECK-BIN-NEXT: (ref.null none) + ;; CHECK-BIN-NEXT: (i32.const 1) + ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: ) @@ -1343,6 +1355,8 @@ ) (drop (block (result eqref) + ;; Note that we will end up emitting a cast here, and in several cases + ;; below, because the br_if value is more refined than the target block. (br_if 0 (ref.null eq) (i32.const 1)) ) ) @@ -1669,7 +1683,7 @@ ;; CHECK-TEXT-NEXT: (local $local_funcref funcref) ;; CHECK-TEXT-NEXT: (local.get $local_funcref) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $return_funcref_local (type $3) (result funcref) + ;; CHECK-BIN: (func $return_funcref_local (type $4) (result funcref) ;; CHECK-BIN-NEXT: (local $local_funcref funcref) ;; CHECK-BIN-NEXT: (local.get $local_funcref) ;; CHECK-BIN-NEXT: ) @@ -1681,7 +1695,7 @@ ;; CHECK-TEXT: (func $return_funcref_global (type $3) (result funcref) ;; CHECK-TEXT-NEXT: (global.get $global_funcref) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $return_funcref_global (type $3) (result funcref) + ;; CHECK-BIN: (func $return_funcref_global (type $4) (result funcref) ;; CHECK-BIN-NEXT: (global.get $global_funcref) ;; CHECK-BIN-NEXT: ) (func $return_funcref_global (result funcref) @@ -1691,7 +1705,7 @@ ;; CHECK-TEXT: (func $return_funcref_null (type $3) (result funcref) ;; CHECK-TEXT-NEXT: (ref.null nofunc) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $return_funcref_null (type $3) (result funcref) + ;; CHECK-BIN: (func $return_funcref_null (type $4) (result funcref) ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: ) (func $return_funcref_null (result funcref) @@ -1701,7 +1715,7 @@ ;; CHECK-TEXT: (func $return_funcref_func (type $3) (result funcref) ;; CHECK-TEXT-NEXT: (ref.func $foo) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $return_funcref_func (type $3) (result funcref) + ;; CHECK-BIN: (func $return_funcref_func (type $4) (result funcref) ;; CHECK-BIN-NEXT: (ref.func $foo) ;; CHECK-BIN-NEXT: ) (func $return_funcref_func (result funcref) @@ -1818,7 +1832,7 @@ ;; CHECK-TEXT-NEXT: (ref.null nofunc) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $returns_funcref (type $3) (result funcref) + ;; CHECK-BIN: (func $returns_funcref (type $4) (result funcref) ;; CHECK-BIN-NEXT: (local $local_funcref funcref) ;; CHECK-BIN-NEXT: (return ;; CHECK-BIN-NEXT: (local.get $local_funcref) @@ -1892,7 +1906,7 @@ ;; CHECK-TEXT-NEXT: (ref.func $ref-taken-but-not-in-table) ;; CHECK-TEXT-NEXT: ) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-user (type $5) + ;; CHECK-BIN: (func $ref-user (type $3) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (ref.func $ref-taken-but-not-in-table) ;; CHECK-BIN-NEXT: ) @@ -1908,7 +1922,7 @@ ;; CHECK-TEXT: (func $ref-taken-but-not-in-table (type $5) ;; CHECK-TEXT-NEXT: (nop) ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (func $ref-taken-but-not-in-table (type $5) + ;; CHECK-BIN: (func $ref-taken-but-not-in-table (type $3) ;; CHECK-BIN-NEXT: (nop) ;; CHECK-BIN-NEXT: ) (func $ref-taken-but-not-in-table) @@ -1919,7 +1933,7 @@ ;; CHECK-BIN-NODEBUG: (export "export_global" (global $gimport$0)) -;; CHECK-BIN-NODEBUG: (func $0 (type $4) (param $0 eqref) +;; CHECK-BIN-NODEBUG: (func $0 (type $5) (param $0 eqref) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -1931,11 +1945,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $3 (type $5) +;; CHECK-BIN-NODEBUG: (func $3 (type $3) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $4 (type $5) +;; CHECK-BIN-NODEBUG: (func $4 (type $3) ;; CHECK-BIN-NODEBUG-NEXT: (local $0 eqref) ;; CHECK-BIN-NODEBUG-NEXT: (local $1 funcref) ;; CHECK-BIN-NODEBUG-NEXT: (local $2 anyref) @@ -2056,15 +2070,15 @@ ;; CHECK-BIN-NODEBUG-NEXT: (call $2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (call_indirect $0 (type $4) +;; CHECK-BIN-NODEBUG-NEXT: (call_indirect $0 (type $5) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (call_indirect $0 (type $4) +;; CHECK-BIN-NODEBUG-NEXT: (call_indirect $0 (type $5) ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$0) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG-NEXT: (call_indirect $0 (type $4) +;; CHECK-BIN-NODEBUG-NEXT: (call_indirect $0 (type $5) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 0) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2126,9 +2140,11 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $label$3 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$3 -;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$3 +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2150,17 +2166,21 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $label$6 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$6 -;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref +;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$6 +;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $label$7 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$7 -;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref $3) +;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$7 +;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2182,25 +2202,31 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $label$10 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$10 -;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$10 +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $label$11 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$11 -;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast eqref +;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$11 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $label$12 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$12 -;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) -;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (br_if $label$12 +;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) +;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) +;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2484,20 +2510,20 @@ ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $8 (type $3) (result funcref) +;; CHECK-BIN-NODEBUG: (func $8 (type $4) (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (local $0 funcref) ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $9 (type $3) (result funcref) +;; CHECK-BIN-NODEBUG: (func $9 (type $4) (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (global.get $global$1) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $10 (type $3) (result funcref) +;; CHECK-BIN-NODEBUG: (func $10 (type $4) (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $11 (type $3) (result funcref) +;; CHECK-BIN-NODEBUG: (func $11 (type $4) (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $3) ;; CHECK-BIN-NODEBUG-NEXT: ) @@ -2534,7 +2560,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $19 (type $3) (result funcref) +;; CHECK-BIN-NODEBUG: (func $19 (type $4) (result funcref) ;; CHECK-BIN-NODEBUG-NEXT: (local $0 funcref) ;; CHECK-BIN-NODEBUG-NEXT: (return ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) @@ -2556,12 +2582,12 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $22 (type $5) +;; CHECK-BIN-NODEBUG: (func $22 (type $3) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (ref.func $23) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) -;; CHECK-BIN-NODEBUG: (func $23 (type $5) +;; CHECK-BIN-NODEBUG: (func $23 (type $3) ;; CHECK-BIN-NODEBUG-NEXT: (nop) ;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/cast-and-recast-tuple.wast b/test/lit/cast-and-recast-tuple.wast new file mode 100644 index 000000000..7f0886fce --- /dev/null +++ b/test/lit/cast-and-recast-tuple.wast @@ -0,0 +1,444 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Part of cast-and-recast.wast, but containing tuples. This is split out +;; because we do not roundtrip tuple-containing code properly. We also use only +;; one roundtrip because of the accumulation of tuple logic, which would +;; otherwise make the output here very hard to read. + +;; RUN: wasm-opt %s -all --roundtrip -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct ))) + (type $A (sub (struct))) + ;; CHECK: (type $B (sub $A (struct ))) + (type $B (sub $A (struct))) + ) + + ;; CHECK: (func $test-local-tuple-1 (type $5) (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; CHECK-NEXT: (local $2 (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local $3 (ref $B)) + ;; CHECK-NEXT: (local $4 (tuple (ref $A) i32)) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (block $label$1 (type $3) (result (ref $A) i32) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-local-tuple-1 (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; A dropped tuple that contains a ref. As it is dropped, we do not need to + ;; do anything for this br_if. However, due to our general handling of + ;; tuples the code here will grow quite a bit due the roundtrip, but we can + ;; at least verify that there is no ref.cast added anywhere here. + (block $out (result (ref $A) i32) + (tuple.drop 2 + (br_if $out + (tuple.make 2 + (local.get $B) + (i32.const 3) + ) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $test-local-tuple-2 (type $9) (param $B (ref $B)) (param $x i32) (result i32 i32) + ;; CHECK-NEXT: (local $temp i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 (tuple i32 i32)) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 (tuple i32 i32)) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (block $label$1 (type $4) (result i32 i32) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-local-tuple-2 (param $B (ref $B)) (param $x i32) (result i32 i32) + (local $temp (tuple i32 i32)) + ;; This tuple is not dropped, but it contains no references, so we do not + ;; need to do anything for the br_if, and we add no casts. + (block $out (result i32 i32) + (local.set $temp + (br_if $out + (tuple.make 2 + (i32.const -1) + (i32.const 3) + ) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $test-local-tuple-3 (type $5) (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; CHECK-NEXT: (local $temp (ref $B)) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local $5 (ref $B)) + ;; CHECK-NEXT: (local $6 (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (block $label$1 (type $6) (result (ref $B) i32) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-local-tuple-3 (param $B (ref $B)) (param $x i32) (result anyref i32) + (local $temp (tuple (ref $B) i32)) + ;; This is not dropped and has a reference, but it has the right type, so no + ;; cast is needed. + (block $out (result (ref $B) i32) + (local.set $temp + (br_if $out + (tuple.make 2 + (local.get $B) + (i32.const 3) + ) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $test-local-tuple-4-bad (type $5) (param $B (ref $B)) (param $x i32) (result anyref i32) + ;; CHECK-NEXT: (local $temp (ref $B)) + ;; CHECK-NEXT: (local $3 (ref $A)) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 (tuple (ref $B) i32)) + ;; CHECK-NEXT: (local $7 (ref $B)) + ;; CHECK-NEXT: (local $8 (ref $B)) + ;; CHECK-NEXT: (local $9 (tuple (ref $A) i32)) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (block $label$1 (type $3) (result (ref $A) i32) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (tuple.extract 2 0 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 2 1 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-local-tuple-4-bad (param $B (ref $B)) (param $x i32) (result anyref i32) + (local $temp (tuple (ref $B) i32)) + ;; As above, but none of the mitigating circumstances happens: we have a + ;; tuple with a reference that is refined compared to the break target. As a + ;; result we must fix this up, which we do by adding locals, saving the + ;; br_if's output to them, and then loading from those locals and casting. + ;; + ;; Comparing to $test-local-tuple-4, we end up with 3 more locals, and also + ;; there is now a ref.cast. + (block $out (result (ref $A) i32) + (local.set $temp + (br_if $out + (tuple.make 2 + (local.get $B) + (i32.const 3) + ) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $test-local-tuple-4-bad-dupes (type $10) (param $B (ref $B)) (param $x i32) (result i32 anyref i32) + ;; CHECK-NEXT: (local $temp (ref $B)) + ;; CHECK-NEXT: (local $3 (ref $B)) + ;; CHECK-NEXT: (local $4 (ref $A)) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $scratch i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 (tuple i32 (ref $B) i32)) + ;; CHECK-NEXT: (local $11 (ref $B)) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 (ref $B)) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (local $15 (ref $B)) + ;; CHECK-NEXT: (local $16 (tuple i32 (ref $A) i32)) + ;; CHECK-NEXT: (local.set $16 + ;; CHECK-NEXT: (block $label$1 (type $7) (result i32 (ref $A) i32) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (i32.const -3) + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (tuple.extract 3 0 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (tuple.extract 3 1 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (tuple.extract 3 2 + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.tee $scratch + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (block (result (ref $B)) + ;; CHECK-NEXT: (local.set $15 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.make 3 + ;; CHECK-NEXT: (tuple.extract 3 0 + ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 3 1 + ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (tuple.extract 3 2 + ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-local-tuple-4-bad-dupes (param $B (ref $B)) (param $x i32) (result i32 anyref i32) + (local $temp (tuple (ref $B) i32)) + ;; As above, but now the tuple has multiple appearances of the same type in + ;; it, each of which needs its own scratch local. We can see in the output + ;; that the tuple.extracts use different locals for the first and last i32. + ;; For easier reading, here is the wami output of the binary: + ;; + ;; (func $func4 (param $var0 (ref $type1)) (param $var1 i32) (result i32) (result anyref) (result i32) + ;; (local $var2 (ref $type1)) + ;; (local $var3 (ref $type1)) + ;; (local $var4 (ref $type0)) + ;; (local $var5 i32) + ;; (local $var6 i32) + ;; (local $var7 i32) + ;; (local $var8 i32) + ;; (local $var9 i32) + ;; block $label0 (result i32) (result (ref $type0)) (result i32) + ;; i32.const -3 + ;; local.get $var0 + ;; i32.const 3 + ;; local.get $var1 + ;; br_if $label0 + ;; local.set $var8 ;; saves 3 + ;; local.set $var4 ;; saves the ref + ;; local.set $var9 ;; saves -3 + ;; local.get $var9 ;; gets -3 + ;; local.get $var4 ;; gets the ref + ;; ref.cast $type1 ;; casts the ref + ;; local.get $var8 ;; gets 3 + ;; local.set $var7 + ;; local.set $var3 + ;; local.tee $var6 + ;; drop + ;; local.get $var3 + ;; local.get $var7 + ;; local.set $var5 + ;; local.set $var2 + ;; unreachable + ;; end $label0 + ;; ) + ;; + (block $out (result i32 (ref $A) i32) + (local.set $temp + (br_if $out + (tuple.make 3 + (i32.const -3) ;; this was added + (local.get $B) + (i32.const 3) + ) + (local.get $x) + ) + ) + (unreachable) + ) + ) +) diff --git a/test/lit/cast-and-recast.wast b/test/lit/cast-and-recast.wast new file mode 100644 index 000000000..c25ccc69a --- /dev/null +++ b/test/lit/cast-and-recast.wast @@ -0,0 +1,185 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test that our hack for br_if output types does not cause the binary to grow +;; linearly with each roundtrip (note the three roundtrips here). When we emit +;; a br_if whose output type is not refined enough (Binaryen IR uses the value's +;; type; wasm uses the target's) then we add a cast. + +;; RUN: wasm-opt %s -all --roundtrip --roundtrip --roundtrip -S -o - | filecheck %s + +(module + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $A (sub (struct ))) + (type $A (sub (struct))) + ;; CHECK: (type $B (sub $A (struct ))) + (type $B (sub $A (struct))) + ;; CHECK: (type $C (sub $B (struct ))) + (type $C (sub $B (struct))) + ) + + ;; CHECK: (func $test (type $3) (param $B (ref $B)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $B (ref $B)) (param $x i32) (result anyref) + (block $out (result (ref $A)) + ;; The br_if's value is of type $B which is more precise than the block's + ;; type, $A, so we emit a cast here, but only one despite the three + ;; roundtrips. + (br_if $out + (local.get $B) + (local.get $x) + ) + ) + ) + + ;; CHECK: (func $test-cast (type $3) (param $B (ref $B)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-cast (param $B (ref $B)) (param $x i32) (result anyref) + ;; This is the result of a single roundtrip: there is a cast. We should not + ;; modify this function at all in additional roundtrips. + (block $out (result (ref $A)) + (ref.cast (ref $B) + (br_if $out + (local.get $B) + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $test-cast-more (type $3) (param $B (ref $B)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (ref.cast (ref $C) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-cast-more (param $B (ref $B)) (param $x i32) (result anyref) + ;; As above but the cast is more refined. Again, we do not need an + ;; additional cast. + (block $out (result (ref $A)) + (ref.cast (ref $C) ;; this changed + (br_if $out + (local.get $B) + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $test-cast-less (type $3) (param $B (ref $B)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-cast-less (param $B (ref $B)) (param $x i32) (result anyref) + ;; As above but the cast is less refined. As a result we'd add a cast to $B + ;; (but we refine casts automatically in finalize(), so this cast becomes a + ;; cast to $B anyhow, and as a result we have only one cast here). + (block $out (result (ref $A)) + (ref.cast (ref $A) ;; this changed + (br_if $out + (local.get $B) + (local.get $x) + ) + ) + ) + ) + + ;; CHECK: (func $test-local (type $3) (param $B (ref $B)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (local $temp (ref $B)) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (local.set $temp + ;; CHECK-NEXT: (ref.cast (ref $B) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-local (param $B (ref $B)) (param $x i32) (result anyref) + (local $temp (ref $B)) + ;; As above, but with local.set that receives the br_if's value, verifying + ;; it is refined. We emit a cast here. + (block $out (result (ref $A)) + (local.set $temp + (br_if $out + (local.get $B) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $test-drop (type $3) (param $B (ref $B)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $B) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-drop (param $B (ref $B)) (param $x i32) (result anyref) + ;; As above, but with a drop of the br_if value. We do not emit a cast here. + (block $out (result (ref $A)) + (drop + (br_if $out + (local.get $B) + (local.get $x) + ) + ) + (unreachable) + ) + ) + + ;; CHECK: (func $test-same (type $4) (param $A (ref $A)) (param $x i32) (result anyref) + ;; CHECK-NEXT: (block $label$1 (result (ref $A)) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (local.get $A) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test-same (param $A (ref $A)) (param $x i32) (result anyref) + ;; As above, but now we use $A everywhere, which means there is no + ;; difference between the type in Binaryen IR and wasm, so we do not need + ;; to emit any extra cast here. + (block $out (result (ref $A)) + (br_if $out + (local.get $A) + (local.get $x) + ) + ) + ) +) diff --git a/test/lit/reftypes-without-gc.wast b/test/lit/reftypes-without-gc.wast new file mode 100644 index 000000000..e71aa5269 --- /dev/null +++ b/test/lit/reftypes-without-gc.wast @@ -0,0 +1,30 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Test that we roundtrip br_if with a reference value properly if GC is not +;; enabled. We emit a ref.cast in such cases when GC is used, but if only +;; reference types are enabled (and not GC) then we do not need to emit a cast +;; at all (without GC, there are no situations that require a cast anyhow). If +;; we did emit a cast we would error here on GC not being enabled. + +;; RUN: wasm-opt %s --enable-reference-types --roundtrip -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (param $x i32) (result funcref) + ;; CHECK-NEXT: (block $label$1 (result funcref) + ;; CHECK-NEXT: (br_if $label$1 + ;; CHECK-NEXT: (ref.func $test) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param $x i32) (result funcref) + (block $out (result funcref) + (br_if $out + ;; This has non-nullable type, which is more refined than the block, so + ;; it looks like we need to emit a cast after the br_if. + (ref.func $test) + (local.get $x) + ) + ) + ) +) |