Bug 1244405 - Baldr: add memory segments (r=sunfish)
authorLuke Wagner <luke@mozilla.com>
Thu, 04 Feb 2016 21:39:18 -0600
changeset 283130 0a534e3ded0582ebbdef2a91ac37cad43a1aa557
parent 283129 0bc0fead84b9d4174e49d02f01c6aac62634edf4
child 283131 a0599bb3ff5c145ad4af7da3135b83677ab83acf
push id29974
push usercbook@mozilla.com
push dateFri, 05 Feb 2016 10:53:43 +0000
treeherdermozilla-central@1dbe350b57b1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssunfish
bugs1244405
milestone47.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 1244405 - Baldr: add memory segments (r=sunfish)
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmText.cpp
js/src/jit-test/tests/wasm/basic.js
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -25,18 +25,16 @@
 #include "vm/ArrayBufferObject.h"
 
 #include "jsatominlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
 using namespace js::wasm;
 
-using mozilla::PodCopy;
-
 typedef Handle<WasmModuleObject*> HandleWasmModule;
 typedef MutableHandle<WasmModuleObject*> MutableHandleWasmModule;
 
 /*****************************************************************************/
 // reporting
 
 static bool
 Fail(JSContext* cx, const char* str)
@@ -510,17 +508,17 @@ DecodeFuncBody(JSContext* cx, Decoder& d
     if (!DecodeExpr(f, f.ret()))
         return false;
 
     const uint8_t* bodyEnd = d.currentPosition();
     uintptr_t bodyLength = bodyEnd - bodyBegin;
     if (!fg.bytecode().resize(bodyLength))
         return false;
 
-    PodCopy(fg.bytecode().begin(), bodyBegin, bodyLength);
+    memcpy(fg.bytecode().begin(), bodyBegin, bodyLength);
     return true;
 }
 
 /*****************************************************************************/
 // dynamic link data
 
 struct ImportName
 {
@@ -898,27 +896,78 @@ DecodeCodeSection(JSContext* cx, Decoder
 
     if (!mg.finishFuncDefs())
         return false;
 
     return true;
 }
 
 static bool
+DecodeDataSection(JSContext* cx, Decoder& d, Handle<ArrayBufferObject*> heap)
+{
+    if (!d.readCStringIf(DataSection))
+        return true;
+
+    uint32_t sectionStart;
+    if (!d.startSection(&sectionStart))
+        return Fail(cx, d, "expected data section byte size");
+
+    uint32_t numSegments;
+    if (!d.readVarU32(&numSegments))
+        return Fail(cx, d, "expected number of data segments");
+
+    uint8_t* const heapBase = heap->dataPointer();
+    uint32_t const heapLength = heap->byteLength();
+    uint32_t prevEnd = 0;
+
+    for (uint32_t i = 0; i < numSegments; i++) {
+        if (!d.readCStringIf(SegmentSubsection))
+            return Fail(cx, d, "expected segment tag");
+
+        uint32_t dstOffset;
+        if (!d.readVarU32(&dstOffset))
+            return Fail(cx, d, "expected segment destination offset");
+
+        if (dstOffset < prevEnd)
+            return Fail(cx, d, "data segments must be disjoint and ordered");
+
+        uint32_t numBytes;
+        if (!d.readVarU32(&numBytes))
+            return Fail(cx, d, "expected segment size");
+
+        if (dstOffset > heapLength || heapLength - dstOffset < numBytes)
+            return Fail(cx, d, "data segment does not fit in memory");
+
+        const uint8_t* src;
+        if (!d.readData(numBytes, &src))
+            return Fail(cx, d, "data segment shorter than declared");
+
+        memcpy(heapBase + dstOffset, src, numBytes);
+        prevEnd = dstOffset + numBytes;
+    }
+
+    if (!d.finishSection(sectionStart))
+        return Fail(cx, d, "data section byte size mismatch");
+
+    return true;
+}
+
+static bool
 DecodeUnknownSection(JSContext* cx, Decoder& d)
 {
     UniqueChars sectionName = d.readCString();
     if (!sectionName)
         return Fail(cx, d, "failed to read section name");
 
     if (!strcmp(sectionName.get(), SigSection) ||
         !strcmp(sectionName.get(), ImportSection) ||
         !strcmp(sectionName.get(), DeclSection) ||
         !strcmp(sectionName.get(), ExportSection) ||
-        !strcmp(sectionName.get(), CodeSection))
+        !strcmp(sectionName.get(), CodeSection) ||
+        !strcmp(sectionName.get(), DataSection))
     {
         return Fail(cx, d, "known section out of order");
     }
 
     if (!d.skipSection())
         return Fail(cx, d, "unable to skip unknown section");
 
     return true;
@@ -959,16 +1008,19 @@ DecodeModule(JSContext* cx, UniqueChars 
         return false;
 
     if (!DecodeExportsSection(cx, d, mg))
         return false;
 
     if (!DecodeCodeSection(cx, d, mg))
         return false;
 
+    if (!DecodeDataSection(cx, d, heap))
+        return false;
+
     CacheableCharsVector funcNames;
 
     while (!d.readCStringIf(EndSection)) {
         if (!DecodeUnknownSection(cx, d))
             return false;
     }
 
     if (!d.done())
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -43,21 +43,23 @@ static const uint32_t EncodingVersion = 
 
 // Module section names:
 static const char SigSection[]        = "sig";
 static const char ImportSection[]     = "import";
 static const char DeclSection[]       = "decl";
 static const char MemorySection[]     = "memory";
 static const char ExportSection[]     = "export";
 static const char CodeSection[]       = "code";
+static const char DataSection[]       = "data";
 static const char EndSection[]        = "";
 
 // Subsection names:
 static const char FuncSubsection[]    = "func";
 static const char MemorySubsection[]  = "memory";
+static const char SegmentSubsection[] = "segment";
 
 // Field names:
 static const char FieldInitial[]      = "initial";
 
 enum class Expr : uint16_t
 {
     // Control opcodes
     Nop,
@@ -434,16 +436,21 @@ class Encoder
         return true;
     }
 
     MOZ_WARN_UNUSED_RESULT bool writeCString(const char* cstr) {
         MOZ_ASSERT(cstr);
         return bytecode_.append(reinterpret_cast<const uint8_t*>(cstr), strlen(cstr) + 1);
     }
 
+    MOZ_WARN_UNUSED_RESULT bool writeData(const uint8_t* bytes, uint32_t numBytes) {
+        MOZ_ASSERT(bytes);
+        return bytecode_.append(bytes, numBytes);
+    }
+
     MOZ_WARN_UNUSED_RESULT bool startSection(size_t* offset) {
         if (!writeU32(BadSectionLength))
             return false;
         *offset = bytecode_.length();
         return true;
     }
     void finishSection(size_t offset) {
         uint8_t* patchAt = bytecode_.begin() + offset - sizeof(uint32_t);
@@ -480,43 +487,46 @@ class Encoder
 };
 
 class Decoder
 {
     const uint8_t* const beg_;
     const uint8_t* const end_;
     const uint8_t* cur_;
 
+    uintptr_t bytesRemain() const {
+        MOZ_ASSERT(end_ >= cur_);
+        return uintptr_t(end_ - cur_);
+    }
+
     template <class T>
-    MOZ_WARN_UNUSED_RESULT bool
-    read(T* out) {
-        if (uintptr_t(end_ - cur_) < sizeof(T))
+    MOZ_WARN_UNUSED_RESULT bool read(T* out) {
+        if (bytesRemain() < sizeof(T))
             return false;
         if (out)
             memcpy((void*)out, cur_, sizeof(T));
         cur_ += sizeof(T);
         return true;
     }
 
     template <class IntT, class T>
-    MOZ_WARN_UNUSED_RESULT bool
-    readEnum(T* out) {
+    MOZ_WARN_UNUSED_RESULT bool readEnum(T* out) {
         static_assert(mozilla::IsEnum<T>::value, "is an enum");
         // See Encoder::writeEnum.
         IntT i;
         if (!read(&i) || i >= IntT(T::Limit))
             return false;
         if (out)
             *out = T(i);
         return true;
     }
 
     template <class T>
     T uncheckedPeek() const {
-        MOZ_ASSERT(uintptr_t(end_ - cur_) >= sizeof(T));
+        MOZ_ASSERT(bytesRemain() >= sizeof(T));
         T ret;
         memcpy(&ret, cur_, sizeof(T));
         return ret;
     }
 
     template <class IntT, class T>
     T uncheckedPeekEnum() const {
         // See Encoder::writeEnum.
@@ -647,16 +657,25 @@ class Decoder
             if (!*p) {
                 cur_ = p + 1;
                 return true;
             }
         }
         return false;
     }
 
+    MOZ_WARN_UNUSED_RESULT bool readData(uint32_t numBytes, const uint8_t** bytes = nullptr) {
+        if (bytes)
+            *bytes = cur_;
+        if (bytesRemain() < numBytes)
+            return false;
+        cur_ += numBytes;
+        return true;
+    }
+
     MOZ_WARN_UNUSED_RESULT bool startSection(uint32_t* offset) {
         uint32_t unused;
         if (!readU32(&unused))
             return false;
         *offset = currentOffset();
         return true;
     }
     MOZ_WARN_UNUSED_RESULT bool finishSection(uint32_t offset) {
@@ -664,17 +683,17 @@ class Decoder
         uint32_t numBytes;
         memcpy(&numBytes, start - sizeof(uint32_t), sizeof(uint32_t));
         return numBytes == uintptr_t(cur_ - start);
     }
     MOZ_WARN_UNUSED_RESULT bool skipSection() {
         uint32_t numBytes;
         if (!readU32(&numBytes))
             return false;
-        if (uintptr_t(end_ - cur_) < numBytes)
+        if (bytesRemain() < numBytes)
             return false;
         cur_ += numBytes;
         return true;
     }
 
     // The infallible unpacking API should be used when we are sure that the
     // bytecode is well-formed.
     uint8_t        uncheckedReadU8 () { return uncheckedRead<uint8_t>(); }
--- a/js/src/asmjs/WasmText.cpp
+++ b/js/src/asmjs/WasmText.cpp
@@ -287,25 +287,43 @@ class WasmAstExport : public WasmAstNode
     explicit WasmAstExport(TwoByteChars name)
       : name_(name), kind_(WasmAstExportKind::Memory)
     {}
     TwoByteChars name() const { return name_; }
     WasmAstExportKind kind() const { return kind_; }
     size_t funcIndex() const { MOZ_ASSERT(kind_ == WasmAstExportKind::Func); return u.funcIndex_; }
 };
 
+class WasmAstSegment : public WasmAstNode
+{
+    uint32_t offset_;
+    TwoByteChars text_;
+
+  public:
+    WasmAstSegment(uint32_t offset, TwoByteChars text)
+      : offset_(offset), text_(text)
+    {}
+    uint32_t offset() const { return offset_; }
+    TwoByteChars text() const { return text_; }
+};
+
+typedef WasmAstVector<WasmAstSegment*> WasmAstSegmentVector;
+
 class WasmAstMemory : public WasmAstNode
 {
     uint32_t initialSize_;
+    WasmAstSegmentVector segments_;
 
   public:
-    explicit WasmAstMemory(uint32_t initialSize)
-      : initialSize_(initialSize)
+    explicit WasmAstMemory(uint32_t initialSize, WasmAstSegmentVector&& segments)
+      : initialSize_(initialSize),
+        segments_(Move(segments))
     {}
     uint32_t initialSize() const { return initialSize_; }
+    const WasmAstSegmentVector& segments() const { return segments_; }
 };
 
 class WasmAstModule : public WasmAstNode
 {
     typedef WasmAstVector<WasmAstFunc*> FuncVector;
     typedef WasmAstVector<WasmAstImport*> ImportVector;
     typedef WasmAstVector<WasmAstExport*> ExportVector;
     typedef WasmAstVector<WasmAstSig*> SigVector;
@@ -470,16 +488,17 @@ class WasmToken
         Memory,
         Local,
         Module,
         Name,
         Nop,
         OpenParen,
         Param,
         Result,
+        Segment,
         SetLocal,
         Text,
         UnaryOpcode,
         ValueType
     };
   private:
     Kind kind_;
     const char16_t* begin_;
@@ -597,16 +616,83 @@ IsWasmLetter(char16_t c)
 }
 
 static bool
 IsNameAfterDollar(char16_t c)
 {
     return c == '_' || IsWasmDigit(c) || IsWasmLetter(c);
 }
 
+static bool
+IsHexDigit(char c, uint8_t* value)
+{
+    if (c >= '0' && c <= '9') {
+        *value = c - '0';
+        return true;
+    }
+
+    if (c >= 'a' && c <= 'f') {
+        *value = 10 + (c - 'a');
+        return true;
+    }
+
+    if (c >= 'A' && c <= 'F') {
+        *value = 10 + (c - 'A');
+        return true;
+    }
+
+    return false;
+}
+
+static bool
+ConsumeTextByte(const char16_t** curp, const char16_t* end, uint8_t *byte = nullptr)
+{
+    const char16_t*& cur = *curp;
+    MOZ_ASSERT(cur != end);
+
+    if (*cur != '\\') {
+        if (byte)
+            *byte = *cur;
+        cur++;
+        return true;
+    }
+
+    if (++cur == end)
+        return false;
+
+    uint8_t u8;
+    switch (*cur) {
+      case 'n': u8 = '\n'; break;
+      case 't': u8 = '\t'; break;
+      case '\\': u8 = '\\'; break;
+      case '\"': u8 = '\"'; break;
+      case '\'': u8 = '\''; break;
+      default: {
+        uint8_t lowNibble;
+        if (!IsHexDigit(*cur, &lowNibble))
+            return false;
+
+        if (++cur == end)
+            return false;
+
+        uint8_t highNibble;
+        if (!IsHexDigit(*cur, &highNibble))
+            return false;
+
+        u8 = lowNibble | (highNibble << 4);
+        break;
+      }
+    }
+
+    if (byte)
+        *byte = u8;
+    cur++;
+    return true;
+}
+
 class WasmTokenStream
 {
     static const uint32_t LookaheadSize = 2;
 
     const char16_t* cur_;
     const char16_t* const end_;
     const char16_t* lineStart_;
     unsigned line_;
@@ -636,20 +722,25 @@ class WasmTokenStream
 
         if (cur_ == end_)
             return WasmToken(WasmToken::EndOfFile, cur_, cur_);
 
         const char16_t* begin = cur_;
         switch (*begin) {
           case '"':
             cur_++;
-            do {
+            while (true) {
                 if (cur_ == end_)
                     return fail(begin);
-            } while (*cur_++ != '"');
+                if (*cur_ == '"')
+                    break;
+                if (!ConsumeTextByte(&cur_, end_))
+                    return fail(begin);
+            }
+            cur_++;
             return WasmToken(WasmToken::Text, begin, cur_);
 
           case '$':
             cur_++;
             while (cur_ != end_ && IsNameAfterDollar(*cur_))
                 cur_++;
             return WasmToken(WasmToken::Name, begin, cur_);
 
@@ -1128,16 +1219,18 @@ class WasmTokenStream
           case 'r':
             if (consume(MOZ_UTF16("result")))
                 return WasmToken(WasmToken::Result, begin, cur_);
             break;
 
           case 's':
             if (consume(MOZ_UTF16("set_local")))
                 return WasmToken(WasmToken::SetLocal, begin, cur_);
+            if (consume(MOZ_UTF16("segment")))
+                return WasmToken(WasmToken::Segment, begin, cur_);
             break;
 
           default:
             break;
         }
 
         return fail(begin);
     }
@@ -1479,24 +1572,50 @@ ParseFunc(WasmParseContext& c, WasmAstMo
 
     uint32_t sigIndex;
     if (!module->declare(WasmAstSig(Move(args), result), &sigIndex))
         return nullptr;
 
     return new(c.lifo) WasmAstFunc(sigIndex, Move(vars), maybeBody);
 }
 
+static WasmAstSegment*
+ParseSegment(WasmParseContext& c)
+{
+    if (!c.ts.match(WasmToken::Segment, c.error))
+        return nullptr;
+
+    WasmToken dstOffset;
+    if (!c.ts.match(WasmToken::Integer, &dstOffset, c.error))
+        return nullptr;
+
+    WasmToken text;
+    if (!c.ts.match(WasmToken::Text, &text, c.error))
+        return nullptr;
+
+    return new(c.lifo) WasmAstSegment(dstOffset.integer(), text.text());
+}
+
 static WasmAstMemory*
 ParseMemory(WasmParseContext& c)
 {
     WasmToken initialSize;
     if (!c.ts.match(WasmToken::Integer, &initialSize, c.error))
         return nullptr;
 
-    return new(c.lifo) WasmAstMemory(initialSize.integer());
+    WasmAstSegmentVector segments(c.lifo);
+    while (c.ts.getIf(WasmToken::OpenParen)) {
+        WasmAstSegment* segment = ParseSegment(c);
+        if (!segment || !segments.append(segment))
+            return nullptr;
+        if (!c.ts.match(WasmToken::CloseParen, c.error))
+            return nullptr;
+    }
+
+    return new(c.lifo) WasmAstMemory(initialSize.integer(), Move(segments));
 }
 
 static WasmAstImport*
 ParseImport(WasmParseContext& c, WasmAstModule* module)
 {
     WasmToken moduleName;
     if (!c.ts.match(WasmToken::Text, &moduleName, c.error))
         return nullptr;
@@ -1552,17 +1671,17 @@ ParseExport(WasmParseContext& c)
     }
 
     c.ts.generateError(exportee, c.error);
     return nullptr;
 
 }
 
 static WasmAstModule*
-TextToAst(const char16_t* text, LifoAlloc& lifo, UniqueChars* error)
+ParseModule(const char16_t* text, LifoAlloc& lifo, UniqueChars* error)
 {
     WasmParseContext c(text, lifo, error);
 
     if (!c.ts.match(WasmToken::OpenParen, c.error))
         return nullptr;
     if (!c.ts.match(WasmToken::Module, c.error))
         return nullptr;
 
@@ -2005,18 +2124,77 @@ EncodeCodeSection(Encoder& e, WasmAstMod
         if (!EncodeFunc(e, *func))
             return false;
     }
 
     e.finishSection(offset);
     return true;
 }
 
+static bool
+EncodeDataSegment(Encoder& e, WasmAstSegment& segment)
+{
+    if (!e.writeCString(SegmentSubsection))
+        return false;
+
+    if (!e.writeVarU32(segment.offset()))
+        return false;
+
+    TwoByteChars text = segment.text();
+
+    Vector<uint8_t, 0, SystemAllocPolicy> bytes;
+    if (!bytes.reserve(text.length()))
+        return false;
+
+    const char16_t* cur = text.start().get();
+    const char16_t* end = text.end().get();
+    while (cur != end) {
+        uint8_t byte;
+        MOZ_ALWAYS_TRUE(ConsumeTextByte(&cur, end, &byte));
+        bytes.infallibleAppend(byte);
+    }
+
+    if (!e.writeVarU32(bytes.length()))
+        return false;
+
+    if (!e.writeData(bytes.begin(), bytes.length()))
+        return false;
+
+    return true;
+}
+
+static bool
+EncodeDataSection(Encoder& e, WasmAstModule& module)
+{
+    if (!module.maybeMemory() || module.maybeMemory()->segments().empty())
+        return true;
+
+    const WasmAstSegmentVector& segments = module.maybeMemory()->segments();
+
+    if (!e.writeCString(DataSection))
+        return false;
+
+    size_t offset;
+    if (!e.startSection(&offset))
+        return false;
+
+    if (!e.writeVarU32(segments.length()))
+        return false;
+
+    for (WasmAstSegment* segment : segments) {
+        if (!EncodeDataSegment(e, *segment))
+            return false;
+    }
+
+    e.finishSection(offset);
+    return true;
+}
+
 static UniqueBytecode
-AstToBinary(WasmAstModule& module)
+EncodeModule(WasmAstModule& module)
 {
     UniqueBytecode bytecode = MakeUnique<Bytecode>();
     if (!bytecode)
         return nullptr;
 
     Encoder e(*bytecode);
 
     if (!e.writeU32(MagicNumber))
@@ -2038,26 +2216,29 @@ AstToBinary(WasmAstModule& module)
         return nullptr;
 
     if (!EncodeExportSection(e, module))
         return nullptr;
 
     if (!EncodeCodeSection(e, module))
         return nullptr;
 
+    if (!EncodeDataSection(e, module))
+        return nullptr;
+
     if (!e.writeCString(EndSection))
         return nullptr;
 
     return Move(bytecode);
 }
 
 /*****************************************************************************/
 
 UniqueBytecode
 wasm::TextToBinary(const char16_t* text, UniqueChars* error)
 {
     LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE);
-    WasmAstModule* module = TextToAst(text, lifo, error);
+    WasmAstModule* module = ParseModule(text, lifo, error);
     if (!module)
         return nullptr;
 
-    return AstToBinary(*module);
+    return EncodeModule(*module);
 }
--- a/js/src/jit-test/tests/wasm/basic.js
+++ b/js/src/jit-test/tests/wasm/basic.js
@@ -159,16 +159,37 @@ assertEq(obj.c(), undefined);
 var obj = wasmEvalText('(module (memory 65536) (func (result i32) (i32.const 42)) (export "" memory) (export "a" 0) (export "b" 0))');
 assertEq(obj instanceof ArrayBuffer, true);
 assertEq(obj.a instanceof Function, true);
 assertEq(obj.b instanceof Function, true);
 assertEq(obj.a, obj.b);
 assertEq(obj.byteLength, 65536);
 assertEq(obj.a(), 42);
 
+var buf = wasmEvalText('(module (memory 65536 (segment 0 "")) (export "" memory))');
+assertEq(new Uint8Array(buf)[0], 0);
+
+var buf = wasmEvalText('(module (memory 65536 (segment 65536 "")) (export "" memory))');
+assertEq(new Uint8Array(buf)[0], 0);
+
+var buf = wasmEvalText('(module (memory 65536 (segment 0 "a")) (export "" memory))');
+assertEq(new Uint8Array(buf)[0], 'a'.charCodeAt(0));
+
+var buf = wasmEvalText('(module (memory 65536 (segment 0 "a") (segment 2 "b")) (export "" memory))');
+assertEq(new Uint8Array(buf)[0], 'a'.charCodeAt(0));
+assertEq(new Uint8Array(buf)[1], 0);
+assertEq(new Uint8Array(buf)[2], 'b'.charCodeAt(0));
+
+var buf = wasmEvalText('(module (memory 65536 (segment 65535 "c")) (export "" memory))');
+assertEq(new Uint8Array(buf)[0], 0);
+assertEq(new Uint8Array(buf)[65535], 'c'.charCodeAt(0));
+
+assertErrorMessage(() => wasmEvalText('(module (memory 65536 (segment 65536 "a")) (export "" memory))'), TypeError, /data segment does not fit/);
+assertErrorMessage(() => wasmEvalText('(module (memory 65536 (segment 65535 "ab")) (export "" memory))'), TypeError, /data segment does not fit/);
+
 // ----------------------------------------------------------------------------
 // locals
 
 assertEq(wasmEvalText('(module (func (param i32) (result i32) (get_local 0)) (export "" 0))')(), 0);
 assertEq(wasmEvalText('(module (func (param i32) (result i32) (get_local 0)) (export "" 0))')(42), 42);
 assertEq(wasmEvalText('(module (func (param i32) (param i32) (result i32) (get_local 0)) (export "" 0))')(42, 43), 42);
 assertEq(wasmEvalText('(module (func (param i32) (param i32) (result i32) (get_local 1)) (export "" 0))')(42, 43), 43);