Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not emit 100k data segments, browsers reject it #1350

Merged
merged 10 commits into from
Jan 10, 2018
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
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