Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions src/passes/OptimizeInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include <ir/iteration.h>
#include <ir/literal-utils.h>
#include <ir/load-utils.h>
#include <ir/localize.h>
#include <ir/manipulation.h>
#include <ir/match.h>
#include <ir/ordering.h>
Expand Down Expand Up @@ -1782,6 +1783,40 @@ struct OptimizeInstructions
}
}

void visitStructNew(StructNew* curr) {
// If values are provided, but they are all the default, then we can remove
// them (in reachable code).
if (curr->type == Type::unreachable || curr->isWithDefault()) {
return;
}

auto& passOptions = getPassOptions();
const auto& fields = curr->type.getHeapType().getStruct().fields;
assert(fields.size() == curr->operands.size());

for (Index i = 0; i < fields.size(); i++) {
// The field must be defaultable.
auto type = fields[i].type;
if (!type.isDefaultable()) {
return;
}

// The field must be written the default value.
auto* value = Properties::getFallthrough(
curr->operands[i], passOptions, *getModule());
if (!Properties::isSingleConstantExpression(value) ||
Properties::getLiteral(value) != Literal::makeZero(type)) {
return;
}
}

// Success! Drop the children and return a struct.new_with_default.
auto* rep = getDroppedChildrenAndAppend(curr, curr);
curr->operands.clear();
assert(curr->isWithDefault());
replaceCurrent(rep);
}

void visitStructGet(StructGet* curr) {
skipNonNullCast(curr->ref, curr);
trapOnNull(curr, curr->ref);
Expand Down Expand Up @@ -1943,6 +1978,130 @@ struct OptimizeInstructions
return true;
}

void visitArrayNew(ArrayNew* curr) {
// If a value is provided, we can optimize in some cases.
if (curr->type == Type::unreachable || curr->isWithDefault()) {
return;
}

Builder builder(*getModule());

// ArrayNew of size 1 is less efficient than ArrayNewFixed with one value
// (the latter avoids a Const, which ends up saving one byte).
// TODO: also look at the case with a fallthrough or effects on the size
if (auto* c = curr->size->dynCast<Const>()) {
if (c->value.geti32() == 1) {
// Optimize to ArrayNewFixed. Note that if the value is the default
// then we may end up optimizing further in visitArrayNewFixed.
replaceCurrent(
builder.makeArrayNewFixed(curr->type.getHeapType(), {curr->init}));
return;
}
}

// If the type is defaultable then perhaps the value here is the default.
auto type = curr->type.getHeapType().getArray().element.type;
if (!type.isDefaultable()) {
return;
}

// The value must be the default/zero.
auto& passOptions = getPassOptions();
auto zero = Literal::makeZero(type);
auto* value =
Properties::getFallthrough(curr->init, passOptions, *getModule());
if (!Properties::isSingleConstantExpression(value) ||
Properties::getLiteral(value) != zero) {
return;
}

// Success! Drop the init and return an array.new_with_default.
auto* init = curr->init;
curr->init = nullptr;
assert(curr->isWithDefault());
replaceCurrent(builder.makeSequence(builder.makeDrop(init), curr));
}

void visitArrayNewFixed(ArrayNewFixed* curr) {
if (curr->type == Type::unreachable) {
return;
}

auto size = curr->values.size();
if (size == 0) {
// TODO: Consider what to do in the trivial case of an empty array: we can
// can use ArrayNew or ArrayNewFixed there. Measure which is best.
return;
}

auto& passOptions = getPassOptions();

// If all the values are equal then we can optimize, either to
// array.new_default (if they are all equal to the default) or array.new (if
// they are all equal to some other value). First, see if they are all
// equal, which we do by comparing in pairs: [0,1], then [1,2], etc.
for (Index i = 0; i < size - 1; i++) {
if (!areConsecutiveInputsEqual(curr->values[i], curr->values[i + 1])) {
return;
}
}

// Great, they are all equal!

Builder builder(*getModule());

// See if they are equal to a constant, and if that constant is the default.
auto type = curr->type.getHeapType().getArray().element.type;
if (type.isDefaultable()) {
auto* value =
Properties::getFallthrough(curr->values[0], passOptions, *getModule());

if (Properties::isSingleConstantExpression(value) &&
Properties::getLiteral(value) == Literal::makeZero(type)) {
// They are all equal to the default. Drop the children and return an
// array.new_with_default.
auto* withDefault = builder.makeArrayNew(
curr->type.getHeapType(), builder.makeConst(int32_t(size)));
replaceCurrent(getDroppedChildrenAndAppend(curr, withDefault));
return;
}
}

// They are all equal to each other, but not to the default value. If there
// are 2 or more elements here then we can save by using array.new. For
// example, with 2 elements we are doing this:
//
// (array.new_fixed
// (A)
// (A)
// )
// =>
// (array.new
// (A)
// (i32.const 2) ;; get two copies of (A)
// )
//
// However, with 1, ArrayNewFixed is actually more compact, and we optimize
// ArrayNew to it, above.
if (size == 1) {
return;
}

// Move children to locals, if we need to keep them around. We are removing
// them all, except from the first, when we remove the array.new_fixed's
// list of children and replace it with a single child + a constant for the
// number of children.
ChildLocalizer localizer(
curr, getFunction(), *getModule(), getPassOptions());
auto* block = localizer.getChildrenReplacement();
auto* arrayNew = builder.makeArrayNew(curr->type.getHeapType(),
builder.makeConst(int32_t(size)),
curr->values[0]);
block->list.push_back(arrayNew);
block->finalize();
replaceCurrent(block);
}

void visitArrayGet(ArrayGet* curr) {
skipNonNullCast(curr->ref, curr);
trapOnNull(curr, curr->ref);
Expand Down
Loading