summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-05-15 17:00:54 -0700
committerGitHub <noreply@github.com>2024-05-16 00:00:54 +0000
commite5f2edf4bedb1ab842c2f7ac0dfd58d73e26df7d (patch)
tree258946ed4f722057af16ae640784b7029a1ed62c
parent268feb9d77b66d60649682d62a93baa3daadd143 (diff)
downloadbinaryen-e5f2edf4bedb1ab842c2f7ac0dfd58d73e26df7d.tar.gz
binaryen-e5f2edf4bedb1ab842c2f7ac0dfd58d73e26df7d.tar.bz2
binaryen-e5f2edf4bedb1ab842c2f7ac0dfd58d73e26df7d.zip
Fix binary emitting of br_if with a refined value by emitting a cast (#6510)
This makes us compliant with the wasm spec by adding a cast: we use the refined type for br_if fallthrough values, and the wasm spec uses the branch target. If the two differ, we add a cast after the br_if to make things match. Alternatively we could match the wasm spec's typing in our IR, but we hope the wasm spec will improve here, and so this is will only be temporary in that case. Even if not, this is useful because by using the most refined type in the IR we optimize in the best way possible, and only suffer when we emit fixups in the binary, but in practice those cases are very rare: br_if is almost always dropped rather than used, in real-world code (except for fuzz cases and exploits). We check carefully when a br_if value is actually used (and not dropped) and its type actually differs, and it does not already have a cast. The last condition ensures that we do not keep adding casts over repeated roundtripping.
-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)
+ )
+ )
+ )
+)