diff options
Diffstat (limited to 'js/src/wasm/WasmBinaryFormat.cpp')
-rw-r--r-- | js/src/wasm/WasmBinaryFormat.cpp | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/js/src/wasm/WasmBinaryFormat.cpp b/js/src/wasm/WasmBinaryFormat.cpp new file mode 100644 index 000000000..b3ee8642a --- /dev/null +++ b/js/src/wasm/WasmBinaryFormat.cpp @@ -0,0 +1,655 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * Copyright 2016 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wasm/WasmBinaryFormat.h" + +#include "mozilla/CheckedInt.h" + +#include "jsprf.h" + +#include "jit/JitOptions.h" + +using namespace js; +using namespace js::wasm; + +using mozilla::CheckedInt; + +bool +wasm::DecodePreamble(Decoder& d) +{ + uint32_t u32; + if (!d.readFixedU32(&u32) || u32 != MagicNumber) + return d.fail("failed to match magic number"); + + if (!d.readFixedU32(&u32) || (u32 != EncodingVersion && u32 != PrevEncodingVersion)) { + return d.fail("binary version 0x%" PRIx32 " does not match expected version 0x%" PRIx32, + u32, EncodingVersion); + } + + return true; +} + +static bool +DecodeValType(Decoder& d, ModuleKind kind, ValType* type) +{ + uint8_t unchecked; + if (!d.readValType(&unchecked)) + return false; + + switch (unchecked) { + case uint8_t(ValType::I32): + case uint8_t(ValType::F32): + case uint8_t(ValType::F64): + case uint8_t(ValType::I64): + *type = ValType(unchecked); + return true; + case uint8_t(ValType::I8x16): + case uint8_t(ValType::I16x8): + case uint8_t(ValType::I32x4): + case uint8_t(ValType::F32x4): + case uint8_t(ValType::B8x16): + case uint8_t(ValType::B16x8): + case uint8_t(ValType::B32x4): + if (kind != ModuleKind::AsmJS) + return d.fail("bad type"); + *type = ValType(unchecked); + return true; + default: + break; + } + return d.fail("bad type"); +} + +bool +wasm::DecodeTypeSection(Decoder& d, SigWithIdVector* sigs) +{ + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Type, §ionStart, §ionSize, "type")) + return false; + if (sectionStart == Decoder::NotStarted) + return true; + + uint32_t numSigs; + if (!d.readVarU32(&numSigs)) + return d.fail("expected number of signatures"); + + if (numSigs > MaxSigs) + return d.fail("too many signatures"); + + if (!sigs->resize(numSigs)) + return false; + + for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) { + uint32_t form; + if (!d.readVarU32(&form) || form != uint32_t(TypeCode::Func)) + return d.fail("expected function form"); + + uint32_t numArgs; + if (!d.readVarU32(&numArgs)) + return d.fail("bad number of function args"); + + if (numArgs > MaxArgsPerFunc) + return d.fail("too many arguments in signature"); + + ValTypeVector args; + if (!args.resize(numArgs)) + return false; + + for (uint32_t i = 0; i < numArgs; i++) { + if (!DecodeValType(d, ModuleKind::Wasm, &args[i])) + return false; + } + + uint32_t numRets; + if (!d.readVarU32(&numRets)) + return d.fail("bad number of function returns"); + + if (numRets > 1) + return d.fail("too many returns in signature"); + + ExprType result = ExprType::Void; + + if (numRets == 1) { + ValType type; + if (!DecodeValType(d, ModuleKind::Wasm, &type)) + return false; + + result = ToExprType(type); + } + + (*sigs)[sigIndex] = Sig(Move(args), result); + } + + if (!d.finishSection(sectionStart, sectionSize, "type")) + return false; + + return true; +} + +UniqueChars +wasm::DecodeName(Decoder& d) +{ + uint32_t numBytes; + if (!d.readVarU32(&numBytes)) + return nullptr; + + const uint8_t* bytes; + if (!d.readBytes(numBytes, &bytes)) + return nullptr; + + UniqueChars name(js_pod_malloc<char>(numBytes + 1)); + if (!name) + return nullptr; + + memcpy(name.get(), bytes, numBytes); + name[numBytes] = '\0'; + + return name; +} + +static bool +DecodeSignatureIndex(Decoder& d, const SigWithIdVector& sigs, uint32_t* sigIndex) +{ + if (!d.readVarU32(sigIndex)) + return d.fail("expected signature index"); + + if (*sigIndex >= sigs.length()) + return d.fail("signature index out of range"); + + return true; +} + +bool +wasm::DecodeTableLimits(Decoder& d, TableDescVector* tables) +{ + uint32_t elementType; + if (!d.readVarU32(&elementType)) + return d.fail("expected table element type"); + + if (elementType != uint32_t(TypeCode::AnyFunc)) + return d.fail("expected 'anyfunc' element type"); + + Limits limits; + if (!DecodeLimits(d, &limits)) + return false; + + if (tables->length()) + return d.fail("already have default table"); + + return tables->emplaceBack(TableKind::AnyFunction, limits); +} + +bool +wasm::GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable) +{ + switch (type) { + case ValType::I32: + case ValType::F32: + case ValType::F64: + break; + case ValType::I64: + if (!jit::JitOptions.wasmTestMode) + return d.fail("can't import/export an Int64 global to JS"); + break; + default: + return d.fail("unexpected variable type in global import/export"); + } + + if (isMutable) + return d.fail("can't import/export mutable globals in the MVP"); + + return true; +} + +static bool +DecodeImport(Decoder& d, const SigWithIdVector& sigs, Uint32Vector* funcSigIndices, + GlobalDescVector* globals, TableDescVector* tables, Maybe<Limits>* memory, + ImportVector* imports) +{ + UniqueChars moduleName = DecodeName(d); + if (!moduleName) + return d.fail("expected valid import module name"); + + UniqueChars funcName = DecodeName(d); + if (!funcName) + return d.fail("expected valid import func name"); + + uint32_t rawImportKind; + if (!d.readVarU32(&rawImportKind)) + return d.fail("failed to read import kind"); + + DefinitionKind importKind = DefinitionKind(rawImportKind); + + switch (importKind) { + case DefinitionKind::Function: { + uint32_t sigIndex; + if (!DecodeSignatureIndex(d, sigs, &sigIndex)) + return false; + if (!funcSigIndices->append(sigIndex)) + return false; + break; + } + case DefinitionKind::Table: { + if (!DecodeTableLimits(d, tables)) + return false; + break; + } + case DefinitionKind::Memory: { + Limits limits; + if (!DecodeMemoryLimits(d, !!*memory, &limits)) + return false; + memory->emplace(limits); + break; + } + case DefinitionKind::Global: { + ValType type; + bool isMutable; + if (!DecodeGlobalType(d, &type, &isMutable)) + return false; + if (!GlobalIsJSCompatible(d, type, isMutable)) + return false; + if (!globals->append(GlobalDesc(type, isMutable, globals->length()))) + return false; + break; + } + default: + return d.fail("unsupported import kind"); + } + + return imports->emplaceBack(Move(moduleName), Move(funcName), importKind); +} + +bool +wasm::DecodeImportSection(Decoder& d, const SigWithIdVector& sigs, Uint32Vector* funcSigIndices, + GlobalDescVector* globals, TableDescVector* tables, Maybe<Limits>* memory, + ImportVector* imports) +{ + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Import, §ionStart, §ionSize, "import")) + return false; + if (sectionStart == Decoder::NotStarted) + return true; + + uint32_t numImports; + if (!d.readVarU32(&numImports)) + return d.fail("failed to read number of imports"); + + if (numImports > MaxImports) + return d.fail("too many imports"); + + for (uint32_t i = 0; i < numImports; i++) { + if (!DecodeImport(d, sigs, funcSigIndices, globals, tables, memory, imports)) + return false; + } + + if (!d.finishSection(sectionStart, sectionSize, "import")) + return false; + + return true; +} + +bool +wasm::DecodeFunctionSection(Decoder& d, const SigWithIdVector& sigs, size_t numImportedFunc, + Uint32Vector* funcSigIndexes) +{ + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Function, §ionStart, §ionSize, "function")) + return false; + if (sectionStart == Decoder::NotStarted) + return true; + + uint32_t numDefs; + if (!d.readVarU32(&numDefs)) + return d.fail("expected number of function definitions"); + + CheckedInt<uint32_t> numFuncs = numImportedFunc; + numFuncs += numDefs; + if (!numFuncs.isValid() || numFuncs.value() > MaxFuncs) + return d.fail("too many functions"); + + if (!funcSigIndexes->reserve(numDefs)) + return false; + + for (uint32_t i = 0; i < numDefs; i++) { + uint32_t sigIndex; + if (!DecodeSignatureIndex(d, sigs, &sigIndex)) + return false; + funcSigIndexes->infallibleAppend(sigIndex); + } + + if (!d.finishSection(sectionStart, sectionSize, "function")) + return false; + + return true; +} + +bool +wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals) +{ + uint32_t numLocalEntries = 0; + ValType prev = ValType(TypeCode::Limit); + for (ValType t : locals) { + if (t != prev) { + numLocalEntries++; + prev = t; + } + } + + if (!e.writeVarU32(numLocalEntries)) + return false; + + if (numLocalEntries) { + prev = locals[0]; + uint32_t count = 1; + for (uint32_t i = 1; i < locals.length(); i++, count++) { + if (prev != locals[i]) { + if (!e.writeVarU32(count)) + return false; + if (!e.writeValType(prev)) + return false; + prev = locals[i]; + count = 0; + } + } + if (!e.writeVarU32(count)) + return false; + if (!e.writeValType(prev)) + return false; + } + + return true; +} + +bool +wasm::DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals) +{ + uint32_t numLocalEntries; + if (!d.readVarU32(&numLocalEntries)) + return d.fail("failed to read number of local entries"); + + for (uint32_t i = 0; i < numLocalEntries; i++) { + uint32_t count; + if (!d.readVarU32(&count)) + return d.fail("failed to read local entry count"); + + if (MaxLocals - locals->length() < count) + return d.fail("too many locals"); + + ValType type; + if (!DecodeValType(d, kind, &type)) + return false; + + if (!locals->appendN(type, count)) + return false; + } + + return true; +} + +bool +wasm::DecodeGlobalType(Decoder& d, ValType* type, bool* isMutable) +{ + if (!DecodeValType(d, ModuleKind::Wasm, type)) + return false; + + uint32_t flags; + if (!d.readVarU32(&flags)) + return d.fail("expected global flags"); + + if (flags & ~uint32_t(GlobalTypeImmediate::AllowedMask)) + return d.fail("unexpected bits set in global flags"); + + *isMutable = flags & uint32_t(GlobalTypeImmediate::IsMutable); + return true; +} + +bool +wasm::DecodeInitializerExpression(Decoder& d, const GlobalDescVector& globals, ValType expected, + InitExpr* init) +{ + uint16_t op; + if (!d.readOp(&op)) + return d.fail("failed to read initializer type"); + + switch (op) { + case uint16_t(Op::I32Const): { + int32_t i32; + if (!d.readVarS32(&i32)) + return d.fail("failed to read initializer i32 expression"); + *init = InitExpr(Val(uint32_t(i32))); + break; + } + case uint16_t(Op::I64Const): { + int64_t i64; + if (!d.readVarS64(&i64)) + return d.fail("failed to read initializer i64 expression"); + *init = InitExpr(Val(uint64_t(i64))); + break; + } + case uint16_t(Op::F32Const): { + RawF32 f32; + if (!d.readFixedF32(&f32)) + return d.fail("failed to read initializer f32 expression"); + *init = InitExpr(Val(f32)); + break; + } + case uint16_t(Op::F64Const): { + RawF64 f64; + if (!d.readFixedF64(&f64)) + return d.fail("failed to read initializer f64 expression"); + *init = InitExpr(Val(f64)); + break; + } + case uint16_t(Op::GetGlobal): { + uint32_t i; + if (!d.readVarU32(&i)) + return d.fail("failed to read get_global index in initializer expression"); + if (i >= globals.length()) + return d.fail("global index out of range in initializer expression"); + if (!globals[i].isImport() || globals[i].isMutable()) + return d.fail("initializer expression must reference a global immutable import"); + *init = InitExpr(i, globals[i].type()); + break; + } + default: { + return d.fail("unexpected initializer expression"); + } + } + + if (expected != init->type()) + return d.fail("type mismatch: initializer type and expected type don't match"); + + uint16_t end; + if (!d.readOp(&end) || end != uint16_t(Op::End)) + return d.fail("failed to read end of initializer expression"); + + return true; +} + +bool +wasm::DecodeLimits(Decoder& d, Limits* limits) +{ + uint32_t flags; + if (!d.readVarU32(&flags)) + return d.fail("expected flags"); + + if (flags & ~uint32_t(0x1)) + return d.fail("unexpected bits set in flags: %" PRIu32, (flags & ~uint32_t(0x1))); + + if (!d.readVarU32(&limits->initial)) + return d.fail("expected initial length"); + + if (flags & 0x1) { + uint32_t maximum; + if (!d.readVarU32(&maximum)) + return d.fail("expected maximum length"); + + if (limits->initial > maximum) { + return d.fail("memory size minimum must not be greater than maximum; " + "maximum length %" PRIu32 " is less than initial length %" PRIu32, + maximum, limits->initial); + } + + limits->maximum.emplace(maximum); + } + + return true; +} + +bool +wasm::DecodeDataSection(Decoder& d, bool usesMemory, uint32_t minMemoryByteLength, + const GlobalDescVector& globals, DataSegmentVector* segments) +{ + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Data, §ionStart, §ionSize, "data")) + return false; + if (sectionStart == Decoder::NotStarted) + return true; + + if (!usesMemory) + return d.fail("data section requires a memory section"); + + uint32_t numSegments; + if (!d.readVarU32(&numSegments)) + return d.fail("failed to read number of data segments"); + + if (numSegments > MaxDataSegments) + return d.fail("too many data segments"); + + for (uint32_t i = 0; i < numSegments; i++) { + uint32_t linearMemoryIndex; + if (!d.readVarU32(&linearMemoryIndex)) + return d.fail("expected linear memory index"); + + if (linearMemoryIndex != 0) + return d.fail("linear memory index must currently be 0"); + + DataSegment seg; + if (!DecodeInitializerExpression(d, globals, ValType::I32, &seg.offset)) + return false; + + if (!d.readVarU32(&seg.length)) + return d.fail("expected segment size"); + + seg.bytecodeOffset = d.currentOffset(); + + if (!d.readBytes(seg.length)) + return d.fail("data segment shorter than declared"); + + if (!segments->append(seg)) + return false; + } + + if (!d.finishSection(sectionStart, sectionSize, "data")) + return false; + + return true; +} + +bool +wasm::DecodeMemoryLimits(Decoder& d, bool hasMemory, Limits* memory) +{ + if (hasMemory) + return d.fail("already have default memory"); + + if (!DecodeLimits(d, memory)) + return false; + + CheckedInt<uint32_t> initialBytes = memory->initial; + initialBytes *= PageSize; + if (!initialBytes.isValid() || initialBytes.value() > uint32_t(INT32_MAX)) + return d.fail("initial memory size too big"); + + memory->initial = initialBytes.value(); + + if (memory->maximum) { + CheckedInt<uint32_t> maximumBytes = *memory->maximum; + maximumBytes *= PageSize; + if (!maximumBytes.isValid()) + return d.fail("maximum memory size too big"); + + memory->maximum = Some(maximumBytes.value()); + } + + return true; +} + +bool +wasm::DecodeMemorySection(Decoder& d, bool hasMemory, Limits* memory, bool *present) +{ + *present = false; + + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Memory, §ionStart, §ionSize, "memory")) + return false; + if (sectionStart == Decoder::NotStarted) + return true; + + *present = true; + + uint32_t numMemories; + if (!d.readVarU32(&numMemories)) + return d.fail("failed to read number of memories"); + + if (numMemories != 1) + return d.fail("the number of memories must be exactly one"); + + if (!DecodeMemoryLimits(d, hasMemory, memory)) + return false; + + if (!d.finishSection(sectionStart, sectionSize, "memory")) + return false; + + return true; +} + +bool +wasm::DecodeUnknownSections(Decoder& d) +{ + while (!d.done()) { + if (!d.skipUserDefinedSection()) + return false; + } + + return true; +} + +bool +Decoder::fail(const char* msg, ...) +{ + va_list ap; + va_start(ap, msg); + UniqueChars str(JS_vsmprintf(msg, ap)); + va_end(ap); + if (!str) + return false; + + return fail(Move(str)); +} + +bool +Decoder::fail(UniqueChars msg) +{ + MOZ_ASSERT(error_); + UniqueChars strWithOffset(JS_smprintf("at offset %" PRIuSIZE ": %s", currentOffset(), msg.get())); + if (!strWithOffset) + return false; + + *error_ = Move(strWithOffset); + return false; +} |