Bug 1244405 - Baldr: add memory segments (r=sunfish)

This commit is contained in:
Luke Wagner 2016-02-04 21:39:18 -06:00
parent ec2e633035
commit 9f62e6aede
4 changed files with 293 additions and 20 deletions

View File

@ -30,8 +30,6 @@
using namespace js;
using namespace js::wasm;
using mozilla::PodCopy;
typedef Handle<WasmModuleObject*> HandleWasmModule;
typedef MutableHandle<WasmModuleObject*> MutableHandleWasmModule;
@ -515,7 +513,7 @@ DecodeFuncBody(JSContext* cx, Decoder& d, ModuleGenerator& mg, FunctionGenerator
if (!fg.bytecode().resize(bodyLength))
return false;
PodCopy(fg.bytecode().begin(), bodyBegin, bodyLength);
memcpy(fg.bytecode().begin(), bodyBegin, bodyLength);
return true;
}
@ -902,6 +900,56 @@ DecodeCodeSection(JSContext* cx, Decoder& d, ModuleGenerator& mg)
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)
{
@ -913,7 +961,8 @@ DecodeUnknownSection(JSContext* cx, Decoder& d)
!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");
}
@ -964,6 +1013,9 @@ DecodeModule(JSContext* cx, UniqueChars file, const uint8_t* bytes, uint32_t len
if (!DecodeCodeSection(cx, d, mg))
return false;
if (!DecodeDataSection(cx, d, heap))
return false;
CacheableCharsVector funcNames;
while (!d.readCStringIf(EndSection)) {

View File

@ -48,11 +48,13 @@ 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";
@ -439,6 +441,11 @@ class Encoder
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;
@ -485,10 +492,14 @@ class Decoder
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));
@ -497,8 +508,7 @@ class Decoder
}
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;
@ -511,7 +521,7 @@ class Decoder
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;
@ -652,6 +662,15 @@ class Decoder
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))
@ -669,7 +688,7 @@ class Decoder
uint32_t numBytes;
if (!readU32(&numBytes))
return false;
if (uintptr_t(end_ - cur_) < numBytes)
if (bytesRemain() < numBytes)
return false;
cur_ += numBytes;
return true;

View File

@ -292,15 +292,33 @@ class WasmAstExport : public WasmAstNode
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
@ -475,6 +493,7 @@ class WasmToken
OpenParen,
Param,
Result,
Segment,
SetLocal,
Text,
UnaryOpcode,
@ -602,6 +621,73 @@ 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;
@ -641,10 +727,15 @@ class WasmTokenStream
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 '$':
@ -1133,6 +1224,8 @@ class WasmTokenStream
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:
@ -1484,6 +1577,23 @@ ParseFunc(WasmParseContext& c, WasmAstModule* module)
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)
{
@ -1491,7 +1601,16 @@ ParseMemory(WasmParseContext& c)
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*
@ -1557,7 +1676,7 @@ ParseExport(WasmParseContext& c)
}
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);
@ -2010,8 +2129,67 @@ EncodeCodeSection(Encoder& e, WasmAstModule& module)
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)
@ -2043,6 +2221,9 @@ AstToBinary(WasmAstModule& module)
if (!EncodeCodeSection(e, module))
return nullptr;
if (!EncodeDataSection(e, module))
return nullptr;
if (!e.writeCString(EndSection))
return nullptr;
@ -2055,9 +2236,9 @@ 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);
}

View File

@ -164,6 +164,27 @@ 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