diff options
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 141 | ||||
-rw-r--r-- | test/lit/passes/optimize-instructions-gc-heap.wast | 719 |
2 files changed, 860 insertions, 0 deletions
diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index ed3d474d9..ba18bfd74 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -1006,6 +1006,12 @@ struct OptimizeInstructions } } + void visitBlock(Block* curr) { + if (getModule()->features.hasGC()) { + optimizeHeapStores(curr->list); + } + } + void visitIf(If* curr) { curr->condition = optimizeBoolean(curr->condition); if (curr->ifFalse) { @@ -1319,6 +1325,141 @@ struct OptimizeInstructions const auto& fields = curr->ref->type.getHeapType().getStruct().fields; optimizeStoredValue(curr->value, fields[curr->index].getByteSize()); } + + // If our reference is a tee of a struct.new, we may be able to fold the + // stored value into the new itself: + // + // (struct.set (local.tee $x (struct.new X Y Z)) X') + // => + // (local.set $x (struct.new X' Y Z)) + // + if (auto* tee = curr->ref->dynCast<LocalSet>()) { + if (auto* new_ = tee->value->dynCast<StructNew>()) { + if (optimizeSubsequentStructSet(new_, curr, tee->index)) { + // Success, so we do not need the struct.set any more, and the tee + // can just be a set instead of us. + tee->makeSet(); + replaceCurrent(tee); + } + } + } + } + + // Similar to the above with struct.set whose reference is a tee of a new, we + // can do the same for subsequent sets in a list: + // + // (local.set $x (struct.new X Y Z)) + // (struct.set (local.get $x) X') + // => + // (local.set $x (struct.new X' Y Z)) + // + // We also handle other struct.sets immediately after this one, but we only + // handle the case where they are all in sequence and right after the + // local.set (anything in the middle of this pattern will stop us from + // optimizing later struct.sets, which might be improved later but would + // require an analysis of effects TODO). + void optimizeHeapStores(ExpressionList& list) { + for (Index i = 0; i < list.size(); i++) { + auto* localSet = list[i]->dynCast<LocalSet>(); + if (!localSet) { + continue; + } + auto* new_ = localSet->value->dynCast<StructNew>(); + if (!new_) { + continue; + } + + // This local.set of a struct.new looks good. Find struct.sets after it + // to optimize. + for (Index j = i + 1; j < list.size(); j++) { + auto* structSet = list[j]->dynCast<StructSet>(); + if (!structSet) { + // Any time the pattern no longer matches, stop optimizing possible + // struct.sets for this struct.new. + break; + } + auto* localGet = structSet->ref->dynCast<LocalGet>(); + if (!localGet || localGet->index != localSet->index) { + break; + } + if (!optimizeSubsequentStructSet(new_, structSet, localGet->index)) { + break; + } else { + // Success. Replace the set with a nop, and continue to + // perhaps optimize more. + ExpressionManipulator::nop(structSet); + } + } + } + } + + // Given a struct.new and a struct.set that occurs right after it, and that + // applies to the same data, try to apply the set during the new. This can be + // either with a nested tee: + // + // (struct.set + // (local.tee $x (struct.new X Y Z)) + // X' + // ) + // => + // (local.set $x (struct.new X' Y Z)) + // + // or without: + // + // (local.set $x (struct.new X Y Z)) + // (struct.set (local.get $x) X') + // => + // (local.set $x (struct.new X' Y Z)) + // + // Returns true if we succeeded. + bool optimizeSubsequentStructSet(StructNew* new_, + StructSet* set, + Index refLocalIndex) { + if (new_->isWithDefault()) { + // Ignore a new_default for now. If the fields are defaultable then we + // could add them, in principle, but that might increase code size. + return false; + } + + auto index = set->index; + auto& operands = new_->operands; + + // Check for effects that prevent us moving the struct.set's value (X' in + // the function comment) into its new position in the struct.new. First, it + // must be ok to move it past the local.set (otherwise, it might read from + // memory using that local, and depend on the struct.new having already + // occurred; or, if it writes to that local, then it would cross another + // write). + auto setValueEffects = effects(set->value); + if (setValueEffects.localsRead.count(refLocalIndex) || + setValueEffects.localsWritten.count(refLocalIndex)) { + return false; + } + + // We must move the set's value past indexes greater than it (Y and Z in + // the example in the comment on this function). + // TODO When this function is called repeatedly in a sequence this can + // become quadratic - perhaps we should memoize (though, struct sizes + // tend to not be ridiculously large). + for (Index i = index + 1; i < operands.size(); i++) { + auto operandEffects = effects(operands[i]); + if (operandEffects.invalidates(setValueEffects)) { + // TODO: we could use locals to reorder everything + return false; + } + } + + Builder builder(*getModule()); + + // See if we need to keep the old value. + if (effects(operands[index]).hasUnremovableSideEffects()) { + operands[index] = + builder.makeSequence(builder.makeDrop(operands[index]), set->value); + } else { + operands[index] = set->value; + } + + return true; } void visitArrayGet(ArrayGet* curr) { skipNonNullCast(curr->ref); } diff --git a/test/lit/passes/optimize-instructions-gc-heap.wast b/test/lit/passes/optimize-instructions-gc-heap.wast new file mode 100644 index 000000000..4554e651d --- /dev/null +++ b/test/lit/passes/optimize-instructions-gc-heap.wast @@ -0,0 +1,719 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions -all -S -o - \ +;; RUN: | filecheck %s +;; +;; --remove-unused-names allows the optimizer to see that the blocks have no +;; breaks to them, and so they have no nonlinear control flow. + +(module + ;; CHECK: (type $struct (struct (field (mut i32)))) + (type $struct (struct (field (mut i32)))) + + ;; CHECK: (type $struct2 (struct (field (mut i32)) (field (mut i32)))) + (type $struct2 (struct (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) + (type $struct3 (struct (field (mut i32)) (field (mut i32)) (field (mut i32)))) + + ;; CHECK: (func $tee + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $tee + (local $ref (ref null $struct)) + ;; The set is not needed as we can apply the 20 in the new. + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + (i32.const 20) + ) + ) + + ;; CHECK: (func $side-effects-in-old-value + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effects-in-old-value + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + ;; Side effects here force us to keep the old value around. + (call $helper-i32 (i32.const 0)) + ) + ) + (i32.const 20) + ) + ) + + ;; CHECK: (func $side-effects-in-new-value + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effects-in-new-value + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; Side effects here are not a problem. + (call $helper-i32 (i32.const 0)) + ) + ) + + ;; CHECK: (func $many-fields + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $many-fields + (local $ref (ref null $struct2)) + ;; Set to the first field. + (struct.set $struct2 0 + (local.tee $ref + (struct.new $struct2 + (i32.const 10) + (i32.const 20) + ) + ) + (i32.const 30) + ) + ;; Set to the second. + (struct.set $struct2 1 + (local.tee $ref + (struct.new $struct2 + (i32.const 40) + (i32.const 50) + ) + ) + (i32.const 60) + ) + ) + + ;; CHECK: (func $side-effect-conflict + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (struct.set $struct2 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effect-conflict + (local $ref (ref null $struct2)) + (struct.set $struct2 0 + (local.tee $ref + (struct.new $struct2 + (i32.const 10) + ;; Side effects on the second field prevent us from moving the set's + ;; value past it to replace the first field above it. + (call $helper-i32 (i32.const 0)) + ) + ) + (call $helper-i32 (i32.const 1)) + ) + ) + + ;; CHECK: (func $side-effect-ok + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $side-effect-ok + (local $ref (ref null $struct2)) + (struct.set $struct2 0 + (local.tee $ref + (struct.new $struct2 + ;; Side effects on the first field do not interfere. + (call $helper-i32 (i32.const 0)) + (i32.const 10) + ) + ) + (call $helper-i32 (i32.const 1)) + ) + ) + + ;; CHECK: (func $optimize-subsequent + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $optimize-subsequent + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; A set that comes right after can be optimized too. + (struct.set $struct 0 + (local.get $ref) + (i32.const 20) + ) + ) + + ;; CHECK: (func $optimize-subsequent-bad-local + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $other (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $optimize-subsequent-bad-local + (local $ref (ref null $struct)) + (local $other (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; As above, but the local.get uses a different local, so we have nothing + ;; to optimize. + (struct.set $struct 0 + (local.get $other) + (i32.const 20) + ) + ) + + ;; CHECK: (func $optimize-chain + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $optimize-chain + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (i32.const 20) + ) + ;; The value in the last item in the chain should apply. + (struct.set $struct 0 + (local.get $ref) + (i32.const 30) + ) + ) + + ;; CHECK: (func $pattern-breaker + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pattern-breaker + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + ;; Anything that we don't recognize breaks the pattern. + (nop) + (struct.set $struct 0 + (local.get $ref) + (i32.const 20) + ) + ) + + ;; CHECK: (func $ref-local-write + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-write + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A write to the ref local prevents us from optimizing. + (local.set $ref + (ref.null $struct) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-local-write-tee + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-write-tee + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + (block (result i32) + ;; As above, but now in a tee. + (local.set $ref + (ref.null $struct) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $other-local-write + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $other (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (local.set $other + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $other-local-write + (local $ref (ref null $struct)) + (local $other (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A write to another local is fine. + (local.set $other + (ref.null $struct) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-local-read + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-read + (local $ref (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A read of the ref local prevents us from optimizing. + (drop + (local.get $ref) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-local-read-tee + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ref-local-read-tee + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + (struct.new $struct + (i32.const 10) + ) + ) + (block (result i32) + ;; As above, but now in a tee. + (drop + (local.get $ref) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $ref-other-read + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (local $other (ref null $struct)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $other) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $ref-other-read + (local $ref (ref null $struct)) + (local $other (ref null $struct)) + (local.set $ref + (struct.new $struct + (i32.const 10) + ) + ) + (struct.set $struct 0 + (local.get $ref) + (block (result i32) + ;; A read of another local is fine. + (drop + (local.get $other) + ) + (i32.const 20) + ) + ) + ) + + ;; CHECK: (func $tee-and-subsequent + ;; CHECK-NEXT: (local $ref (ref null $struct3)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $tee-and-subsequent + (local $ref (ref null $struct3)) + ;; Test the common pattern of several subsequent sets, one of which is + ;; using a tee. + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (i32.const 40) + ) + (struct.set $struct3 1 + (local.get $ref) + (i32.const 50) + ) + (struct.set $struct3 2 + (local.get $ref) + (i32.const 60) + ) + ) + + ;; CHECK: (func $side-effect-subsequent-ok + ;; CHECK-NEXT: (local $ref (ref null $struct2)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct2 + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $helper-i32 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $side-effect-subsequent-ok + (local $ref (ref null $struct2)) + (local.set $ref + (struct.new $struct2 + ;; The first field has side effects, but the second does not. + (call $helper-i32 (i32.const 0)) + (i32.const 10) + ) + ) + ;; Replace the second field with something with side effects. + (struct.set $struct2 1 + (local.get $ref) + (call $helper-i32 (i32.const 1)) + ) + ) + + ;; CHECK: (func $default + ;; CHECK-NEXT: (local $ref (ref null $struct)) + ;; CHECK-NEXT: (struct.set $struct 0 + ;; CHECK-NEXT: (local.tee $ref + ;; CHECK-NEXT: (struct.new_default $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $default + (local $ref (ref null $struct)) + (struct.set $struct 0 + (local.tee $ref + ;; Ignore a new_default for now. If the fields are defaultable then we + ;; could add them, in principle, but that might increase code size. + (struct.new_default $struct) + ) + (i32.const 20) + ) + ) + + ;; CHECK: (func $many-news + ;; CHECK-NEXT: (local $ref (ref null $struct3)) + ;; CHECK-NEXT: (local $ref2 (ref null $struct3)) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 50) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (struct.set $struct3 2 + ;; CHECK-NEXT: (local.get $ref) + ;; CHECK-NEXT: (i32.const 60) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 400) + ;; CHECK-NEXT: (i32.const 200) + ;; CHECK-NEXT: (i32.const 500) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $ref + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 40) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: (i32.const 30) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $ref2 + ;; CHECK-NEXT: (struct.new $struct3 + ;; CHECK-NEXT: (i32.const 400) + ;; CHECK-NEXT: (i32.const 600) + ;; CHECK-NEXT: (i32.const 500) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $many-news + (local $ref (ref null $struct3)) + (local $ref2 (ref null $struct3)) + ;; Test that we optimize for multiple struct.news in the same function. + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (i32.const 40) + ) + (struct.set $struct3 1 + (local.get $ref) + (i32.const 50) + ) + (nop) + (struct.set $struct3 2 + (local.get $ref) + (i32.const 60) + ) + (nop) + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 100) + (i32.const 200) + (i32.const 300) + ) + ) + (i32.const 400) + ) + (struct.set $struct3 2 + (local.get $ref) + (i32.const 500) + ) + ;; Test inside an inner block. + (block $inner + (struct.set $struct3 0 + (local.tee $ref + (struct.new $struct3 + (i32.const 10) + (i32.const 20) + (i32.const 30) + ) + ) + (i32.const 40) + ) + ;; Use a different ref local here. + (struct.set $struct3 0 + (local.tee $ref2 + (struct.new $struct3 + (i32.const 100) + (i32.const 200) + (i32.const 300) + ) + ) + (i32.const 400) + ) + (struct.set $struct3 2 + (local.get $ref2) + (i32.const 500) + ) + (struct.set $struct3 1 + (local.get $ref2) + (i32.const 600) + ) + ) + ) + + ;; CHECK: (func $helper-i32 (param $x i32) (result i32) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + (func $helper-i32 (param $x i32) (result i32) + (i32.const 42) + ) +) |