Skip to content

Commit

Permalink
Do not emit 100k data segments, browsers reject it (#1350)
Browse files Browse the repository at this point in the history
Instead merge constant-offset segments if we must in order to stay under the limit.

If we can't - too many non-constant-offset segments - then issue a warning.
  • Loading branch information
kripken authored Jan 10, 2018
1 parent 2372862 commit 8f90b65
Show file tree
Hide file tree
Showing 6 changed files with 406,947 additions and 5 deletions.
9 changes: 8 additions & 1 deletion src/wasm-binary.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ namespace wasm {

enum {
// the maximum amount of bytes we emit per LEB
MaxLEB32Bytes = 5
MaxLEB32Bytes = 5,
};

// wasm VMs on the web have decided to impose some limits on what they
// accept
enum WebLimitations {
MaxDataSegments = 100 * 1000,
MaxFunctionBodySize = 128 * 1024
};

template<typename T, typename MiniT>
Expand Down
82 changes: 78 additions & 4 deletions src/wasm/wasm-binary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,20 +328,94 @@ void WasmBinaryWriter::writeExports() {
finishSection(start);
}

static bool isEmpty(Memory::Segment& segment) {
return segment.data.size() == 0;
}

static bool isConstantOffset(Memory::Segment& segment) {
return segment.offset->is<Const>();
}

void WasmBinaryWriter::writeDataSegments() {
if (wasm->memory.segments.size() == 0) return;
uint32_t num = 0;
Index numConstant = 0,
numDynamic = 0;
for (auto& segment : wasm->memory.segments) {
if (segment.data.size() > 0) num++;
if (!isEmpty(segment)) {
if (isConstantOffset(segment)) {
numConstant++;
} else {
numDynamic++;
}
}
}
// check if we have too many dynamic data segments, which we can do nothing about
auto num = numConstant + numDynamic;
if (numDynamic + 1 >= WebLimitations::MaxDataSegments) {
std::cerr << "too many non-constant-offset data segments, wasm VMs may not accept this binary" << std::endl;
}
// we'll merge constant segments if we must
if (numConstant + numDynamic >= WebLimitations::MaxDataSegments) {
numConstant = WebLimitations::MaxDataSegments - numDynamic - 1;
num = numConstant + numDynamic;
assert(num == WebLimitations::MaxDataSegments - 1);
}
auto start = startSection(BinaryConsts::Section::Data);
o << U32LEB(num);
for (auto& segment : wasm->memory.segments) {
if (segment.data.size() == 0) continue;
// first, emit all non-constant-offset segments; then emit the constants,
// which we may merge if forced to
Index emitted = 0;
auto emit = [&](Memory::Segment& segment) {
o << U32LEB(0); // Linear memory 0 in the MVP
writeExpression(segment.offset);
o << int8_t(BinaryConsts::End);
writeInlineBuffer(&segment.data[0], segment.data.size());
emitted++;
};
auto& segments = wasm->memory.segments;
for (auto& segment : segments) {
if (isEmpty(segment)) continue;
if (isConstantOffset(segment)) continue;
emit(segment);
}
// from here on, we concern ourselves with non-empty constant-offset
// segments, the ones which we may need to merge
auto isRelevant = [](Memory::Segment& segment) {
return !isEmpty(segment) && isConstantOffset(segment);
};
for (Index i = 0; i < segments.size(); i++) {
auto& segment = segments[i];
if (!isRelevant(segment)) continue;
if (emitted + 2 < WebLimitations::MaxDataSegments) {
emit(segment);
} else {
// we can emit only one more segment! merge everything into one
// start the combined segment at the bottom of them all
auto start = segment.offset->cast<Const>()->value.getInteger();
for (Index j = i + 1; j < segments.size(); j++) {
auto& segment = segments[j];
if (!isRelevant(segment)) continue;
auto offset = segment.offset->cast<Const>()->value.getInteger();
start = std::min(start, offset);
}
// create the segment and add in all the data
Const c;
c.value = Literal(int32_t(start));
c.type = i32;
Memory::Segment combined(&c);
for (Index j = i; j < segments.size(); j++) {
auto& segment = segments[j];
if (!isRelevant(segment)) continue;
auto offset = segment.offset->cast<Const>()->value.getInteger();
auto needed = offset + segment.data.size() - start;
if (combined.data.size() < needed) {
combined.data.resize(needed);
}
std::copy(segment.data.begin(), segment.data.end(), combined.data.begin() + offset - start);
}
emit(combined);
break;
}
}
finishSection(start);
}
Expand Down
Loading

0 comments on commit 8f90b65

Please sign in to comment.