Skip to content
Open
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
102 changes: 102 additions & 0 deletions src/passes/OptimizeInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,108 @@ struct OptimizeInstructions
replaceCurrent(block);
}

void visitArrayNewData(ArrayNewData* curr) {
// Lower array.new_data with constant operands to array.new_default,
// array.new or array.new_fixed. Unlike array.new_data, those are constant
// expressions, and more optimizations understand them.
if (curr->type == Type::unreachable) {
return;
}

auto* offsetConst = curr->offset->dynCast<Const>();
auto* sizeConst = curr->size->dynCast<Const>();
if (!offsetConst || !sizeConst) {
return;
}

auto* seg = getModule()->getDataSegment(curr->segment);
if (!seg->isPassive()) {
return;
}

auto heapType = curr->type.getHeapType();
const auto& element = heapType.getArray().element;
if (!element.type.isNumber()) {
return;
}

uint64_t offset = offsetConst->value.getUnsigned();
uint64_t size = sizeConst->value.getUnsigned();
uint64_t elemBytes = element.getByteSize();
// offset and size are u32 and elemBytes <= 16, so this can't overflow.
uint64_t end = offset + size * elemBytes;
if (end > seg->data.size()) {
// This will trap.
return;
}

// A dropped segment acts as if it had length 0, so unless end == 0 this
// traps if the segment was dropped at runtime, which we can't rule out.
// TODO: Optimize without TNH if the segment is never dropped.
if (end > 0 && !getPassOptions().trapsNeverHappen) {
return;
}

Builder builder(*getModule());

if (size == 0) {
replaceCurrent(builder.makeArrayNewFixed(heapType, {}));
return;
}

auto readElement = [&](Index i) -> Literal {
const auto* src = &seg->data[offset + i * elemBytes];
switch (element.packedType) {
case Field::NotPacked:
return Literal::makeFromMemory(src, element.type);
// Packed values are truncated when stored, so the extension doesn't
// matter; sign-extending gives the shorter encoding (-1 vs 255).
case Field::i8:
return Literal(int32_t(int8_t(*src)));
case Field::i16:
return Literal(int32_t(Bits::readLE<int16_t>(src)));
}
WASM_UNREACHABLE("unexpected packed type");
};

// If all elements are equal, array.new(_default) is compact at any size.
auto first = readElement(0);
bool allEqual = true;
for (Index i = 1; i < size; i++) {
if (readElement(i) != first) {
allEqual = false;
break;
}
}
if (allEqual) {
auto* sizeExpr = builder.makeConst(int32_t(size));
// Literal comparison is bitwise, so e.g. -0.0 is not considered zero.
if (first == Literal::makeZero(element.type)) {
replaceCurrent(builder.makeArrayNew(heapType, sizeExpr));
} else {
// visitArrayNew turns the size == 1 case into array.new_fixed.
replaceCurrent(
builder.makeArrayNew(heapType, sizeExpr, builder.makeConst(first)));
}
return;
}

// array.new_fixed needs a full *.const per element while the segment
// stores raw bytes, so only do this for small arrays. Empirically, 64 sits
// in a natural gap between small/medium array literals (2D array rows,
// enum/switch tables, etc.) and large bulk lookup tables (100+ elements).
constexpr uint64_t MaxArrayNewFixedSize = 64;
if (size > MaxArrayNewFixedSize) {
return;
}
std::vector<Expression*> values;
values.reserve(size);
for (Index i = 0; i < size; i++) {
values.push_back(builder.makeConst(readElement(i)));
}
replaceCurrent(builder.makeArrayNewFixed(heapType, values));
}

void visitArrayGet(ArrayGet* curr) {
skipNonNullCast(curr->ref, curr);
trapOnNull(curr, curr->ref);
Expand Down
10 changes: 3 additions & 7 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4747,11 +4747,9 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
const auto& seg = *wasm.getDataSegment(curr->segment);
auto elemBytes = element.getByteSize();

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"

uint64_t end;
if (std::ckd_add(&end, offset, size * elemBytes) || end > seg.data.size()) {
// offset and size are u32 and elemBytes <= 16, so this can't overflow.
uint64_t end = offset + size * elemBytes;
if (end > seg.data.size()) {
trap("out of bounds segment access in array.new_data");
}
if (droppedDataSegments.contains(curr->segment) && end > 0) {
Expand All @@ -4762,8 +4760,6 @@ class ModuleRunnerBase : public ExpressionRunner<SubType> {
memcpy(contents.data(), &seg.data[offset], size * elemBytes);
}

#pragma GCC diagnostic pop

return self()->makeGCData(std::move(contents), curr->type);
}
Flow visitArrayNewElem(ArrayNewElem* curr) {
Expand Down
Loading
Loading