summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/OptimizeInstructions.cpp141
-rw-r--r--test/lit/passes/optimize-instructions-gc-heap.wast719
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)
+ )
+)