summaryrefslogtreecommitdiff
path: root/src/passes/OptimizeInstructions.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/passes/OptimizeInstructions.cpp')
-rw-r--r--src/passes/OptimizeInstructions.cpp141
1 files changed, 141 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); }