Bug 1292724 - Baldr: fix segment offsets (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Tue, 06 Sep 2016 09:42:13 -0500
changeset 312918 dd39ceedb7f08460bc4cb8fe95bde63b7b3f388b
parent 312917 05cd37129db435532e201628a21ed1268354d786
child 312919 94befb88aee28153afdd35ef8a46761cd1a07bc6
push id30665
push usercbook@mozilla.com
push dateWed, 07 Sep 2016 15:20:43 +0000
treeherdermozilla-central@95acb9299faf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1292724
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1292724 - Baldr: fix segment offsets (r=bbouvier) MozReview-Commit-ID: L4sWbIYY0g9
js/src/asmjs/WasmAST.h
js/src/asmjs/WasmBinaryToAST.cpp
js/src/asmjs/WasmBinaryToExperimentalText.cpp
js/src/asmjs/WasmBinaryToText.cpp
js/src/asmjs/WasmCode.h
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmTextToBinary.cpp
js/src/jit-test/tests/wasm/import-export.js
js/src/jit-test/tests/wasm/spec/memory.wast
js/src/jit-test/tests/wasm/tables.js
js/src/js.msg
--- a/js/src/asmjs/WasmAST.h
+++ b/js/src/asmjs/WasmAST.h
@@ -641,24 +641,25 @@ class AstExport : public AstNode
     AstRef& ref() {
         MOZ_ASSERT(kind_ == DefinitionKind::Function || kind_ == DefinitionKind::Global);
         return ref_;
     }
 };
 
 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_; }
 };
 
 typedef AstVector<AstDataSegment*> AstDataSegmentVector;
 
 class AstElemSegment : public AstNode
 {
     AstExpr* offset_;
--- a/js/src/asmjs/WasmBinaryToAST.cpp
+++ b/js/src/asmjs/WasmBinaryToAST.cpp
@@ -1650,47 +1650,45 @@ AstDecodeDataSection(AstDecodeContext &c
     if (sectionStart == Decoder::NotStarted)
         return true;
 
     uint32_t numSegments;
     if (!c.d.readVarU32(&numSegments))
         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");
 
         if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
             return AstDecodeFail(c, "data segment does not fit in memory");
 
         const uint8_t* src;
         if (!c.d.readBytes(numBytes, &src))
             return AstDecodeFail(c, "data segment shorter than declared");
 
         char16_t *buffer = static_cast<char16_t *>(c.lifo.alloc(numBytes * sizeof(char16_t)));
         for (size_t i = 0; i < numBytes; i++)
             buffer[i] = src[i];
 
+        AstExpr* offset = new(c.lifo) AstConst(Val(dstOffset));
+        if (!offset)
+            return false;
+
         AstName name(buffer, numBytes);
-        AstDataSegment* segment = new(c.lifo) AstDataSegment(dstOffset, name);
+        AstDataSegment* segment = new(c.lifo) AstDataSegment(offset, name);
         if (!segment || !c.module().append(segment))
             return false;
-
-        prevEnd = dstOffset + numBytes;
     }
 
     if (!c.d.finishSection(sectionStart, sectionSize))
         return AstDecodeFail(c, "data section byte size mismatch");
 
     return true;
 }
 
--- a/js/src/asmjs/WasmBinaryToExperimentalText.cpp
+++ b/js/src/asmjs/WasmBinaryToExperimentalText.cpp
@@ -1734,17 +1734,17 @@ PrintDataSection(WasmPrintContext& c, co
 
     for (uint32_t i = 0; i < numSegments; i++) {
         const AstDataSegment* segment = module.dataSegments()[i];
 
         if (!PrintIndent(c))
             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;
 
         PrintEscapedString(c, segment->text());
 
         if (!c.buffer.append("\";\n"))
            return false;
--- a/js/src/asmjs/WasmBinaryToText.cpp
+++ b/js/src/asmjs/WasmBinaryToText.cpp
@@ -1329,17 +1329,17 @@ RenderDataSection(WasmRenderContext& c, 
 
     for (uint32_t i = 0; i < numSegments; i++) {
         const AstDataSegment* segment = module.dataSegments()[i];
 
         if (!RenderIndent(c))
             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;
 
         RenderEscapedString(c, segment->text());
 
         if (!c.buffer.append("\")\n"))
            return false;
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -107,16 +107,17 @@ class CodeSegment
 
 struct ShareableBytes : ShareableBase<ShareableBytes>
 {
     // Vector is 'final', so instead make Vector a member and add boilerplate.
     Bytes bytes;
     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); }
 };
 
 typedef RefPtr<ShareableBytes> MutableBytes;
 typedef RefPtr<const ShareableBytes> SharedBytes;
 
 // A FuncExport represents a single function inside a wasm Module that has been
 // exported one or more times. A FuncExport represents an internal entry point
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -1330,32 +1330,29 @@ DecodeElemSection(Decoder& d, bool newFo
 
     uint32_t numSegments;
     if (!d.readVarU32(&numSegments))
         return Fail(d, "failed to read number of elem segments");
 
     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");
 
         MOZ_ASSERT(mg.tables().length() <= 1);
         if (tableIndex >= mg.tables().length())
             return Fail(d, "table index out of range");
 
         InitExpr offset;
         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");
 
         uint32_t tableLength = mg.tables()[tableIndex].initial;
         if (offset.isVal()) {
             uint32_t off = offset.val().i32();
             if (off > tableLength || tableLength - off < numElems)
@@ -1368,19 +1365,16 @@ DecodeElemSection(Decoder& d, bool newFo
 
         for (uint32_t i = 0; i < numElems; i++) {
             if (!d.readVarU32(&elemFuncIndices[i]))
                 return Fail(d, "failed to read element function index");
             if (elemFuncIndices[i] >= mg.numFuncSigs())
                 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;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(d, "data section byte size mismatch");
 
     return true;
@@ -1401,55 +1395,52 @@ DecodeDataSection(Decoder& d, bool newFo
     uint32_t numSegments;
     if (!d.readVarU32(&numSegments))
         return Fail(d, "failed to read number of data segments");
 
     if (numSegments > MaxDataSegments)
         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))
                 return Fail(d, "expected linear memory index");
 
             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();
 
         if (!d.readBytes(seg.length))
             return Fail(d, "data segment shorter than declared");
 
         if (!mg.addDataSegment(seg))
             return false;
-
-        prevEnd = seg.memoryOffset + seg.length;
     }
 
     if (!d.finishSection(sectionStart, sectionSize))
         return Fail(d, "data section byte size mismatch");
 
     return true;
 }
 
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -407,79 +407,102 @@ Module::addSizeOfMisc(MallocSizeOf mallo
              SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
              SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
              dataSegments_.sizeOfExcludingThis(mallocSizeOf) +
              SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
              metadata_->sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) +
              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];
+        uint32_t offset = EvaluateInitExpr(globalImports, seg.offset);
+        bool profilingEnabled = instance.code().profilingEnabled();
+        const CodeRangeVector& codeRanges = metadata().codeRanges;
+        uint8_t* codeBase = instance.codeBase();
 
-        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;
-        }
-
-        bool profilingEnabled = instance.code().profilingEnabled();
-        const CodeRangeVector& codeRanges = instance.code().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;
 }
 
 bool
 Module::instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const
 {
@@ -513,73 +536,58 @@ bool
 Module::instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const
 {
     if (!metadata_->usesMemory()) {
         MOZ_ASSERT(!memory);
         MOZ_ASSERT(dataSegments_.empty());
         return true;
     }
 
-    RootedArrayBufferObjectMaybeShared buffer(cx);
+    uint32_t declaredMin = metadata_->minMemoryLength;
+    Maybe<uint32_t> declaredMax = metadata_->maxMemoryLength;
+
     if (memory) {
-        buffer = &memory->buffer();
-        uint32_t length = buffer->wasmActualByteLength();
-        uint32_t declaredMaxLength = metadata_->maxMemoryLength.valueOr(UINT32_MAX);
+        RootedArrayBufferObjectMaybeShared buffer(cx, &memory->buffer());
+        MOZ_RELEASE_ASSERT(buffer->is<SharedArrayBufferObject>() ||
+                           buffer->as<ArrayBufferObject>().isWasm());
 
-        // 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) {
+        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;
         }
 
         // For asm.js maxMemoryLength doesn't play a role since we can't grow memory.
         // 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());
+    } else {
+        MOZ_ASSERT(!metadata_->isAsmJS());
+        MOZ_ASSERT(metadata_->memoryUsage == MemoryUsage::Unshared);
 
-        // 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);
-
+        RootedArrayBufferObjectMaybeShared buffer(cx,
+            ArrayBufferObject::createForWasm(cx, declaredMin, declaredMax));
         if (!buffer)
             return false;
 
         RootedObject proto(cx);
         if (metadata_->assumptions.newFormat)
             proto = &cx->global()->getPrototype(JSProto_WasmMemory).toObject();
 
         memory.set(WasmMemoryObject::create(cx, buffer, proto));
         if (!memory)
             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;
 }
 
 bool
 Module::instantiateTable(JSContext* cx, MutableHandleWasmTableObject tableObj,
                          SharedTableVector* tables) const
 {
     if (tableObj) {
@@ -735,17 +743,17 @@ CreateExportObject(JSContext* cx,
 
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
                     HandleWasmTableObject tableImport,
                     HandleWasmMemoryObject memoryImport,
                     const ValVector& globalImports,
                     HandleObject instanceProto,
-                    MutableHandleWasmInstanceObject instanceObj) const
+                    MutableHandleWasmInstanceObject instance) const
 {
     if (!instantiateFunctions(cx, funcImports))
         return false;
 
     RootedWasmMemoryObject memory(cx, memoryImport);
     if (!instantiateMemory(cx, &memory))
         return false;
 
@@ -768,58 +776,58 @@ Module::instantiate(JSContext* cx,
     auto codeSegment = CodeSegment::create(cx, code_, linkData_, *metadata_, memory);
     if (!codeSegment)
         return false;
 
     auto code = cx->make_unique<Code>(Move(codeSegment), *metadata_, maybeBytecode);
     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));
     if (!atom)
         return false;
     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;
     }
 
     return true;
 }
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -144,17 +144,17 @@ class Export
 
 typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
 
 // DataSegment describes the offset of a data segment in the bytecode that is
 // to be copied at a given offset into linear memory upon instantiation.
 
 struct DataSegment
 {
-    uint32_t memoryOffset;
+    InitExpr offset;
     uint32_t bytecodeOffset;
     uint32_t length;
 };
 
 typedef Vector<DataSegment, 0, SystemAllocPolicy> DataSegmentVector;
 
 // ElemSegment represents an element segment in the module where each element
 // describes both its function index and its code range.
@@ -204,20 +204,23 @@ class Module : public RefCounted<Module>
     const ExportVector      exports_;
     const DataSegmentVector dataSegments_;
     const ElemSegmentVector elemSegments_;
     const SharedMetadata    metadata_;
     const SharedBytes       bytecode_;
 
     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,
            LinkData&& linkData,
            ImportVector&& imports,
            ExportVector&& exports,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -2398,27 +2398,42 @@ ParseTypeDef(WasmParseContext& c)
 
     if (!c.ts.match(WasmToken::CloseParen, c.error))
         return nullptr;
 
     return new(c.lifo) AstSig(name, Move(sig));
 }
 
 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
 ParseResizable(WasmParseContext& c, AstResizable* resizable)
 {
     WasmToken initial;
     if (!c.ts.match(WasmToken::Index, &initial, c.error))
         return false;
@@ -2437,17 +2452,17 @@ ParseMemory(WasmParseContext& c, WasmTok
 {
     AstResizable memory;
     if (!ParseResizable(c, &memory))
         return false;
 
     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))
             return false;
     }
 
     if (!module->setMemory(memory)) {
         c.ts.generateError(token, c.error);
@@ -2701,17 +2716,17 @@ ParseModule(const char16_t* text, bool n
           }
           case WasmToken::Global: {
             AstGlobal* global = ParseGlobal(c);
             if (!global || !module->append(global))
                 return nullptr;
             break;
           }
           case WasmToken::Data: {
-            AstDataSegment* segment = ParseDataSegment(c);
+            AstDataSegment* segment = ParseDataSegment(c, newFormat);
             if (!segment || !module->append(segment))
                 return nullptr;
             break;
           }
           case WasmToken::Import: {
             AstImport* imp = ParseImport(c, newFormat, module);
             if (!imp || !module->append(imp))
                 return nullptr;
@@ -3208,23 +3223,16 @@ ResolveModule(LifoAlloc& lifo, AstModule
     for (size_t i = 0; i < numFuncs; i++) {
         AstFunc* func = module->funcs()[i];
         if (!r.resolveSignature(func->sig()))
             return false;
         if (!r.registerFuncName(func->name(), i))
             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;
     for (size_t i = 0; i < numImports; i++) {
         AstImport* imp = module->imports()[i];
         switch (imp->kind()) {
           case DefinitionKind::Function:
             if (!r.registerImportName(imp->name(), lastFuncImportIndex++))
@@ -3271,19 +3279,28 @@ ResolveModule(LifoAlloc& lifo, AstModule
             return false;
     }
 
     if (module->hasStartFunc()) {
         if (!r.resolveFunction(module->startFunc().func()))
             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;
 }
 
 /*****************************************************************************/
 // wasm function body serialization
 
@@ -4051,23 +4068,25 @@ EncodeCodeSection(Encoder& e, AstModule&
 
 static bool
 EncodeDataSegment(Encoder& e, bool newFormat, AstDataSegment& segment)
 {
     if (newFormat) {
         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;
     if (!bytes.reserve(text.length()))
         return false;
 
     const char16_t* cur = text.begin();
     const char16_t* end = text.end();
@@ -4182,20 +4201,20 @@ EncodeModule(AstModule& module, bool new
         return false;
 
     if (!EncodeStartSection(e, module))
         return false;
 
     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;
 }
 
 /*****************************************************************************/
 
 bool
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -347,16 +347,70 @@ assertEq(get(102), 0x0);
 var i8 = new Uint8Array(mem.buffer);
 assertEq(i8[0], 0xa);
 assertEq(i8[1], 0xb);
 assertEq(i8[2], 0x0);
 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(`
     (module
         (import "a" "b" (table 10))
         (elem (i32.const 0) $one $two)
         (elem (i32.const 3) $three $four)
         (func $one (result i32) (i32.const 1))
--- a/js/src/jit-test/tests/wasm/spec/memory.wast
+++ b/js/src/jit-test/tests/wasm/spec/memory.wast
@@ -14,28 +14,16 @@
 (assert_invalid
   (module (memory 0 0 (segment 0 "a")))
   "data segment does not fit memory"
 )
 (assert_invalid
   (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.
 ;; https://github.com/WebAssembly/spec/issues/217
 ;;(module (memory 0) (func (i32.load8_u align=2 (i32.const 0))))
 ;;(module (memory 0) (func (i32.load16_u align=4 (i32.const 0))))
 ;;(module (memory 0) (func (i32.load align=8 (i32.const 0))))
 ;;(module (memory 0) (func (f32.load align=8 (i32.const 0))))
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -17,31 +17,35 @@ var callee = i => `(func $f${i} (result 
 
 assertErrorMessage(() => new Module(textToBinary(`(module (elem (i32.const 0) $f0) ${callee(0)})`)), TypeError, /table index out of range/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (elem (i32.const 0) 0))`)), TypeError, /table element out of range/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (func) (elem (i32.const 0) 0 1))`)), TypeError, /table element out of range/);
 assertErrorMessage(() => new Module(textToBinary(`(module (table (resizable 10)) (func) (elem (f32.const 0) 0) ${callee(0)})`)), TypeError, /type mismatch/);
 
 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}}), 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/);
 
-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/);
+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}))`;
 
 var call = evalText(`(module (table (resizable 10)) ${callee(0)} ${caller})`).exports.call;
 assertErrorMessage(() => call(0), Error, /bad wasm indirect call/);
 assertErrorMessage(() => call(10), Error, /out-of-range/);
 
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -355,16 +355,17 @@ MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_TYPEERR,     "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_SIZE,           2, JSEXN_TYPEERR,     "bad {0} {1} size")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument, if present, must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field is not {0}")
 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")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_ERR,         "integer divide by zero")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_ERR,         "unaligned memory access")
 MSG_DEF(JSMSG_WASM_OVERRECURSED,       0, JSEXN_INTERNALERR, "call stack exhausted")
 
 // Proxy