diff --git a/src/wasm/wasm-stack-opts.cpp b/src/wasm/wasm-stack-opts.cpp index eae9b6c681f..0050bd95fe5 100644 --- a/src/wasm/wasm-stack-opts.cpp +++ b/src/wasm/wasm-stack-opts.cpp @@ -272,6 +272,79 @@ void StackIROptimizer::local2Stack() { values.push_back(instIndex); } } + + // Optimize the simple case of a multivalue tee and extract. If an expression + // returns a tuple, and that tuple is immediately consumed, we end up with + // something like this: + // + // local.tee $1 + // tuple.extract 4 0 + // local.get $1 + // tuple.extract 4 1 + // local.get $1 + // tuple.extract 4 2 + // local.get $1 + // tuple.extract 4 3 + // + // The tuple is teed, then we extract the components one by one. If no other + // uses of the tee exist, we can just remove all of this. + for (Index instIndex = 0; instIndex < insts.size(); instIndex++) { + auto* inst = insts[instIndex]; + if (!inst) { + continue; + } + auto* tee = inst->origin->dynCast(); + if (!tee || !tee->type.isTuple()) { + continue; + } + + // The tee must be read by exactly the proper number of gets, and no more, + // which is one less than the tuple size (the tee provides one get). + auto size = tee->type.size(); + auto& setInfluences = localGraph.getSetInfluences(tee); + if (setInfluences.size() != size - 1) { + continue; + } + + // This is a tee of a tuple. Look for the expected extracts/gets. Each + // tuple index has 2 items. + bool ok = true; + for (Index i = 0; i < size; i++) { + // Each tuple index has a pair of items. + auto tupleIndexStart = instIndex + i * 2; + if (tupleIndexStart + 1 >= insts.size()) { + ok = false; + break; + } + auto* first = insts[tupleIndexStart]; + auto* second = insts[tupleIndexStart + 1]; + if (!first || !second) { + ok = false; + break; + } + // The first tuple index has the tee (already validated). Others have a + // get. + if (i != 0) { + auto* get = first->origin->dynCast(); + if (!get || get->index != tee->index) { + ok = false; + break; + } + } + // The second item of the pair is an extract. + auto* extract = second->origin->dynCast(); + if (!extract || extract->index != i) { + ok = false; + break; + } + } + if (ok) { + // Optimize. + for (Index i = 0; i < size * 2; i++) { + insts[instIndex + i] = nullptr; + } + } + } } // There may be unnecessary blocks we can remove: blocks without arriving diff --git a/test/lit/passes/optimize-stack-ir-multivalue.wast b/test/lit/passes/optimize-stack-ir-multivalue.wast new file mode 100644 index 00000000000..0eeb4dee870 --- /dev/null +++ b/test/lit/passes/optimize-stack-ir-multivalue.wast @@ -0,0 +1,582 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all --optimize-level=3 --generate-stack-ir --optimize-stack-ir --print-stack-ir | filecheck %s + +;; Also test with roundtrip to verify that parsing does not undo this +;; optimization. +;; RUN: wasm-opt %s -all --optimize-level=3 --generate-stack-ir --optimize-stack-ir --roundtrip --print-stack-ir | filecheck %s --check-prefix=ROUNDTRIP + +(module + ;; CHECK: (type $0 (func (result i32 f64 anyref))) + + ;; CHECK: (type $1 (func (param f64) (result i32 f64 anyref))) + + ;; CHECK: (type $2 (func (param i32 f64 anyref))) + + ;; CHECK: (type $3 (func (result i32 f64))) + + ;; CHECK: (type $4 (func (result i32 f64 anyref eqref))) + + ;; CHECK: (type $5 (func (result i32 f64 anyref anyref))) + + ;; CHECK: (type $6 (func (param f64) (result i32 anyref anyref))) + + ;; CHECK: (func $multivalue-return (type $0) (result i32 f64 anyref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: tuple.make 3 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (type $0 (func (result i32 f64 anyref))) + + ;; ROUNDTRIP: (type $1 (func (param f64) (result i32 f64 anyref))) + + ;; ROUNDTRIP: (type $2 (func (param i32 f64 anyref))) + + ;; ROUNDTRIP: (type $3 (func (result i32 f64))) + + ;; ROUNDTRIP: (type $4 (func (result i32 f64 anyref eqref))) + + ;; ROUNDTRIP: (type $5 (func (result i32 f64 anyref anyref))) + + ;; ROUNDTRIP: (type $6 (func (param f64) (result i32 anyref anyref))) + + ;; ROUNDTRIP: (func $multivalue-return (type $0) (result i32 f64 anyref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $1 f64) + ;; ROUNDTRIP-NEXT: (local $2 anyref) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return (result i32 f64 anyref) + (local $temp (tuple i32 f64 anyref)) + ;; We can remove all these tuple operations after optiming and + ;; roundtripping (though a few locals will be added in roundtripping FIXME). + (tuple.make 3 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp) + ) + (tuple.extract 3 2 + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $multivalue-return-too-short (type $3) (result i32 f64) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 1 + ;; CHECK-NEXT: tuple.make 2 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-too-short (type $3) (result i32 f64) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $1 f64) + ;; ROUNDTRIP-NEXT: (local $2 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_4 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_5 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $2 + ;; ROUNDTRIP-NEXT: local.set $1 + ;; ROUNDTRIP-NEXT: local.tee $temp + ;; ROUNDTRIP-NEXT: local.get $1 + ;; ROUNDTRIP-NEXT: tuple.make 2 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-too-short (result i32 f64) + (local $temp (tuple i32 f64 anyref)) + ;; As above, but we only return 2 of the tuple's 3 items (i.e., we are too + ;; short to fit the pattern), so we do not optimize here. + (tuple.make 2 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $multivalue-return-extra (type $4) (result i32 f64 anyref eqref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: ref.null none + ;; CHECK-NEXT: tuple.make 4 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-extra (type $4) (result i32 f64 anyref eqref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $1 f64) + ;; ROUNDTRIP-NEXT: (local $2 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: ref.null none + ;; ROUNDTRIP-NEXT: tuple.make 4 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-extra (result i32 f64 anyref eqref) + (local $temp (tuple i32 f64 anyref)) + ;; As above, but we add an item to the tuple. We can optimize here. + (tuple.make 4 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp) + ) + (tuple.extract 3 2 + (local.get $temp) + ) + (ref.null eq) + ) + ) + + ;; CHECK: (func $multivalue-return-extra-middle (type $5) (result i32 f64 anyref anyref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 1 + ;; CHECK-NEXT: ref.null none + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: tuple.make 4 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-extra-middle (type $5) (result i32 f64 anyref anyref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $1 f64) + ;; ROUNDTRIP-NEXT: (local $2 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_4 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_5 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $2 + ;; ROUNDTRIP-NEXT: local.set $1 + ;; ROUNDTRIP-NEXT: local.tee $temp + ;; ROUNDTRIP-NEXT: local.get $1 + ;; ROUNDTRIP-NEXT: ref.null none + ;; ROUNDTRIP-NEXT: local.get $2 + ;; ROUNDTRIP-NEXT: tuple.make 4 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-extra-middle (result i32 f64 anyref anyref) + (local $temp (tuple i32 f64 anyref)) + ;; As the last case, but the extra item is in the middle. We cannot + ;; optimize. + (tuple.make 4 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp) + ) + (ref.null eq) + (tuple.extract 3 2 + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $multivalue-return-bad-get (type $1) (param $other f64) (result i32 f64 anyref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $other + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: tuple.make 3 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-bad-get (type $1) (param $other f64) (result i32 f64 anyref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $2 f64) + ;; ROUNDTRIP-NEXT: (local $3 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_5 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_6 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $3 + ;; ROUNDTRIP-NEXT: local.set $2 + ;; ROUNDTRIP-NEXT: local.tee $temp + ;; ROUNDTRIP-NEXT: local.get $other + ;; ROUNDTRIP-NEXT: local.get $3 + ;; ROUNDTRIP-NEXT: tuple.make 3 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-bad-get (param $other f64) (result i32 f64 anyref) + (local $temp (tuple i32 f64 anyref)) + ;; As the first case, but one get has the wrong index, so we do + ;; not optimize. + (tuple.make 3 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (local.get $other) ;; this changed + (tuple.extract 3 2 + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $multivalue-return-non-get (type $0) (result i32 f64 anyref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 1 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: tuple.make 3 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-non-get (type $0) (result i32 f64 anyref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $1 f64) + ;; ROUNDTRIP-NEXT: (local $2 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_4 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_5 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $2 + ;; ROUNDTRIP-NEXT: local.set $1 + ;; ROUNDTRIP-NEXT: local.tee $temp + ;; ROUNDTRIP-NEXT: local.get $1 + ;; ROUNDTRIP-NEXT: local.get $2 + ;; ROUNDTRIP-NEXT: tuple.make 3 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-non-get (result i32 f64 anyref) + (local $temp (tuple i32 f64 anyref)) + ;; As the first case, but one get is replaced by a non-get, so we do + ;; not optimize. + (tuple.make 3 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (nop) ;; this breaks the pattern, appearing where the get + ;; should be + (local.get $temp) + ) + (tuple.extract 3 2 + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $multivalue-return-bad-extract (type $6) (param $other f64) (result i32 anyref anyref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: tuple.make 3 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-bad-extract (type $6) (param $other f64) (result i32 anyref anyref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $2 f64) + ;; ROUNDTRIP-NEXT: (local $3 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_5 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_6 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $3 + ;; ROUNDTRIP-NEXT: local.set $2 + ;; ROUNDTRIP-NEXT: local.tee $temp + ;; ROUNDTRIP-NEXT: local.get $3 + ;; ROUNDTRIP-NEXT: local.get $3 + ;; ROUNDTRIP-NEXT: tuple.make 3 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-bad-extract (param $other f64) (result i32 anyref anyref) + (local $temp (tuple i32 f64 anyref)) + ;; As the first case, but one extract has the wrong index, so we + ;; do not optimize. + (tuple.make 3 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 2 ;; this changed from 1 to 2 + (local.get $temp) + ) + (tuple.extract 3 2 + (local.get $temp) + ) + ) + ) + ;; CHECK: (func $multivalue-return-non-extract (type $1) (param $other f64) (result i32 f64 anyref) + ;; CHECK-NEXT: (local $temp (tuple i32 f64 anyref)) + ;; CHECK-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: local.set $scratch + ;; CHECK-NEXT: local.get $scratch + ;; CHECK-NEXT: tuple.extract 3 1 + ;; CHECK-NEXT: local.get $temp + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: tuple.make 3 + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multivalue-return-non-extract (type $1) (param $other f64) (result i32 f64 anyref) + ;; ROUNDTRIP-NEXT: (local $temp i32) + ;; ROUNDTRIP-NEXT: (local $scratch i32) + ;; ROUNDTRIP-NEXT: (local $3 f64) + ;; ROUNDTRIP-NEXT: (local $4 f64) + ;; ROUNDTRIP-NEXT: (local $5 f64) + ;; ROUNDTRIP-NEXT: (local $6 anyref) + ;; ROUNDTRIP-NEXT: (local $7 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch_8 (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_9 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_10 i32) + ;; ROUNDTRIP-NEXT: (local $scratch_11 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_12 i32) + ;; ROUNDTRIP-NEXT: (local $scratch_13 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_14 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch_8 + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch_8 + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch_8 + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $6 + ;; ROUNDTRIP-NEXT: local.set $3 + ;; ROUNDTRIP-NEXT: local.tee $temp + ;; ROUNDTRIP-NEXT: local.get $temp + ;; ROUNDTRIP-NEXT: local.get $3 + ;; ROUNDTRIP-NEXT: local.get $6 + ;; ROUNDTRIP-NEXT: local.set $7 + ;; ROUNDTRIP-NEXT: local.set $4 + ;; ROUNDTRIP-NEXT: local.get $4 + ;; ROUNDTRIP-NEXT: local.get $7 + ;; ROUNDTRIP-NEXT: drop + ;; ROUNDTRIP-NEXT: local.set $5 + ;; ROUNDTRIP-NEXT: drop + ;; ROUNDTRIP-NEXT: local.get $5 + ;; ROUNDTRIP-NEXT: local.get $6 + ;; ROUNDTRIP-NEXT: tuple.make 3 + ;; ROUNDTRIP-NEXT: ) + (func $multivalue-return-non-extract (param $other f64) (result i32 f64 anyref) + (local $temp (tuple i32 f64 anyref)) + ;; As the first case, but one extract is replaced with something else, so we + ;; do not optimize. + (tuple.make 3 + (tuple.extract 3 0 + (local.tee $temp + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp) + (nop) ;; this breaks the pattern, appearing where the tuple.extract + ;; should be + ) + (tuple.extract 3 2 + (local.get $temp) + ) + ) + ) + + ;; CHECK: (func $multiple-multivalue-return (type $2) (param $0 i32) (param $1 f64) (param $2 anyref) + ;; CHECK-NEXT: (local $temp3 (tuple i32 f64 anyref)) + ;; CHECK-NEXT: (local $temp2 (tuple i32 f64)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: call $multiple-multivalue-return + ;; CHECK-NEXT: call $multivalue-return-too-short + ;; CHECK-NEXT: ref.null none + ;; CHECK-NEXT: call $multiple-multivalue-return + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: call $multiple-multivalue-return + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multiple-multivalue-return (type $2) (param $0 i32) (param $1 f64) (param $2 anyref) + ;; ROUNDTRIP-NEXT: (local $temp3 i32) + ;; ROUNDTRIP-NEXT: (local $temp2 i32) + ;; ROUNDTRIP-NEXT: (local $5 f64) + ;; ROUNDTRIP-NEXT: (local $6 f64) + ;; ROUNDTRIP-NEXT: (local $7 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_9 (tuple i32 f64)) + ;; ROUNDTRIP-NEXT: (local $scratch_10 (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: call $multiple-multivalue-return + ;; ROUNDTRIP-NEXT: call $multivalue-return-too-short + ;; ROUNDTRIP-NEXT: ref.null none + ;; ROUNDTRIP-NEXT: call $multiple-multivalue-return + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: call $multiple-multivalue-return + ;; ROUNDTRIP-NEXT: ) + (func $multiple-multivalue-return (param i32 f64 anyref) + (local $temp3 (tuple i32 f64 anyref)) + (local $temp2 (tuple i32 f64)) + + ;; Multiple optimizations in one function, including a case where we reuse + ;; the local index. + + (call $multiple-multivalue-return + (tuple.extract 3 0 + (local.tee $temp3 + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp3) + ) + (tuple.extract 3 2 + (local.get $temp3) + ) + ) + + (call $multiple-multivalue-return + (tuple.extract 2 0 + (local.tee $temp2 + (call $multivalue-return-too-short) + ) + ) + (tuple.extract 2 1 + (local.get $temp2) + ) + (ref.null any) + ) + + (call $multiple-multivalue-return + (tuple.extract 3 0 + (local.tee $temp3 + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp3) + ) + (tuple.extract 3 2 + (local.get $temp3) + ) + ) + ) + + ;; CHECK: (func $multiple-multivalue-return-local-reuse (type $2) (param $0 i32) (param $1 f64) (param $2 anyref) + ;; CHECK-NEXT: (local $temp3 (tuple i32 f64 anyref)) + ;; CHECK-NEXT: (local $temp2 (tuple i32 f64)) + ;; CHECK-NEXT: call $multivalue-return + ;; CHECK-NEXT: local.tee $temp3 + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp3 + ;; CHECK-NEXT: tuple.extract 3 1 + ;; CHECK-NEXT: local.get $temp3 + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: call $multiple-multivalue-return + ;; CHECK-NEXT: local.get $temp3 + ;; CHECK-NEXT: tuple.extract 3 0 + ;; CHECK-NEXT: local.get $temp3 + ;; CHECK-NEXT: tuple.extract 3 1 + ;; CHECK-NEXT: local.get $temp3 + ;; CHECK-NEXT: tuple.extract 3 2 + ;; CHECK-NEXT: call $multiple-multivalue-return + ;; CHECK-NEXT: ) + ;; ROUNDTRIP: (func $multiple-multivalue-return-local-reuse (type $2) (param $0 i32) (param $1 f64) (param $2 anyref) + ;; ROUNDTRIP-NEXT: (local $temp3 i32) + ;; ROUNDTRIP-NEXT: (local $temp2 i32) + ;; ROUNDTRIP-NEXT: (local $5 f64) + ;; ROUNDTRIP-NEXT: (local $6 f64) + ;; ROUNDTRIP-NEXT: (local $7 anyref) + ;; ROUNDTRIP-NEXT: (local $scratch (tuple i32 f64 anyref)) + ;; ROUNDTRIP-NEXT: (local $scratch_9 f64) + ;; ROUNDTRIP-NEXT: (local $scratch_10 i32) + ;; ROUNDTRIP-NEXT: call $multivalue-return + ;; ROUNDTRIP-NEXT: local.tee $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 0 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 1 + ;; ROUNDTRIP-NEXT: local.get $scratch + ;; ROUNDTRIP-NEXT: tuple.extract 3 2 + ;; ROUNDTRIP-NEXT: local.set $7 + ;; ROUNDTRIP-NEXT: local.set $5 + ;; ROUNDTRIP-NEXT: local.tee $temp3 + ;; ROUNDTRIP-NEXT: local.get $5 + ;; ROUNDTRIP-NEXT: local.get $7 + ;; ROUNDTRIP-NEXT: call $multiple-multivalue-return + ;; ROUNDTRIP-NEXT: local.get $temp3 + ;; ROUNDTRIP-NEXT: local.get $5 + ;; ROUNDTRIP-NEXT: local.get $7 + ;; ROUNDTRIP-NEXT: call $multiple-multivalue-return + ;; ROUNDTRIP-NEXT: ) + (func $multiple-multivalue-return-local-reuse (param i32 f64 anyref) + (local $temp3 (tuple i32 f64 anyref)) + (local $temp2 (tuple i32 f64)) + + ;; As the last case, we have two things to possibly optimize. Here we reuse + ;; the tee'd value after the first one, which prevents any optimizations of + ;; this pattern. + + (call $multiple-multivalue-return + (tuple.extract 3 0 + (local.tee $temp3 + (call $multivalue-return) + ) + ) + (tuple.extract 3 1 + (local.get $temp3) + ) + (tuple.extract 3 2 + (local.get $temp3) + ) + ) + + (call $multiple-multivalue-return + (tuple.extract 3 0 + (local.get $temp3) ;; this changed + ) + (tuple.extract 3 1 + (local.get $temp3) + ) + (tuple.extract 3 2 + (local.get $temp3) + ) + ) + ) +)