summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md10
-rwxr-xr-xscripts/fuzz_opt.py19
-rw-r--r--src/wasm-stack.h15
-rw-r--r--src/wasm/wasm-stack.cpp159
-rw-r--r--test/lit/basic/reference-types.wast154
-rw-r--r--test/lit/cast-and-recast-tuple.wast444
-rw-r--r--test/lit/cast-and-recast.wast185
-rw-r--r--test/lit/reftypes-without-gc.wast30
8 files changed, 949 insertions, 67 deletions
diff --git a/README.md b/README.md
index 236174ceb..37cda5451 100644
--- a/README.md
+++ b/README.md
@@ -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)
+ )
+ )
+ )
+)