зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1292724 - Baldr: fix segment offsets (r=bbouvier)
MozReview-Commit-ID: L4sWbIYY0g9 --HG-- extra : rebase_source : fc31c5b1e139bb538ba7731fa56b04ed6d913f4a
This commit is contained in:
Родитель
e12fcacd53
Коммит
c9dc4a5e06
|
@ -646,14 +646,15 @@ class AstExport : public AstNode
|
|||
|
||||
class AstDataSegment : public AstNode
|
||||
{
|
||||
uint32_t offset_;
|
||||
AstExpr* offset_;
|
||||
AstName text_;
|
||||
|
||||
public:
|
||||
AstDataSegment(uint32_t offset, AstName text)
|
||||
AstDataSegment(AstExpr* offset, AstName text)
|
||||
: offset_(offset), text_(text)
|
||||
{}
|
||||
uint32_t offset() const { return offset_; }
|
||||
|
||||
AstExpr* offset() const { return offset_; }
|
||||
AstName text() const { return text_; }
|
||||
};
|
||||
|
||||
|
|
|
@ -1655,16 +1655,12 @@ AstDecodeDataSection(AstDecodeContext &c)
|
|||
return AstDecodeFail(c, "failed to read number of data segments");
|
||||
|
||||
const uint32_t heapLength = c.module().hasMemory() ? c.module().memory().initial() : 0;
|
||||
uint32_t prevEnd = 0;
|
||||
|
||||
for (uint32_t i = 0; i < numSegments; i++) {
|
||||
uint32_t dstOffset;
|
||||
if (!c.d.readVarU32(&dstOffset))
|
||||
return AstDecodeFail(c, "expected segment destination offset");
|
||||
|
||||
if (dstOffset < prevEnd)
|
||||
return AstDecodeFail(c, "data segments must be disjoint and ordered");
|
||||
|
||||
uint32_t numBytes;
|
||||
if (!c.d.readVarU32(&numBytes))
|
||||
return AstDecodeFail(c, "expected segment size");
|
||||
|
@ -1680,12 +1676,14 @@ AstDecodeDataSection(AstDecodeContext &c)
|
|||
for (size_t i = 0; i < numBytes; i++)
|
||||
buffer[i] = src[i];
|
||||
|
||||
AstName name(buffer, numBytes);
|
||||
AstDataSegment* segment = new(c.lifo) AstDataSegment(dstOffset, name);
|
||||
if (!segment || !c.module().append(segment))
|
||||
AstExpr* offset = new(c.lifo) AstConst(Val(dstOffset));
|
||||
if (!offset)
|
||||
return false;
|
||||
|
||||
prevEnd = dstOffset + numBytes;
|
||||
AstName name(buffer, numBytes);
|
||||
AstDataSegment* segment = new(c.lifo) AstDataSegment(offset, name);
|
||||
if (!segment || !c.module().append(segment))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!c.d.finishSection(sectionStart, sectionSize))
|
||||
|
|
|
@ -1739,7 +1739,7 @@ PrintDataSection(WasmPrintContext& c, const AstModule& module)
|
|||
return false;
|
||||
if (!c.buffer.append("segment "))
|
||||
return false;
|
||||
if (!PrintInt32(c, segment->offset()))
|
||||
if (!PrintInt32(c, segment->offset()->as<AstConst>().val().i32()))
|
||||
return false;
|
||||
if (!c.buffer.append(" \""))
|
||||
return false;
|
||||
|
|
|
@ -1334,7 +1334,7 @@ RenderDataSection(WasmRenderContext& c, const AstModule& module)
|
|||
return false;
|
||||
if (!c.buffer.append("(segment "))
|
||||
return false;
|
||||
if (!RenderInt32(c, segment->offset()))
|
||||
if (!RenderInt32(c, segment->offset()->as<AstConst>().val().i32()))
|
||||
return false;
|
||||
if (!c.buffer.append(" \""))
|
||||
return false;
|
||||
|
|
|
@ -112,6 +112,7 @@ struct ShareableBytes : ShareableBase<ShareableBytes>
|
|||
size_t sizeOfExcludingThis(MallocSizeOf m) const { return bytes.sizeOfExcludingThis(m); }
|
||||
const uint8_t* begin() const { return bytes.begin(); }
|
||||
const uint8_t* end() const { return bytes.end(); }
|
||||
size_t length() const { return bytes.length(); }
|
||||
bool append(const uint8_t *p, uint32_t ct) { return bytes.append(p, ct); }
|
||||
};
|
||||
|
||||
|
|
|
@ -1335,7 +1335,7 @@ DecodeElemSection(Decoder& d, bool newFormat, Uint32Vector&& oldElems, ModuleGen
|
|||
if (numSegments > MaxElemSegments)
|
||||
return Fail(d, "too many elem segments");
|
||||
|
||||
for (uint32_t i = 0, prevEnd = 0; i < numSegments; i++) {
|
||||
for (uint32_t i = 0; i < numSegments; i++) {
|
||||
uint32_t tableIndex;
|
||||
if (!d.readVarU32(&tableIndex))
|
||||
return Fail(d, "expected table index");
|
||||
|
@ -1348,9 +1348,6 @@ DecodeElemSection(Decoder& d, bool newFormat, Uint32Vector&& oldElems, ModuleGen
|
|||
if (!DecodeInitializerExpression(d, mg.globals(), ValType::I32, &offset))
|
||||
return false;
|
||||
|
||||
if (offset.isVal() && offset.val().i32() < prevEnd)
|
||||
return Fail(d, "elem segments must be disjoint and ordered");
|
||||
|
||||
uint32_t numElems;
|
||||
if (!d.readVarU32(&numElems))
|
||||
return Fail(d, "expected segment size");
|
||||
|
@ -1373,9 +1370,6 @@ DecodeElemSection(Decoder& d, bool newFormat, Uint32Vector&& oldElems, ModuleGen
|
|||
return Fail(d, "table element out of range");
|
||||
}
|
||||
|
||||
if (offset.isVal())
|
||||
prevEnd = offset.val().i32() + elemFuncIndices.length();
|
||||
|
||||
if (!mg.addElemSegment(offset, Move(elemFuncIndices)))
|
||||
return false;
|
||||
}
|
||||
|
@ -1406,7 +1400,8 @@ DecodeDataSection(Decoder& d, bool newFormat, ModuleGenerator& mg)
|
|||
return Fail(d, "too many data segments");
|
||||
|
||||
uint32_t max = mg.minMemoryLength();
|
||||
for (uint32_t i = 0, prevEnd = 0; i < numSegments; i++) {
|
||||
for (uint32_t i = 0; i < numSegments; i++) {
|
||||
DataSegment seg;
|
||||
if (newFormat) {
|
||||
uint32_t linearMemoryIndex;
|
||||
if (!d.readVarU32(&linearMemoryIndex))
|
||||
|
@ -1415,26 +1410,24 @@ DecodeDataSection(Decoder& d, bool newFormat, ModuleGenerator& mg)
|
|||
if (linearMemoryIndex != 0)
|
||||
return Fail(d, "linear memory index must currently be 0");
|
||||
|
||||
Expr expr;
|
||||
if (!d.readExpr(&expr))
|
||||
return Fail(d, "failed to read initializer expression");
|
||||
if (!DecodeInitializerExpression(d, mg.globals(), ValType::I32, &seg.offset))
|
||||
return false;
|
||||
} else {
|
||||
uint32_t offset;
|
||||
if (!d.readVarU32(&offset))
|
||||
return Fail(d, "expected segment destination offset");
|
||||
|
||||
if (expr != Expr::I32Const)
|
||||
return Fail(d, "expected i32.const initializer expression");
|
||||
seg.offset = InitExpr(Val(offset));
|
||||
}
|
||||
|
||||
DataSegment seg;
|
||||
if (!d.readVarU32(&seg.memoryOffset))
|
||||
return Fail(d, "expected segment destination offset");
|
||||
|
||||
if (seg.memoryOffset < prevEnd)
|
||||
return Fail(d, "data segments must be disjoint and ordered");
|
||||
|
||||
if (!d.readVarU32(&seg.length))
|
||||
return Fail(d, "expected segment size");
|
||||
|
||||
if (seg.memoryOffset > max || max - seg.memoryOffset < seg.length)
|
||||
return Fail(d, "data segment data segment does not fit");
|
||||
if (seg.offset.isVal()) {
|
||||
uint32_t off = seg.offset.val().i32();
|
||||
if (off > max || max - off < seg.length)
|
||||
return Fail(d, "data segment does not fit");
|
||||
}
|
||||
|
||||
seg.bytecodeOffset = d.currentOffset();
|
||||
|
||||
|
@ -1443,8 +1436,6 @@ DecodeDataSection(Decoder& d, bool newFormat, ModuleGenerator& mg)
|
|||
|
||||
if (!mg.addDataSegment(seg))
|
||||
return false;
|
||||
|
||||
prevEnd = seg.memoryOffset + seg.length;
|
||||
}
|
||||
|
||||
if (!d.finishSection(sectionStart, sectionSize))
|
||||
|
|
|
@ -412,69 +412,92 @@ Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
|
|||
bytecode_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenBytes);
|
||||
}
|
||||
|
||||
static uint32_t
|
||||
EvaluateInitExpr(const ValVector& globalImports, InitExpr initExpr)
|
||||
{
|
||||
switch (initExpr.kind()) {
|
||||
case InitExpr::Kind::Constant:
|
||||
return initExpr.val().i32();
|
||||
case InitExpr::Kind::GetGlobal:
|
||||
return globalImports[initExpr.globalIndex()].i32();
|
||||
}
|
||||
|
||||
MOZ_CRASH("bad initializer expression");
|
||||
}
|
||||
|
||||
bool
|
||||
Module::initElems(JSContext* cx, HandleWasmInstanceObject instanceObj,
|
||||
const ValVector& globalImports, HandleWasmTableObject tableObj) const
|
||||
Module::initSegments(JSContext* cx,
|
||||
HandleWasmInstanceObject instanceObj,
|
||||
HandleWasmMemoryObject memoryObj,
|
||||
const ValVector& globalImports) const
|
||||
{
|
||||
Instance& instance = instanceObj->instance();
|
||||
const SharedTableVector& tables = instance.tables();
|
||||
|
||||
// Perform all error checks up front so that this function does not perform
|
||||
// partial initialization if an error is reported.
|
||||
|
||||
for (const ElemSegment& seg : elemSegments_) {
|
||||
uint32_t tableLength = tables[seg.tableIndex]->length();
|
||||
uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
|
||||
|
||||
if (offset > tableLength || tableLength - offset < seg.elemCodeRangeIndices.length()) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT, "elem", "table");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (memoryObj) {
|
||||
for (const DataSegment& seg : dataSegments_) {
|
||||
uint32_t memoryLength = memoryObj->buffer().byteLength();
|
||||
uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
|
||||
|
||||
if (offset > memoryLength || memoryLength - offset < seg.length) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_FIT, "data", "memory");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(dataSegments_.empty());
|
||||
}
|
||||
|
||||
// Ensure all tables are initialized before storing into them.
|
||||
|
||||
for (const SharedTable& table : tables) {
|
||||
if (!table->initialized())
|
||||
table->init(instance);
|
||||
}
|
||||
|
||||
// Now that all tables have been initialized, write elements.
|
||||
Vector<uint32_t> prevEnds(cx);
|
||||
if (!prevEnds.appendN(0, tables.length()))
|
||||
return false;
|
||||
// Now that initialization can't fail partway through, write data/elem
|
||||
// segments into memories/tables.
|
||||
|
||||
for (const ElemSegment& seg : elemSegments_) {
|
||||
Table& table = *tables[seg.tableIndex];
|
||||
|
||||
uint32_t offset;
|
||||
switch (seg.offset.kind()) {
|
||||
case InitExpr::Kind::Constant: {
|
||||
offset = seg.offset.val().i32();
|
||||
break;
|
||||
}
|
||||
case InitExpr::Kind::GetGlobal: {
|
||||
const GlobalDesc& global = metadata_->globals[seg.offset.globalIndex()];
|
||||
offset = globalImports[global.importIndex()].i32();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t& prevEnd = prevEnds[seg.tableIndex];
|
||||
|
||||
if (offset < prevEnd) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL,
|
||||
"elem segments must be disjoint and ordered");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t tableLength = instance.metadata().tables[seg.tableIndex].initial;
|
||||
if (offset > tableLength || tableLength - offset < seg.elemCodeRangeIndices.length()) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_FAIL,
|
||||
"element segment does not fit");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
|
||||
bool profilingEnabled = instance.code().profilingEnabled();
|
||||
const CodeRangeVector& codeRanges = instance.code().metadata().codeRanges;
|
||||
const CodeRangeVector& codeRanges = metadata().codeRanges;
|
||||
uint8_t* codeBase = instance.codeBase();
|
||||
|
||||
for (uint32_t i = 0; i < seg.elemCodeRangeIndices.length(); i++) {
|
||||
const CodeRange& cr = codeRanges[seg.elemCodeRangeIndices[i]];
|
||||
uint32_t codeOffset = table.isTypedFunction()
|
||||
? profilingEnabled
|
||||
? cr.funcProfilingEntry()
|
||||
: cr.funcNonProfilingEntry()
|
||||
: cr.funcTableEntry();
|
||||
table.set(offset + i, codeBase + codeOffset, instance);
|
||||
uint32_t entryOffset = table.isTypedFunction()
|
||||
? profilingEnabled
|
||||
? cr.funcProfilingEntry()
|
||||
: cr.funcNonProfilingEntry()
|
||||
: cr.funcTableEntry();
|
||||
table.set(offset + i, codeBase + entryOffset, instance);
|
||||
}
|
||||
}
|
||||
|
||||
prevEnd = offset + seg.elemFuncIndices.length();
|
||||
if (memoryObj) {
|
||||
uint8_t* memoryBase = memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
|
||||
|
||||
for (const DataSegment& seg : dataSegments_) {
|
||||
MOZ_ASSERT(seg.bytecodeOffset <= bytecode_->length());
|
||||
MOZ_ASSERT(seg.length <= bytecode_->length() - seg.bytecodeOffset);
|
||||
uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
|
||||
memcpy(memoryBase + offset, bytecode_->begin() + seg.bytecodeOffset, seg.length);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -518,16 +541,16 @@ Module::instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) c
|
|||
return true;
|
||||
}
|
||||
|
||||
RootedArrayBufferObjectMaybeShared buffer(cx);
|
||||
if (memory) {
|
||||
buffer = &memory->buffer();
|
||||
uint32_t length = buffer->wasmActualByteLength();
|
||||
uint32_t declaredMaxLength = metadata_->maxMemoryLength.valueOr(UINT32_MAX);
|
||||
uint32_t declaredMin = metadata_->minMemoryLength;
|
||||
Maybe<uint32_t> declaredMax = metadata_->maxMemoryLength;
|
||||
|
||||
// It's not an error to import a memory whose mapped size is less than
|
||||
// the maxMemoryLength required for the module. This is the same as trying to
|
||||
// map up to maxMemoryLength but actually getting less.
|
||||
if (length < metadata_->minMemoryLength || length > declaredMaxLength) {
|
||||
if (memory) {
|
||||
RootedArrayBufferObjectMaybeShared buffer(cx, &memory->buffer());
|
||||
MOZ_RELEASE_ASSERT(buffer->is<SharedArrayBufferObject>() ||
|
||||
buffer->as<ArrayBufferObject>().isWasm());
|
||||
|
||||
uint32_t actualLength = buffer->wasmActualByteLength();
|
||||
if (actualLength < declaredMin || actualLength > declaredMax.valueOr(UINT32_MAX)) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
|
||||
return false;
|
||||
}
|
||||
|
@ -536,27 +559,18 @@ Module::instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) c
|
|||
// For wasm we require that either both memory and module don't specify a max size
|
||||
// OR that the memory's max size is less than the modules.
|
||||
if (!metadata_->isAsmJS()) {
|
||||
Maybe<uint32_t> memMaxSize =
|
||||
buffer->as<ArrayBufferObject>().wasmMaxSize();
|
||||
|
||||
if (metadata_->maxMemoryLength.isSome() != memMaxSize.isSome() ||
|
||||
metadata_->maxMemoryLength < memMaxSize) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE,
|
||||
"Memory");
|
||||
Maybe<uint32_t> actualMax = buffer->as<ArrayBufferObject>().wasmMaxSize();
|
||||
if (declaredMax.isSome() != actualMax.isSome() || declaredMax < actualMax) {
|
||||
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMP_SIZE, "Memory");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_RELEASE_ASSERT(buffer->is<SharedArrayBufferObject>() ||
|
||||
buffer->as<ArrayBufferObject>().isWasm());
|
||||
|
||||
// We currently assume SharedArrayBuffer => asm.js. Can remove this
|
||||
// once wasmMaxSize/mappedSize/growForWasm have been implemented in SAB
|
||||
MOZ_ASSERT_IF(buffer->is<SharedArrayBufferObject>(), metadata_->isAsmJS());
|
||||
} else {
|
||||
buffer = ArrayBufferObject::createForWasm(cx, metadata_->minMemoryLength,
|
||||
metadata_->maxMemoryLength);
|
||||
MOZ_ASSERT(!metadata_->isAsmJS());
|
||||
MOZ_ASSERT(metadata_->memoryUsage == MemoryUsage::Unshared);
|
||||
|
||||
RootedArrayBufferObjectMaybeShared buffer(cx,
|
||||
ArrayBufferObject::createForWasm(cx, declaredMin, declaredMax));
|
||||
if (!buffer)
|
||||
return false;
|
||||
|
||||
|
@ -569,12 +583,6 @@ Module::instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) c
|
|||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(buffer->is<SharedArrayBufferObject>() || buffer->as<ArrayBufferObject>().isWasm());
|
||||
|
||||
uint8_t* memoryBase = memory->buffer().dataPointerEither().unwrap(/* memcpy */);
|
||||
for (const DataSegment& seg : dataSegments_)
|
||||
memcpy(memoryBase + seg.memoryOffset, bytecode_->begin() + seg.bytecodeOffset, seg.length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -740,7 +748,7 @@ Module::instantiate(JSContext* cx,
|
|||
HandleWasmMemoryObject memoryImport,
|
||||
const ValVector& globalImports,
|
||||
HandleObject instanceProto,
|
||||
MutableHandleWasmInstanceObject instanceObj) const
|
||||
MutableHandleWasmInstanceObject instance) const
|
||||
{
|
||||
if (!instantiateFunctions(cx, funcImports))
|
||||
return false;
|
||||
|
@ -773,18 +781,18 @@ Module::instantiate(JSContext* cx,
|
|||
if (!code)
|
||||
return false;
|
||||
|
||||
instanceObj.set(WasmInstanceObject::create(cx,
|
||||
Move(code),
|
||||
memory,
|
||||
Move(tables),
|
||||
funcImports,
|
||||
globalImports,
|
||||
instanceProto));
|
||||
if (!instanceObj)
|
||||
instance.set(WasmInstanceObject::create(cx,
|
||||
Move(code),
|
||||
memory,
|
||||
Move(tables),
|
||||
funcImports,
|
||||
globalImports,
|
||||
instanceProto));
|
||||
if (!instance)
|
||||
return false;
|
||||
|
||||
RootedObject exportObj(cx);
|
||||
if (!CreateExportObject(cx, instanceObj, table, memory, globalImports, exports_, &exportObj))
|
||||
if (!CreateExportObject(cx, instance, table, memory, globalImports, exports_, &exportObj))
|
||||
return false;
|
||||
|
||||
JSAtom* atom = Atomize(cx, InstanceExportField, strlen(InstanceExportField));
|
||||
|
@ -793,31 +801,31 @@ Module::instantiate(JSContext* cx,
|
|||
RootedId id(cx, AtomToId(atom));
|
||||
|
||||
RootedValue val(cx, ObjectValue(*exportObj));
|
||||
if (!JS_DefinePropertyById(cx, instanceObj, id, val, JSPROP_ENUMERATE))
|
||||
if (!JS_DefinePropertyById(cx, instance, id, val, JSPROP_ENUMERATE))
|
||||
return false;
|
||||
|
||||
// Register the instance with the JSCompartment so that it can find out
|
||||
// about global events like profiling being enabled in the compartment.
|
||||
// Registration does not require a fully-initialized instance and must
|
||||
// precede initSegments as the final pre-requisite for a live instance.
|
||||
|
||||
if (!cx->compartment()->wasm.registerInstance(cx, instanceObj))
|
||||
if (!cx->compartment()->wasm.registerInstance(cx, instance))
|
||||
return false;
|
||||
|
||||
// Initialize table elements only after the instance is fully initialized
|
||||
// since the Table object needs to point to a valid instance object. Perform
|
||||
// initialization as the final step after the instance is fully live since
|
||||
// it is observable (in the case of an imported Table object) and can't be
|
||||
// easily rolled back in case of error.
|
||||
// Perform initialization as the final step after the instance is fully
|
||||
// constructed since this can make the instance live to content (even if the
|
||||
// start function fails).
|
||||
|
||||
if (!initElems(cx, instanceObj, globalImports, table))
|
||||
if (!initSegments(cx, instance, memory, globalImports))
|
||||
return false;
|
||||
|
||||
// Call the start function, if there's one. This effectively makes the
|
||||
// instance object live to content and thus must go after initialization is
|
||||
// complete.
|
||||
// Now that the instance is fully live and initialized, the start function.
|
||||
// Note that failure may cause instantiation to throw, but the instance may
|
||||
// still be live via edges created by initSegments or the start function.
|
||||
|
||||
if (metadata_->hasStartFunction()) {
|
||||
FixedInvokeArgs<0> args(cx);
|
||||
if (!instanceObj->instance().callExport(cx, metadata_->startFuncIndex(), args))
|
||||
if (!instance->instance().callExport(cx, metadata_->startFuncIndex(), args))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
|
|||
|
||||
struct DataSegment
|
||||
{
|
||||
uint32_t memoryOffset;
|
||||
InitExpr offset;
|
||||
uint32_t bytecodeOffset;
|
||||
uint32_t length;
|
||||
};
|
||||
|
@ -209,10 +209,13 @@ class Module : public RefCounted<Module>
|
|||
|
||||
bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
|
||||
bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
|
||||
bool instantiateTable(JSContext* cx, MutableHandleWasmTableObject table,
|
||||
bool instantiateTable(JSContext* cx,
|
||||
MutableHandleWasmTableObject table,
|
||||
SharedTableVector* tables) const;
|
||||
bool initElems(JSContext* cx, HandleWasmInstanceObject instanceObj,
|
||||
const ValVector& globalImports, HandleWasmTableObject tableObj) const;
|
||||
bool initSegments(JSContext* cx,
|
||||
HandleWasmInstanceObject instance,
|
||||
HandleWasmMemoryObject memory,
|
||||
const ValVector& globalImports) const;
|
||||
|
||||
public:
|
||||
Module(Bytes&& code,
|
||||
|
|
|
@ -2403,17 +2403,32 @@ ParseTypeDef(WasmParseContext& c)
|
|||
}
|
||||
|
||||
static AstDataSegment*
|
||||
ParseDataSegment(WasmParseContext& c)
|
||||
ParseDataSegment(WasmParseContext& c, bool newFormat)
|
||||
{
|
||||
WasmToken dstOffset;
|
||||
if (!c.ts.match(WasmToken::Index, &dstOffset, c.error))
|
||||
return nullptr;
|
||||
AstExpr* offset;
|
||||
if (newFormat) {
|
||||
WasmToken dstOffset;
|
||||
if (c.ts.getIf(WasmToken::Index, &dstOffset))
|
||||
offset = new(c.lifo) AstConst(Val(dstOffset.index()));
|
||||
else
|
||||
offset = ParseExpr(c);
|
||||
if (!offset)
|
||||
return nullptr;
|
||||
} else {
|
||||
WasmToken dstOffset;
|
||||
if (!c.ts.match(WasmToken::Index, &dstOffset, c.error))
|
||||
return nullptr;
|
||||
|
||||
offset = new(c.lifo) AstConst(Val(dstOffset.index()));
|
||||
if (!offset)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WasmToken text;
|
||||
if (!c.ts.match(WasmToken::Text, &text, c.error))
|
||||
return nullptr;
|
||||
|
||||
return new(c.lifo) AstDataSegment(dstOffset.index(), text.text());
|
||||
return new(c.lifo) AstDataSegment(offset, text.text());
|
||||
}
|
||||
|
||||
static bool
|
||||
|
@ -2442,7 +2457,7 @@ ParseMemory(WasmParseContext& c, WasmToken token, AstModule* module)
|
|||
while (c.ts.getIf(WasmToken::OpenParen)) {
|
||||
if (!c.ts.match(WasmToken::Segment, c.error))
|
||||
return false;
|
||||
AstDataSegment* segment = ParseDataSegment(c);
|
||||
AstDataSegment* segment = ParseDataSegment(c, /* newFormat = */ false);
|
||||
if (!segment || !module->append(segment))
|
||||
return false;
|
||||
if (!c.ts.match(WasmToken::CloseParen, c.error))
|
||||
|
@ -2706,7 +2721,7 @@ ParseModule(const char16_t* text, bool newFormat, LifoAlloc& lifo, UniqueChars*
|
|||
break;
|
||||
}
|
||||
case WasmToken::Data: {
|
||||
AstDataSegment* segment = ParseDataSegment(c);
|
||||
AstDataSegment* segment = ParseDataSegment(c, newFormat);
|
||||
if (!segment || !module->append(segment))
|
||||
return nullptr;
|
||||
break;
|
||||
|
@ -3213,13 +3228,6 @@ ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
|
|||
return r.fail("duplicate function");
|
||||
}
|
||||
|
||||
for (AstElemSegment* seg : module->elemSegments()) {
|
||||
for (AstRef& ref : seg->elems()) {
|
||||
if (!r.resolveFunction(ref))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t numImports = module->imports().length();
|
||||
size_t lastFuncImportIndex = 0;
|
||||
size_t lastGlobalIndex = 0;
|
||||
|
@ -3276,9 +3284,18 @@ ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
for (AstDataSegment* segment : module->dataSegments()) {
|
||||
if (!ResolveExpr(r, *segment->offset()))
|
||||
return false;
|
||||
}
|
||||
|
||||
for (AstElemSegment* segment : module->elemSegments()) {
|
||||
if (!ResolveExpr(r, *segment->offset()))
|
||||
return false;
|
||||
for (AstRef& ref : segment->elems()) {
|
||||
if (!r.resolveFunction(ref))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -4056,13 +4073,15 @@ EncodeDataSegment(Encoder& e, bool newFormat, AstDataSegment& segment)
|
|||
if (!e.writeVarU32(0)) // linear memory index
|
||||
return false;
|
||||
|
||||
if (!e.writeExpr(Expr::I32Const))
|
||||
if (!EncodeExpr(e, *segment.offset()))
|
||||
return false;
|
||||
if (!e.writeExpr(Expr::End))
|
||||
return false;
|
||||
} else {
|
||||
if (!e.writeVarU32(segment.offset()->as<AstConst>().val().i32()))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!e.writeVarU32(segment.offset()))
|
||||
return false;
|
||||
|
||||
AstName text = segment.text();
|
||||
|
||||
Vector<uint8_t, 0, SystemAllocPolicy> bytes;
|
||||
|
@ -4187,10 +4206,10 @@ EncodeModule(AstModule& module, bool newFormat, Bytes* bytes)
|
|||
if (!EncodeCodeSection(e, module))
|
||||
return false;
|
||||
|
||||
if (!EncodeDataSection(e, newFormat, module))
|
||||
if (!EncodeElemSection(e, newFormat, module))
|
||||
return false;
|
||||
|
||||
if (!EncodeElemSection(e, newFormat, module))
|
||||
if (!EncodeDataSection(e, newFormat, module))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -352,6 +352,60 @@ assertEq(i8[100], 0xc);
|
|||
assertEq(i8[101], 0xd);
|
||||
assertEq(i8[102], 0x0);
|
||||
|
||||
// Data segments with imported offsets
|
||||
|
||||
var m = new Module(textToBinary(`
|
||||
(module
|
||||
(import "glob" "a" (global i32 immutable))
|
||||
(memory 1)
|
||||
(data (get_global 0) "\\0a\\0b"))
|
||||
`));
|
||||
assertEq(new Instance(m, {glob:{a:0}}) instanceof Instance, true);
|
||||
assertEq(new Instance(m, {glob:{a:(64*1024 - 2)}}) instanceof Instance, true);
|
||||
assertErrorMessage(() => new Instance(m, {glob:{a:(64*1024 - 1)}}), RangeError, /data segment does not fit/);
|
||||
assertErrorMessage(() => new Instance(m, {glob:{a:64*1024}}), RangeError, /data segment does not fit/);
|
||||
|
||||
// Errors during segment initialization do not have observable effects
|
||||
// and are checked against the actual memory/table length, not the declared
|
||||
// initial length.
|
||||
|
||||
var m = new Module(textToBinary(`
|
||||
(module
|
||||
(import "a" "mem" (memory 1))
|
||||
(import "a" "tbl" (table 1))
|
||||
(import $memOff "a" "memOff" (global i32 immutable))
|
||||
(import $tblOff "a" "tblOff" (global i32 immutable))
|
||||
(func $f)
|
||||
(func $g)
|
||||
(data (i32.const 0) "\\01")
|
||||
(elem (i32.const 0) $f)
|
||||
(data (get_global $memOff) "\\02")
|
||||
(elem (get_global $tblOff) $g)
|
||||
(export "f" $f)
|
||||
(export "g" $g))
|
||||
`));
|
||||
|
||||
var npages = 2;
|
||||
var mem = new Memory({initial:npages});
|
||||
var mem8 = new Uint8Array(mem.buffer);
|
||||
var tbl = new Table({initial:2, element:"anyfunc"});
|
||||
|
||||
assertErrorMessage(() => new Instance(m, {a:{mem, tbl, memOff:1, tblOff:2}}), RangeError, /elem segment does not fit/);
|
||||
assertEq(mem8[0], 0);
|
||||
assertEq(mem8[1], 0);
|
||||
assertEq(tbl.get(0), null);
|
||||
|
||||
assertErrorMessage(() => new Instance(m, {a:{mem, tbl, memOff:npages*64*1024, tblOff:1}}), RangeError, /data segment does not fit/);
|
||||
assertEq(mem8[0], 0);
|
||||
assertEq(tbl.get(0), null);
|
||||
assertEq(tbl.get(1), null);
|
||||
|
||||
var i = new Instance(m, {a:{mem, tbl, memOff:npages*64*1024-1, tblOff:1}});
|
||||
assertEq(mem8[0], 1);
|
||||
assertEq(mem8[npages*64*1024-1], 2);
|
||||
assertEq(tbl.get(0), i.exports.f);
|
||||
assertEq(tbl.get(1), i.exports.g);
|
||||
|
||||
// Elem segments on imports
|
||||
|
||||
var m = new Module(textToBinary(`
|
||||
|
|
|
@ -19,18 +19,6 @@
|
|||
(module (memory 1 2 (segment 0 "a") (segment 98304 "b")))
|
||||
"data segment does not fit memory"
|
||||
)
|
||||
(assert_invalid
|
||||
(module (memory 1 2 (segment 0 "abc") (segment 0 "def")))
|
||||
"data segment not disjoint and ordered"
|
||||
)
|
||||
(assert_invalid
|
||||
(module (memory 1 2 (segment 3 "ab") (segment 0 "de")))
|
||||
"data segment not disjoint and ordered"
|
||||
)
|
||||
(assert_invalid
|
||||
(module (memory 1 2 (segment 0 "a") (segment 2 "b") (segment 1 "c")))
|
||||
"data segment not disjoint and ordered"
|
||||
)
|
||||
|
||||
;; Test alignment annotation rules
|
||||
;; TODO Tests being debated on the spec repo.
|
||||
|
|
|
@ -22,21 +22,25 @@ assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10))
|
|||
|
||||
assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 10) $f0) ${callee(0)})`)), TypeError, /element segment does not fit/);
|
||||
assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 8) $f0 $f0 $f0) ${callee(0)})`)), TypeError, /element segment does not fit/);
|
||||
assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 1) $f0 $f0) (elem (i32.const 0) $f0) ${callee(0)})`)), TypeError, /must be.*ordered/);
|
||||
assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 1) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`)), TypeError, /must be.*disjoint/);
|
||||
|
||||
assertErrorMessage(() => evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (get_global 0) $f0) ${callee(0)})`, {globals:{a:10}}), TypeError, /element segment does not fit/);
|
||||
assertErrorMessage(() => evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (get_global 0) $f0 $f0 $f0) ${callee(0)})`, {globals:{a:8}}), TypeError, /element segment does not fit/);
|
||||
assertErrorMessage(() => evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (i32.const 1) $f0 $f0) (elem (get_global 0) $f0) ${callee(0)})`, {globals:{a:0}}), TypeError, /must be.*ordered/);
|
||||
assertErrorMessage(() => evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (get_global 0) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`, {globals:{a:1}}), TypeError, /must be.*disjoint/);
|
||||
assertErrorMessage(() => evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (get_global 0) $f0) ${callee(0)})`, {globals:{a:10}}), RangeError, /elem segment does not fit/);
|
||||
assertErrorMessage(() => evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (get_global 0) $f0 $f0 $f0) ${callee(0)})`, {globals:{a:8}}), RangeError, /elem segment does not fit/);
|
||||
|
||||
assertEq(new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 1) $f0 $f0) (elem (i32.const 0) $f0) ${callee(0)})`)) instanceof Module, true);
|
||||
assertEq(new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 1) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`)) instanceof Module, true);
|
||||
evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (i32.const 1) $f0 $f0) (elem (get_global 0) $f0) ${callee(0)})`, {globals:{a:0}});
|
||||
evalText(`(module (table (resizable 10)) (import "globals" "a" (global i32 immutable)) (elem (get_global 0) $f0 $f0) (elem (i32.const 2) $f0) ${callee(0)})`, {globals:{a:1}});
|
||||
|
||||
var m = new Module(textToBinary(`
|
||||
(module
|
||||
(import "globals" "table" (table 10))
|
||||
(import "globals" "a" (global i32 immutable))
|
||||
(elem (get_global 0) $f0 $f0)
|
||||
${callee(0)})
|
||||
`));
|
||||
var tbl = new Table({initial:50, element:"anyfunc"});
|
||||
assertErrorMessage(() => evalText(`(module
|
||||
(import "globals" "table" (table 10 100))
|
||||
(import "globals" "a" (global i32 immutable))
|
||||
(elem (get_global 0) $f0 $f0)
|
||||
${callee(0)})
|
||||
`, {globals:{a:20, table:tbl}}), Error, /element segment does not fit/);
|
||||
assertEq(new Instance(m, {globals:{a:20, table:tbl}}) instanceof Instance, true);
|
||||
assertErrorMessage(() => new Instance(m, {globals:{a:50, table:tbl}}), RangeError, /elem segment does not fit/);
|
||||
|
||||
var caller = `(type $v2i (func (result i32))) (func $call (param $i i32) (result i32) (call_indirect $v2i (get_local $i))) (export "call" $call)`
|
||||
var callee = i => `(func $f${i} (type $v2i) (result i32) (i32.const ${i}))`;
|
||||
|
|
|
@ -360,6 +360,7 @@ MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD, 1, JSEXN_TYPEERR, "import object fiel
|
|||
MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG, 0, JSEXN_TYPEERR, "imported function signature mismatch")
|
||||
MSG_DEF(JSMSG_WASM_BAD_SET_VALUE, 0, JSEXN_TYPEERR, "second argument must be null or an exported WebAssembly Function object")
|
||||
MSG_DEF(JSMSG_WASM_BAD_I64, 0, JSEXN_TYPEERR, "cannot pass i64 to or from JS")
|
||||
MSG_DEF(JSMSG_WASM_BAD_FIT, 2, JSEXN_RANGEERR, "{0} segment does not fit in {1}")
|
||||
MSG_DEF(JSMSG_WASM_UNREACHABLE, 0, JSEXN_ERR, "unreachable executed")
|
||||
MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW, 0, JSEXN_ERR, "integer overflow")
|
||||
MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_ERR, "invalid conversion to integer")
|
||||
|
|
Загрузка…
Ссылка в новой задаче