author | Luke Wagner <luke@mozilla.com> |
Thu, 10 Nov 2016 14:54:39 -0600 | |
changeset 322019 | 1b0a99027126df1d04c20ffc6a2338d62f205f7e |
parent 322018 | b06d68ee9270643f5295a15dd34a920ac79c9ace |
child 322020 | b10dbdd46d12c4f0d98a6e4c6a6a64adab8dfe22 |
push id | 83746 |
push user | lwagner@mozilla.com |
push date | Thu, 10 Nov 2016 20:55:07 +0000 |
treeherder | mozilla-inbound@1b0a99027126 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bbouvier |
bugs | 1316625 |
milestone | 52.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
|
--- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -12,17 +12,16 @@ #include "mozilla/MathAlgorithms.h" #include "jit/arm/Simulator-arm.h" #include "jit/Bailouts.h" #include "jit/BaselineFrame.h" #include "jit/JitFrames.h" #include "jit/MacroAssembler.h" #include "jit/MoveEmitter.h" -#include "wasm/WasmBinary.h" #include "jit/MacroAssembler-inl.h" using namespace js; using namespace jit; using mozilla::Abs; using mozilla::BitwiseCast;
--- a/js/src/jsapi-tests/testWasmLEB128.cpp +++ b/js/src/jsapi-tests/testWasmLEB128.cpp @@ -1,17 +1,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include <stdlib.h> #include "jsapi-tests/tests.h" -#include "wasm/WasmBinary.h" +#include "wasm/WasmBinaryFormat.h" static bool WriteValidBytes(js::wasm::Encoder& encoder, bool* passed) { *passed = false; if (!encoder.empty()) return true; // These remain the same under LEB128 unsigned encoding
--- a/js/src/moz.build +++ b/js/src/moz.build @@ -355,17 +355,16 @@ UNIFIED_SOURCES += [ 'vm/UbiNodeShortestPaths.cpp', 'vm/UnboxedObject.cpp', 'vm/Unicode.cpp', 'vm/Value.cpp', 'vm/WeakMapPtr.cpp', 'vm/Xdr.cpp', 'wasm/AsmJS.cpp', 'wasm/WasmBaselineCompile.cpp', - 'wasm/WasmBinary.cpp', 'wasm/WasmBinaryFormat.cpp', 'wasm/WasmBinaryIterator.cpp', 'wasm/WasmBinaryToAST.cpp', 'wasm/WasmBinaryToExperimentalText.cpp', 'wasm/WasmBinaryToText.cpp', 'wasm/WasmCode.cpp', 'wasm/WasmCompartment.cpp', 'wasm/WasmCompile.cpp',
--- a/js/src/wasm/WasmBaselineCompile.h +++ b/js/src/wasm/WasmBaselineCompile.h @@ -14,17 +14,16 @@ * 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. */ #ifndef asmjs_wasm_baseline_compile_h #define asmjs_wasm_baseline_compile_h -#include "wasm/WasmBinary.h" #include "wasm/WasmIonCompile.h" namespace js { namespace wasm { class FunctionGenerator; // Return true if BaselineCompileFunction can generate code for the
deleted file mode 100644 --- a/js/src/wasm/WasmBinary.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* -*- 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/WasmBinary.h" - -#include <stdarg.h> - -#include "jsprf.h" -#include "wasm/WasmTypes.h" - -using namespace js; -using namespace js::wasm; - -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; -}
rename from js/src/wasm/WasmBinary.h rename to js/src/wasm/WasmBinaryConstants.h --- a/js/src/wasm/WasmBinary.h +++ b/js/src/wasm/WasmBinaryConstants.h @@ -19,75 +19,32 @@ #ifndef wasm_binary_h #define wasm_binary_h #include "builtin/SIMD.h" namespace js { namespace wasm { -// Because WebAssembly allows one to define the payload of a NaN value, -// including the signal/quiet bit (highest order bit of payload), another -// represenation of floating-point values is required: on some platforms (x86 -// without SSE2), passing a floating-point argument to a function call may use -// the x87 stack, which has the side-effect of clearing the signal/quiet bit. -// Because the signal/quiet bit must be preserved (by spec), we use the raw -// punned integer representation of floating points instead, in function calls. -// -// When we leave the WebAssembly sandbox back to JS, NaNs are canonicalized, so -// this isn't observable from JS. - -template<class T> -class Raw -{ - typedef typename mozilla::FloatingPoint<T>::Bits Bits; - Bits value_; - - public: - Raw() : value_(0) {} - - explicit Raw(T value) - : value_(mozilla::BitwiseCast<Bits>(value)) - {} - - template<class U> MOZ_IMPLICIT Raw(U) = delete; - - static Raw fromBits(Bits bits) { Raw r; r.value_ = bits; return r; } - - Bits bits() const { return value_; } - T fp() const { return mozilla::BitwiseCast<T>(value_); } -}; - -using RawF64 = Raw<double>; -using RawF32 = Raw<float>; - -// Telemetry sample values for the JS_AOT_USAGE key, indicating whether asm.js -// or WebAssembly is used. - -enum class Telemetry { - ASMJS = 0, - WASM = 1 -}; - static const uint32_t MagicNumber = 0x6d736100; // "\0asm" static const uint32_t EncodingVersion = 0x0d; enum class SectionId { - UserDefined = 0, - Type = 1, - Import = 2, - Function = 3, - Table = 4, - Memory = 5, - Global = 6, - Export = 7, - Start = 8, - Elem = 9, - Code = 10, - Data = 11 + UserDefined = 0, + Type = 1, + Import = 2, + Function = 3, + Table = 4, + Memory = 5, + Global = 6, + Export = 7, + Start = 8, + Elem = 9, + Code = 10, + Data = 11 }; static const char NameSectionName[] = "name"; enum class TypeCode { I32 = 0x7f, // SLEB128(-0x01) I64 = 0x7e, // SLEB128(-0x02) @@ -470,650 +427,21 @@ enum class Expr : uint32_t // fix type s B32x4Constructor, B32x4Const, #undef _ #undef OPCODE Limit }; -// The ExprType enum represents the type of a WebAssembly expression or return -// value and may either be a value type or void. Soon, expression types will be -// generalized to a list of ValType and this enum will go away, replaced, -// wherever it is used, by a varU32 + list of ValType. - -enum class ExprType : uint32_t // fix type so we can cast from any u8 in decoder -{ - Void = uint8_t(TypeCode::BlockVoid), - - I32 = uint8_t(TypeCode::I32), - I64 = uint8_t(TypeCode::I64), - F32 = uint8_t(TypeCode::F32), - F64 = uint8_t(TypeCode::F64), - - I8x16 = uint8_t(TypeCode::I8x16), - I16x8 = uint8_t(TypeCode::I16x8), - I32x4 = uint8_t(TypeCode::I32x4), - F32x4 = uint8_t(TypeCode::F32x4), - B8x16 = uint8_t(TypeCode::B8x16), - B16x8 = uint8_t(TypeCode::B16x8), - B32x4 = uint8_t(TypeCode::B32x4), - - Limit = uint8_t(TypeCode::Limit) -}; - -typedef int8_t I8x16[16]; -typedef int16_t I16x8[8]; -typedef int32_t I32x4[4]; -typedef float F32x4[4]; -typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes; - -// The Encoder class appends bytes to the Bytes object it is given during -// construction. The client is responsible for the Bytes's lifetime and must -// keep the Bytes alive as long as the Encoder is used. - -class Encoder -{ - Bytes& bytes_; - - template <class T> - MOZ_MUST_USE bool write(const T& v) { - return bytes_.append(reinterpret_cast<const uint8_t*>(&v), sizeof(T)); - } - - template <typename UInt> - MOZ_MUST_USE bool writeVarU(UInt i) { - do { - uint8_t byte = i & 0x7f; - i >>= 7; - if (i != 0) - byte |= 0x80; - if (!bytes_.append(byte)) - return false; - } while (i != 0); - return true; - } - - template <typename SInt> - MOZ_MUST_USE bool writeVarS(SInt i) { - bool done; - do { - uint8_t byte = i & 0x7f; - i >>= 7; - done = ((i == 0) && !(byte & 0x40)) || ((i == -1) && (byte & 0x40)); - if (!done) - byte |= 0x80; - if (!bytes_.append(byte)) - return false; - } while (!done); - return true; - } - - void patchVarU32(size_t offset, uint32_t patchBits, uint32_t assertBits) { - do { - uint8_t assertByte = assertBits & 0x7f; - uint8_t patchByte = patchBits & 0x7f; - assertBits >>= 7; - patchBits >>= 7; - if (assertBits != 0) { - assertByte |= 0x80; - patchByte |= 0x80; - } - MOZ_ASSERT(assertByte == bytes_[offset]); - bytes_[offset] = patchByte; - offset++; - } while(assertBits != 0); - } - - void patchFixedU7(size_t offset, uint8_t patchBits, uint8_t assertBits) { - MOZ_ASSERT(patchBits <= uint8_t(INT8_MAX)); - patchFixedU8(offset, patchBits, assertBits); - } - - void patchFixedU8(size_t offset, uint8_t patchBits, uint8_t assertBits) { - MOZ_ASSERT(bytes_[offset] == assertBits); - bytes_[offset] = patchBits; - } - - uint32_t varU32ByteLength(size_t offset) const { - size_t start = offset; - while (bytes_[offset] & 0x80) - offset++; - return offset - start + 1; - } - - static const size_t ExprLimit = 2 * UINT8_MAX - 1; - - public: - explicit Encoder(Bytes& bytes) - : bytes_(bytes) - { - MOZ_ASSERT(empty()); - } - - size_t currentOffset() const { return bytes_.length(); } - bool empty() const { return currentOffset() == 0; } - - // Fixed-size encoding operations simply copy the literal bytes (without - // attempting to align). - - MOZ_MUST_USE bool writeFixedU7(uint8_t i) { - MOZ_ASSERT(i <= uint8_t(INT8_MAX)); - return writeFixedU8(i); - } - MOZ_MUST_USE bool writeFixedU8(uint8_t i) { - return write<uint8_t>(i); - } - MOZ_MUST_USE bool writeFixedU32(uint32_t i) { - return write<uint32_t>(i); - } - MOZ_MUST_USE bool writeFixedF32(RawF32 f) { - return write<uint32_t>(f.bits()); - } - MOZ_MUST_USE bool writeFixedF64(RawF64 d) { - return write<uint64_t>(d.bits()); - } - MOZ_MUST_USE bool writeFixedI8x16(const I8x16& i8x16) { - return write<I8x16>(i8x16); - } - MOZ_MUST_USE bool writeFixedI16x8(const I16x8& i16x8) { - return write<I16x8>(i16x8); - } - MOZ_MUST_USE bool writeFixedI32x4(const I32x4& i32x4) { - return write<I32x4>(i32x4); - } - MOZ_MUST_USE bool writeFixedF32x4(const F32x4& f32x4) { - return write<F32x4>(f32x4); - } - - // Variable-length encodings that all use LEB128. - - MOZ_MUST_USE bool writeVarU32(uint32_t i) { - return writeVarU<uint32_t>(i); - } - MOZ_MUST_USE bool writeVarS32(int32_t i) { - return writeVarS<int32_t>(i); - } - MOZ_MUST_USE bool writeVarU64(uint64_t i) { - return writeVarU<uint64_t>(i); - } - MOZ_MUST_USE bool writeVarS64(int64_t i) { - return writeVarS<int64_t>(i); - } - MOZ_MUST_USE bool writeValType(ValType type) { - static_assert(size_t(TypeCode::Max) <= INT8_MAX, "fits"); - MOZ_ASSERT(size_t(type) <= size_t(TypeCode::Max)); - return writeFixedU8(uint8_t(type)); - } - MOZ_MUST_USE bool writeBlockType(ExprType type) { - static_assert(size_t(TypeCode::Max) <= INT8_MAX, "fits"); - MOZ_ASSERT(size_t(type) <= size_t(TypeCode::Max)); - return writeFixedU8(uint8_t(type)); - } - MOZ_MUST_USE bool writeExpr(Expr expr) { - static_assert(size_t(Expr::Limit) <= ExprLimit, "fits"); - if (size_t(expr) < UINT8_MAX) - return writeFixedU8(uint8_t(expr)); - return writeFixedU8(UINT8_MAX) && - writeFixedU8(size_t(expr) - UINT8_MAX); - } - - // Fixed-length encodings that allow back-patching. - - MOZ_MUST_USE bool writePatchableFixedU7(size_t* offset) { - *offset = bytes_.length(); - return writeFixedU8(UINT8_MAX); - } - void patchFixedU7(size_t offset, uint8_t patchBits) { - return patchFixedU7(offset, patchBits, UINT8_MAX); - } - - // Variable-length encodings that allow back-patching. - - MOZ_MUST_USE bool writePatchableVarU32(size_t* offset) { - *offset = bytes_.length(); - return writeVarU32(UINT32_MAX); - } - void patchVarU32(size_t offset, uint32_t patchBits) { - return patchVarU32(offset, patchBits, UINT32_MAX); - } - - // Byte ranges start with an LEB128 length followed by an arbitrary sequence - // of bytes. When used for strings, bytes are to be interpreted as utf8. - - MOZ_MUST_USE bool writeBytes(const void* bytes, uint32_t numBytes) { - return writeVarU32(numBytes) && - bytes_.append(reinterpret_cast<const uint8_t*>(bytes), numBytes); - } - - // A "section" is a contiguous range of bytes that stores its own size so - // that it may be trivially skipped without examining the contents. Sections - // require backpatching since the size of the section is only known at the - // end while the size's varU32 must be stored at the beginning. Immediately - // after the section length is the string id of the section. - - MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) { - MOZ_ASSERT(id != SectionId::UserDefined); // not supported yet - - return writeVarU32(uint32_t(id)) && - writePatchableVarU32(offset); - } - void finishSection(size_t offset) { - return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset)); - } -}; - -// The Decoder class decodes the bytes in the range it is given during -// construction. The client is responsible for keeping the byte range alive as -// long as the Decoder is used. - -class Decoder -{ - const uint8_t* const beg_; - const uint8_t* const end_; - const uint8_t* cur_; - UniqueChars* error_; - - template <class T> - MOZ_MUST_USE bool read(T* out) { - if (bytesRemain() < sizeof(T)) - return false; - memcpy((void*)out, cur_, sizeof(T)); - cur_ += sizeof(T); - return true; - } - - template <class T> - T uncheckedRead() { - MOZ_ASSERT(bytesRemain() >= sizeof(T)); - T ret; - memcpy(&ret, cur_, sizeof(T)); - cur_ += sizeof(T); - return ret; - } - - template <class T> - void uncheckedRead(T* ret) { - MOZ_ASSERT(bytesRemain() >= sizeof(T)); - memcpy(ret, cur_, sizeof(T)); - cur_ += sizeof(T); - } - - template <typename UInt> - MOZ_MUST_USE bool readVarU(UInt* out) { - const unsigned numBits = sizeof(UInt) * CHAR_BIT; - const unsigned remainderBits = numBits % 7; - const unsigned numBitsInSevens = numBits - remainderBits; - UInt u = 0; - uint8_t byte; - UInt shift = 0; - do { - if (!readFixedU8(&byte)) - return false; - if (!(byte & 0x80)) { - *out = u | UInt(byte) << shift; - return true; - } - u |= UInt(byte & 0x7F) << shift; - shift += 7; - } while (shift != numBitsInSevens); - if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits))) - return false; - *out = u | (UInt(byte) << numBitsInSevens); - return true; - } - - template <typename SInt> - MOZ_MUST_USE bool readVarS(SInt* out) { - const unsigned numBits = sizeof(SInt) * CHAR_BIT; - const unsigned remainderBits = numBits % 7; - const unsigned numBitsInSevens = numBits - remainderBits; - SInt s = 0; - uint8_t byte; - unsigned shift = 0; - do { - if (!readFixedU8(&byte)) - return false; - s |= SInt(byte & 0x7f) << shift; - shift += 7; - if (!(byte & 0x80)) { - if (byte & 0x40) - s |= SInt(-1) << shift; - *out = s; - return true; - } - } while (shift < numBitsInSevens); - if (!remainderBits || !readFixedU8(&byte) || (byte & 0x80)) - return false; - uint8_t mask = 0x7f & (uint8_t(-1) << remainderBits); - if ((byte & mask) != ((byte & (1 << (remainderBits - 1))) ? mask : 0)) - return false; - *out = s | SInt(byte) << shift; - return true; - } - - static const size_t ExprLimit = 2 * UINT8_MAX - 1; +// Telemetry sample values for the JS_AOT_USAGE key, indicating whether asm.js +// or WebAssembly is used. - public: - Decoder(const uint8_t* begin, const uint8_t* end, UniqueChars* error) - : beg_(begin), - end_(end), - cur_(begin), - error_(error) - { - MOZ_ASSERT(begin <= end); - } - explicit Decoder(const Bytes& bytes, UniqueChars* error = nullptr) - : beg_(bytes.begin()), - end_(bytes.end()), - cur_(bytes.begin()), - error_(error) - {} - - bool fail(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3); - bool fail(UniqueChars msg); - void clearError() { - if (error_) - error_->reset(); - } - - bool done() const { - MOZ_ASSERT(cur_ <= end_); - return cur_ == end_; - } - - size_t bytesRemain() const { - MOZ_ASSERT(end_ >= cur_); - return size_t(end_ - cur_); - } - const uint8_t* currentPosition() const { - return cur_; - } - size_t currentOffset() const { - return cur_ - beg_; - } - const uint8_t* begin() const { - return beg_; - } - - // Fixed-size encoding operations simply copy the literal bytes (without - // attempting to align). - - MOZ_MUST_USE bool readFixedU8(uint8_t* i) { - return read<uint8_t>(i); - } - MOZ_MUST_USE bool readFixedU32(uint32_t* u) { - return read<uint32_t>(u); - } - MOZ_MUST_USE bool readFixedF32(RawF32* f) { - uint32_t u; - if (!read<uint32_t>(&u)) - return false; - *f = RawF32::fromBits(u); - return true; - } - MOZ_MUST_USE bool readFixedF64(RawF64* d) { - uint64_t u; - if (!read<uint64_t>(&u)) - return false; - *d = RawF64::fromBits(u); - return true; - } - MOZ_MUST_USE bool readFixedI8x16(I8x16* i8x16) { - return read<I8x16>(i8x16); - } - MOZ_MUST_USE bool readFixedI16x8(I16x8* i16x8) { - return read<I16x8>(i16x8); - } - MOZ_MUST_USE bool readFixedI32x4(I32x4* i32x4) { - return read<I32x4>(i32x4); - } - MOZ_MUST_USE bool readFixedF32x4(F32x4* f32x4) { - return read<F32x4>(f32x4); - } - - // Variable-length encodings that all use LEB128. - - MOZ_MUST_USE bool readVarU32(uint32_t* out) { - return readVarU<uint32_t>(out); - } - MOZ_MUST_USE bool readVarS32(int32_t* out) { - return readVarS<int32_t>(out); - } - MOZ_MUST_USE bool readVarU64(uint64_t* out) { - return readVarU<uint64_t>(out); - } - MOZ_MUST_USE bool readVarS64(int64_t* out) { - return readVarS<int64_t>(out); - } - MOZ_MUST_USE bool readValType(ValType* type) { - static_assert(uint8_t(TypeCode::Max) <= INT8_MAX, "fits"); - uint8_t u8; - if (!readFixedU8(&u8)) - return false; - *type = (ValType)u8; - return true; - } - MOZ_MUST_USE bool readBlockType(ExprType* type) { - static_assert(size_t(TypeCode::Max) <= INT8_MAX, "fits"); - uint8_t u8; - if (!readFixedU8(&u8)) - return false; - *type = (ExprType)u8; - return true; - } - MOZ_MUST_USE bool readExpr(Expr* expr) { - static_assert(size_t(Expr::Limit) <= ExprLimit, "fits"); - uint8_t u8; - if (!readFixedU8(&u8)) - return false; - if (u8 != UINT8_MAX) { - *expr = Expr(u8); - return true; - } - if (!readFixedU8(&u8)) - return false; - if (u8 == UINT8_MAX) - return false; - *expr = Expr(uint16_t(u8) + UINT8_MAX); - return true; - } - - // See writeBytes comment. - - MOZ_MUST_USE bool readBytes(uint32_t numBytes, const uint8_t** bytes = nullptr) { - if (bytes) - *bytes = cur_; - if (bytesRemain() < numBytes) - return false; - cur_ += numBytes; - return true; - } - - // See "section" description in Encoder. - - static const uint32_t NotStarted = UINT32_MAX; - - MOZ_MUST_USE bool startSection(SectionId id, - uint32_t* startOffset, - uint32_t* size, - const char* sectionName) - { - const uint8_t* const before = cur_; - const uint8_t* beforeId = before; - uint32_t idValue; - if (!readVarU32(&idValue)) - goto backup; - while (idValue != uint32_t(id)) { - if (idValue != uint32_t(SectionId::UserDefined)) - goto backup; - // Rewind to the section id since skipUserDefinedSection expects it. - cur_ = beforeId; - if (!skipUserDefinedSection()) - return false; - beforeId = cur_; - if (!readVarU32(&idValue)) - goto backup; - } - if (!readVarU32(size)) - goto fail; - if (bytesRemain() < *size) - goto fail; - *startOffset = cur_ - beg_; - return true; - backup: - cur_ = before; - *startOffset = NotStarted; - return true; - fail: - return fail("failed to start %s section", sectionName); - } - MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size, - const char* sectionName) - { - if (size != (cur_ - beg_) - startOffset) - return fail("byte size mismatch in %s section", sectionName); - return true; - } - - // "User sections" do not cause validation errors unless the error is in - // the user-defined section header itself. - - MOZ_MUST_USE bool startUserDefinedSection(const char* expectedId, - size_t expectedIdSize, - uint32_t* sectionStart, - uint32_t* sectionSize) - { - const uint8_t* const before = cur_; - while (true) { - if (!startSection(SectionId::UserDefined, sectionStart, sectionSize, "user-defined")) - return false; - if (*sectionStart == NotStarted) { - cur_ = before; - return true; - } - uint32_t idSize; - if (!readVarU32(&idSize)) - goto fail; - if (idSize > bytesRemain() || currentOffset() + idSize > *sectionStart + *sectionSize) - goto fail; - if (expectedId && (expectedIdSize != idSize || !!memcmp(cur_, expectedId, idSize))) { - finishUserDefinedSection(*sectionStart, *sectionSize); - continue; - } - cur_ += idSize; - return true; - } - MOZ_CRASH("unreachable"); - fail: - return fail("failed to start user-defined section"); - } - template <size_t IdSizeWith0> - MOZ_MUST_USE bool startUserDefinedSection(const char (&id)[IdSizeWith0], - uint32_t* sectionStart, - uint32_t* sectionSize) - { - MOZ_ASSERT(id[IdSizeWith0 - 1] == '\0'); - return startUserDefinedSection(id, IdSizeWith0 - 1, sectionStart, sectionSize); - } - void finishUserDefinedSection(uint32_t sectionStart, uint32_t sectionSize) { - MOZ_ASSERT(cur_ >= beg_); - MOZ_ASSERT(cur_ <= end_); - cur_ = (beg_ + sectionStart) + sectionSize; - MOZ_ASSERT(cur_ <= end_); - clearError(); - } - MOZ_MUST_USE bool skipUserDefinedSection() { - uint32_t sectionStart, sectionSize; - if (!startUserDefinedSection(nullptr, 0, §ionStart, §ionSize)) - return false; - if (sectionStart == NotStarted) - return fail("expected user-defined section"); - finishUserDefinedSection(sectionStart, sectionSize); - return true; - } - - // The infallible "unchecked" decoding functions can be used when we are - // sure that the bytes are well-formed (by construction or due to previous - // validation). - - uint8_t uncheckedReadFixedU8() { - return uncheckedRead<uint8_t>(); - } - uint32_t uncheckedReadFixedU32() { - return uncheckedRead<uint32_t>(); - } - RawF32 uncheckedReadFixedF32() { - return RawF32::fromBits(uncheckedRead<uint32_t>()); - } - RawF64 uncheckedReadFixedF64() { - return RawF64::fromBits(uncheckedRead<uint64_t>()); - } - template <typename UInt> - UInt uncheckedReadVarU() { - static const unsigned numBits = sizeof(UInt) * CHAR_BIT; - static const unsigned remainderBits = numBits % 7; - static const unsigned numBitsInSevens = numBits - remainderBits; - UInt decoded = 0; - uint32_t shift = 0; - do { - uint8_t byte = *cur_++; - if (!(byte & 0x80)) - return decoded | (UInt(byte) << shift); - decoded |= UInt(byte & 0x7f) << shift; - shift += 7; - } while (shift != numBitsInSevens); - uint8_t byte = *cur_++; - MOZ_ASSERT(!(byte & 0xf0)); - return decoded | (UInt(byte) << numBitsInSevens); - } - uint32_t uncheckedReadVarU32() { - return uncheckedReadVarU<uint32_t>(); - } - int32_t uncheckedReadVarS32() { - int32_t i32 = 0; - MOZ_ALWAYS_TRUE(readVarS32(&i32)); - return i32; - } - uint64_t uncheckedReadVarU64() { - return uncheckedReadVarU<uint64_t>(); - } - int64_t uncheckedReadVarS64() { - int64_t i64 = 0; - MOZ_ALWAYS_TRUE(readVarS64(&i64)); - return i64; - } - ValType uncheckedReadValType() { - return (ValType)uncheckedReadFixedU8(); - } - Expr uncheckedReadExpr() { - static_assert(size_t(Expr::Limit) <= ExprLimit, "fits"); - uint8_t u8 = uncheckedReadFixedU8(); - return u8 != UINT8_MAX - ? Expr(u8) - : Expr(uncheckedReadFixedU8() + UINT8_MAX); - } - void uncheckedReadFixedI8x16(I8x16* i8x16) { - struct T { I8x16 v; }; - T t = uncheckedRead<T>(); - memcpy(i8x16, &t, sizeof(t)); - } - void uncheckedReadFixedI16x8(I16x8* i16x8) { - struct T { I16x8 v; }; - T t = uncheckedRead<T>(); - memcpy(i16x8, &t, sizeof(t)); - } - void uncheckedReadFixedI32x4(I32x4* i32x4) { - struct T { I32x4 v; }; - T t = uncheckedRead<T>(); - memcpy(i32x4, &t, sizeof(t)); - } - void uncheckedReadFixedF32x4(F32x4* f32x4) { - struct T { F32x4 v; }; - T t = uncheckedRead<T>(); - memcpy(f32x4, &t, sizeof(t)); - } +enum class Telemetry +{ + ASMJS = 0, + WASM = 1 }; } // namespace wasm } // namespace js #endif // wasm_binary_h
--- a/js/src/wasm/WasmBinaryFormat.cpp +++ b/js/src/wasm/WasmBinaryFormat.cpp @@ -326,8 +326,33 @@ 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; +}
--- a/js/src/wasm/WasmBinaryFormat.h +++ b/js/src/wasm/WasmBinaryFormat.h @@ -14,22 +14,628 @@ * 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. */ #ifndef wasm_binary_format_h #define wasm_binary_format_h -#include "wasm/WasmBinary.h" #include "wasm/WasmTypes.h" namespace js { namespace wasm { +// The Encoder class appends bytes to the Bytes object it is given during +// construction. The client is responsible for the Bytes's lifetime and must +// keep the Bytes alive as long as the Encoder is used. + +class Encoder +{ + Bytes& bytes_; + + template <class T> + MOZ_MUST_USE bool write(const T& v) { + return bytes_.append(reinterpret_cast<const uint8_t*>(&v), sizeof(T)); + } + + template <typename UInt> + MOZ_MUST_USE bool writeVarU(UInt i) { + do { + uint8_t byte = i & 0x7f; + i >>= 7; + if (i != 0) + byte |= 0x80; + if (!bytes_.append(byte)) + return false; + } while (i != 0); + return true; + } + + template <typename SInt> + MOZ_MUST_USE bool writeVarS(SInt i) { + bool done; + do { + uint8_t byte = i & 0x7f; + i >>= 7; + done = ((i == 0) && !(byte & 0x40)) || ((i == -1) && (byte & 0x40)); + if (!done) + byte |= 0x80; + if (!bytes_.append(byte)) + return false; + } while (!done); + return true; + } + + void patchVarU32(size_t offset, uint32_t patchBits, uint32_t assertBits) { + do { + uint8_t assertByte = assertBits & 0x7f; + uint8_t patchByte = patchBits & 0x7f; + assertBits >>= 7; + patchBits >>= 7; + if (assertBits != 0) { + assertByte |= 0x80; + patchByte |= 0x80; + } + MOZ_ASSERT(assertByte == bytes_[offset]); + bytes_[offset] = patchByte; + offset++; + } while(assertBits != 0); + } + + void patchFixedU7(size_t offset, uint8_t patchBits, uint8_t assertBits) { + MOZ_ASSERT(patchBits <= uint8_t(INT8_MAX)); + patchFixedU8(offset, patchBits, assertBits); + } + + void patchFixedU8(size_t offset, uint8_t patchBits, uint8_t assertBits) { + MOZ_ASSERT(bytes_[offset] == assertBits); + bytes_[offset] = patchBits; + } + + uint32_t varU32ByteLength(size_t offset) const { + size_t start = offset; + while (bytes_[offset] & 0x80) + offset++; + return offset - start + 1; + } + + static const size_t ExprLimit = 2 * UINT8_MAX - 1; + + public: + explicit Encoder(Bytes& bytes) + : bytes_(bytes) + { + MOZ_ASSERT(empty()); + } + + size_t currentOffset() const { return bytes_.length(); } + bool empty() const { return currentOffset() == 0; } + + // Fixed-size encoding operations simply copy the literal bytes (without + // attempting to align). + + MOZ_MUST_USE bool writeFixedU7(uint8_t i) { + MOZ_ASSERT(i <= uint8_t(INT8_MAX)); + return writeFixedU8(i); + } + MOZ_MUST_USE bool writeFixedU8(uint8_t i) { + return write<uint8_t>(i); + } + MOZ_MUST_USE bool writeFixedU32(uint32_t i) { + return write<uint32_t>(i); + } + MOZ_MUST_USE bool writeFixedF32(RawF32 f) { + return write<uint32_t>(f.bits()); + } + MOZ_MUST_USE bool writeFixedF64(RawF64 d) { + return write<uint64_t>(d.bits()); + } + MOZ_MUST_USE bool writeFixedI8x16(const I8x16& i8x16) { + return write<I8x16>(i8x16); + } + MOZ_MUST_USE bool writeFixedI16x8(const I16x8& i16x8) { + return write<I16x8>(i16x8); + } + MOZ_MUST_USE bool writeFixedI32x4(const I32x4& i32x4) { + return write<I32x4>(i32x4); + } + MOZ_MUST_USE bool writeFixedF32x4(const F32x4& f32x4) { + return write<F32x4>(f32x4); + } + + // Variable-length encodings that all use LEB128. + + MOZ_MUST_USE bool writeVarU32(uint32_t i) { + return writeVarU<uint32_t>(i); + } + MOZ_MUST_USE bool writeVarS32(int32_t i) { + return writeVarS<int32_t>(i); + } + MOZ_MUST_USE bool writeVarU64(uint64_t i) { + return writeVarU<uint64_t>(i); + } + MOZ_MUST_USE bool writeVarS64(int64_t i) { + return writeVarS<int64_t>(i); + } + MOZ_MUST_USE bool writeValType(ValType type) { + static_assert(size_t(TypeCode::Max) <= INT8_MAX, "fits"); + MOZ_ASSERT(size_t(type) <= size_t(TypeCode::Max)); + return writeFixedU8(uint8_t(type)); + } + MOZ_MUST_USE bool writeBlockType(ExprType type) { + static_assert(size_t(TypeCode::Max) <= INT8_MAX, "fits"); + MOZ_ASSERT(size_t(type) <= size_t(TypeCode::Max)); + return writeFixedU8(uint8_t(type)); + } + MOZ_MUST_USE bool writeExpr(Expr expr) { + static_assert(size_t(Expr::Limit) <= ExprLimit, "fits"); + if (size_t(expr) < UINT8_MAX) + return writeFixedU8(uint8_t(expr)); + return writeFixedU8(UINT8_MAX) && + writeFixedU8(size_t(expr) - UINT8_MAX); + } + + // Fixed-length encodings that allow back-patching. + + MOZ_MUST_USE bool writePatchableFixedU7(size_t* offset) { + *offset = bytes_.length(); + return writeFixedU8(UINT8_MAX); + } + void patchFixedU7(size_t offset, uint8_t patchBits) { + return patchFixedU7(offset, patchBits, UINT8_MAX); + } + + // Variable-length encodings that allow back-patching. + + MOZ_MUST_USE bool writePatchableVarU32(size_t* offset) { + *offset = bytes_.length(); + return writeVarU32(UINT32_MAX); + } + void patchVarU32(size_t offset, uint32_t patchBits) { + return patchVarU32(offset, patchBits, UINT32_MAX); + } + + // Byte ranges start with an LEB128 length followed by an arbitrary sequence + // of bytes. When used for strings, bytes are to be interpreted as utf8. + + MOZ_MUST_USE bool writeBytes(const void* bytes, uint32_t numBytes) { + return writeVarU32(numBytes) && + bytes_.append(reinterpret_cast<const uint8_t*>(bytes), numBytes); + } + + // A "section" is a contiguous range of bytes that stores its own size so + // that it may be trivially skipped without examining the contents. Sections + // require backpatching since the size of the section is only known at the + // end while the size's varU32 must be stored at the beginning. Immediately + // after the section length is the string id of the section. + + MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) { + MOZ_ASSERT(id != SectionId::UserDefined); // not supported yet + + return writeVarU32(uint32_t(id)) && + writePatchableVarU32(offset); + } + void finishSection(size_t offset) { + return patchVarU32(offset, bytes_.length() - offset - varU32ByteLength(offset)); + } +}; + +// The Decoder class decodes the bytes in the range it is given during +// construction. The client is responsible for keeping the byte range alive as +// long as the Decoder is used. + +class Decoder +{ + const uint8_t* const beg_; + const uint8_t* const end_; + const uint8_t* cur_; + UniqueChars* error_; + + template <class T> + MOZ_MUST_USE bool read(T* out) { + if (bytesRemain() < sizeof(T)) + return false; + memcpy((void*)out, cur_, sizeof(T)); + cur_ += sizeof(T); + return true; + } + + template <class T> + T uncheckedRead() { + MOZ_ASSERT(bytesRemain() >= sizeof(T)); + T ret; + memcpy(&ret, cur_, sizeof(T)); + cur_ += sizeof(T); + return ret; + } + + template <class T> + void uncheckedRead(T* ret) { + MOZ_ASSERT(bytesRemain() >= sizeof(T)); + memcpy(ret, cur_, sizeof(T)); + cur_ += sizeof(T); + } + + template <typename UInt> + MOZ_MUST_USE bool readVarU(UInt* out) { + const unsigned numBits = sizeof(UInt) * CHAR_BIT; + const unsigned remainderBits = numBits % 7; + const unsigned numBitsInSevens = numBits - remainderBits; + UInt u = 0; + uint8_t byte; + UInt shift = 0; + do { + if (!readFixedU8(&byte)) + return false; + if (!(byte & 0x80)) { + *out = u | UInt(byte) << shift; + return true; + } + u |= UInt(byte & 0x7F) << shift; + shift += 7; + } while (shift != numBitsInSevens); + if (!readFixedU8(&byte) || (byte & (unsigned(-1) << remainderBits))) + return false; + *out = u | (UInt(byte) << numBitsInSevens); + return true; + } + + template <typename SInt> + MOZ_MUST_USE bool readVarS(SInt* out) { + const unsigned numBits = sizeof(SInt) * CHAR_BIT; + const unsigned remainderBits = numBits % 7; + const unsigned numBitsInSevens = numBits - remainderBits; + SInt s = 0; + uint8_t byte; + unsigned shift = 0; + do { + if (!readFixedU8(&byte)) + return false; + s |= SInt(byte & 0x7f) << shift; + shift += 7; + if (!(byte & 0x80)) { + if (byte & 0x40) + s |= SInt(-1) << shift; + *out = s; + return true; + } + } while (shift < numBitsInSevens); + if (!remainderBits || !readFixedU8(&byte) || (byte & 0x80)) + return false; + uint8_t mask = 0x7f & (uint8_t(-1) << remainderBits); + if ((byte & mask) != ((byte & (1 << (remainderBits - 1))) ? mask : 0)) + return false; + *out = s | SInt(byte) << shift; + return true; + } + + static const size_t ExprLimit = 2 * UINT8_MAX - 1; + + public: + Decoder(const uint8_t* begin, const uint8_t* end, UniqueChars* error) + : beg_(begin), + end_(end), + cur_(begin), + error_(error) + { + MOZ_ASSERT(begin <= end); + } + explicit Decoder(const Bytes& bytes, UniqueChars* error = nullptr) + : beg_(bytes.begin()), + end_(bytes.end()), + cur_(bytes.begin()), + error_(error) + {} + + bool fail(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3); + bool fail(UniqueChars msg); + void clearError() { + if (error_) + error_->reset(); + } + + bool done() const { + MOZ_ASSERT(cur_ <= end_); + return cur_ == end_; + } + + size_t bytesRemain() const { + MOZ_ASSERT(end_ >= cur_); + return size_t(end_ - cur_); + } + const uint8_t* currentPosition() const { + return cur_; + } + size_t currentOffset() const { + return cur_ - beg_; + } + const uint8_t* begin() const { + return beg_; + } + + // Fixed-size encoding operations simply copy the literal bytes (without + // attempting to align). + + MOZ_MUST_USE bool readFixedU8(uint8_t* i) { + return read<uint8_t>(i); + } + MOZ_MUST_USE bool readFixedU32(uint32_t* u) { + return read<uint32_t>(u); + } + MOZ_MUST_USE bool readFixedF32(RawF32* f) { + uint32_t u; + if (!read<uint32_t>(&u)) + return false; + *f = RawF32::fromBits(u); + return true; + } + MOZ_MUST_USE bool readFixedF64(RawF64* d) { + uint64_t u; + if (!read<uint64_t>(&u)) + return false; + *d = RawF64::fromBits(u); + return true; + } + MOZ_MUST_USE bool readFixedI8x16(I8x16* i8x16) { + return read<I8x16>(i8x16); + } + MOZ_MUST_USE bool readFixedI16x8(I16x8* i16x8) { + return read<I16x8>(i16x8); + } + MOZ_MUST_USE bool readFixedI32x4(I32x4* i32x4) { + return read<I32x4>(i32x4); + } + MOZ_MUST_USE bool readFixedF32x4(F32x4* f32x4) { + return read<F32x4>(f32x4); + } + + // Variable-length encodings that all use LEB128. + + MOZ_MUST_USE bool readVarU32(uint32_t* out) { + return readVarU<uint32_t>(out); + } + MOZ_MUST_USE bool readVarS32(int32_t* out) { + return readVarS<int32_t>(out); + } + MOZ_MUST_USE bool readVarU64(uint64_t* out) { + return readVarU<uint64_t>(out); + } + MOZ_MUST_USE bool readVarS64(int64_t* out) { + return readVarS<int64_t>(out); + } + MOZ_MUST_USE bool readValType(ValType* type) { + static_assert(uint8_t(TypeCode::Max) <= INT8_MAX, "fits"); + uint8_t u8; + if (!readFixedU8(&u8)) + return false; + *type = (ValType)u8; + return true; + } + MOZ_MUST_USE bool readBlockType(ExprType* type) { + static_assert(size_t(TypeCode::Max) <= INT8_MAX, "fits"); + uint8_t u8; + if (!readFixedU8(&u8)) + return false; + *type = (ExprType)u8; + return true; + } + MOZ_MUST_USE bool readExpr(Expr* expr) { + static_assert(size_t(Expr::Limit) <= ExprLimit, "fits"); + uint8_t u8; + if (!readFixedU8(&u8)) + return false; + if (u8 != UINT8_MAX) { + *expr = Expr(u8); + return true; + } + if (!readFixedU8(&u8)) + return false; + if (u8 == UINT8_MAX) + return false; + *expr = Expr(uint16_t(u8) + UINT8_MAX); + return true; + } + + // See writeBytes comment. + + MOZ_MUST_USE bool readBytes(uint32_t numBytes, const uint8_t** bytes = nullptr) { + if (bytes) + *bytes = cur_; + if (bytesRemain() < numBytes) + return false; + cur_ += numBytes; + return true; + } + + // See "section" description in Encoder. + + static const uint32_t NotStarted = UINT32_MAX; + + MOZ_MUST_USE bool startSection(SectionId id, + uint32_t* startOffset, + uint32_t* size, + const char* sectionName) + { + const uint8_t* const before = cur_; + const uint8_t* beforeId = before; + uint32_t idValue; + if (!readVarU32(&idValue)) + goto backup; + while (idValue != uint32_t(id)) { + if (idValue != uint32_t(SectionId::UserDefined)) + goto backup; + // Rewind to the section id since skipUserDefinedSection expects it. + cur_ = beforeId; + if (!skipUserDefinedSection()) + return false; + beforeId = cur_; + if (!readVarU32(&idValue)) + goto backup; + } + if (!readVarU32(size)) + goto fail; + if (bytesRemain() < *size) + goto fail; + *startOffset = cur_ - beg_; + return true; + backup: + cur_ = before; + *startOffset = NotStarted; + return true; + fail: + return fail("failed to start %s section", sectionName); + } + MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size, + const char* sectionName) + { + if (size != (cur_ - beg_) - startOffset) + return fail("byte size mismatch in %s section", sectionName); + return true; + } + + // "User sections" do not cause validation errors unless the error is in + // the user-defined section header itself. + + MOZ_MUST_USE bool startUserDefinedSection(const char* expectedId, + size_t expectedIdSize, + uint32_t* sectionStart, + uint32_t* sectionSize) + { + const uint8_t* const before = cur_; + while (true) { + if (!startSection(SectionId::UserDefined, sectionStart, sectionSize, "user-defined")) + return false; + if (*sectionStart == NotStarted) { + cur_ = before; + return true; + } + uint32_t idSize; + if (!readVarU32(&idSize)) + goto fail; + if (idSize > bytesRemain() || currentOffset() + idSize > *sectionStart + *sectionSize) + goto fail; + if (expectedId && (expectedIdSize != idSize || !!memcmp(cur_, expectedId, idSize))) { + finishUserDefinedSection(*sectionStart, *sectionSize); + continue; + } + cur_ += idSize; + return true; + } + MOZ_CRASH("unreachable"); + fail: + return fail("failed to start user-defined section"); + } + template <size_t IdSizeWith0> + MOZ_MUST_USE bool startUserDefinedSection(const char (&id)[IdSizeWith0], + uint32_t* sectionStart, + uint32_t* sectionSize) + { + MOZ_ASSERT(id[IdSizeWith0 - 1] == '\0'); + return startUserDefinedSection(id, IdSizeWith0 - 1, sectionStart, sectionSize); + } + void finishUserDefinedSection(uint32_t sectionStart, uint32_t sectionSize) { + MOZ_ASSERT(cur_ >= beg_); + MOZ_ASSERT(cur_ <= end_); + cur_ = (beg_ + sectionStart) + sectionSize; + MOZ_ASSERT(cur_ <= end_); + clearError(); + } + MOZ_MUST_USE bool skipUserDefinedSection() { + uint32_t sectionStart, sectionSize; + if (!startUserDefinedSection(nullptr, 0, §ionStart, §ionSize)) + return false; + if (sectionStart == NotStarted) + return fail("expected user-defined section"); + finishUserDefinedSection(sectionStart, sectionSize); + return true; + } + + // The infallible "unchecked" decoding functions can be used when we are + // sure that the bytes are well-formed (by construction or due to previous + // validation). + + uint8_t uncheckedReadFixedU8() { + return uncheckedRead<uint8_t>(); + } + uint32_t uncheckedReadFixedU32() { + return uncheckedRead<uint32_t>(); + } + RawF32 uncheckedReadFixedF32() { + return RawF32::fromBits(uncheckedRead<uint32_t>()); + } + RawF64 uncheckedReadFixedF64() { + return RawF64::fromBits(uncheckedRead<uint64_t>()); + } + template <typename UInt> + UInt uncheckedReadVarU() { + static const unsigned numBits = sizeof(UInt) * CHAR_BIT; + static const unsigned remainderBits = numBits % 7; + static const unsigned numBitsInSevens = numBits - remainderBits; + UInt decoded = 0; + uint32_t shift = 0; + do { + uint8_t byte = *cur_++; + if (!(byte & 0x80)) + return decoded | (UInt(byte) << shift); + decoded |= UInt(byte & 0x7f) << shift; + shift += 7; + } while (shift != numBitsInSevens); + uint8_t byte = *cur_++; + MOZ_ASSERT(!(byte & 0xf0)); + return decoded | (UInt(byte) << numBitsInSevens); + } + uint32_t uncheckedReadVarU32() { + return uncheckedReadVarU<uint32_t>(); + } + int32_t uncheckedReadVarS32() { + int32_t i32 = 0; + MOZ_ALWAYS_TRUE(readVarS32(&i32)); + return i32; + } + uint64_t uncheckedReadVarU64() { + return uncheckedReadVarU<uint64_t>(); + } + int64_t uncheckedReadVarS64() { + int64_t i64 = 0; + MOZ_ALWAYS_TRUE(readVarS64(&i64)); + return i64; + } + ValType uncheckedReadValType() { + return (ValType)uncheckedReadFixedU8(); + } + Expr uncheckedReadExpr() { + static_assert(size_t(Expr::Limit) <= ExprLimit, "fits"); + uint8_t u8 = uncheckedReadFixedU8(); + return u8 != UINT8_MAX + ? Expr(u8) + : Expr(uncheckedReadFixedU8() + UINT8_MAX); + } + void uncheckedReadFixedI8x16(I8x16* i8x16) { + struct T { I8x16 v; }; + T t = uncheckedRead<T>(); + memcpy(i8x16, &t, sizeof(t)); + } + void uncheckedReadFixedI16x8(I16x8* i16x8) { + struct T { I16x8 v; }; + T t = uncheckedRead<T>(); + memcpy(i16x8, &t, sizeof(t)); + } + void uncheckedReadFixedI32x4(I32x4* i32x4) { + struct T { I32x4 v; }; + T t = uncheckedRead<T>(); + memcpy(i32x4, &t, sizeof(t)); + } + void uncheckedReadFixedF32x4(F32x4* f32x4) { + struct T { F32x4 v; }; + T t = uncheckedRead<T>(); + memcpy(f32x4, &t, sizeof(t)); + } +}; + // Reusable macro encoding/decoding functions reused by both the two // encoders (AsmJS/WasmTextToBinary) and all the decoders // (WasmCompile/WasmIonCompile/WasmBaselineCompile/WasmBinaryToText). MOZ_MUST_USE bool DecodePreamble(Decoder& d); MOZ_MUST_USE bool
--- a/js/src/wasm/WasmBinaryIterator.h +++ b/js/src/wasm/WasmBinaryIterator.h @@ -19,17 +19,17 @@ #ifndef wasm_binary_iterator_h #define wasm_binary_iterator_h #include "mozilla/Poison.h" #include "jsprf.h" #include "jit/AtomicOp.h" -#include "wasm/WasmTypes.h" +#include "wasm/WasmBinaryFormat.h" namespace js { namespace wasm { // The kind of a control-flow stack item. enum class LabelKind : uint8_t { Block, Loop,
--- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -15,17 +15,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef wasm_generator_h #define wasm_generator_h #include "jit/MacroAssembler.h" -#include "wasm/WasmBinary.h" #include "wasm/WasmCompile.h" #include "wasm/WasmModule.h" namespace js { namespace wasm { class FunctionGenerator;
--- a/js/src/wasm/WasmIonCompile.h +++ b/js/src/wasm/WasmIonCompile.h @@ -15,17 +15,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef wasm_ion_compile_h #define wasm_ion_compile_h #include "jit/MacroAssembler.h" -#include "wasm/WasmBinary.h" +#include "wasm/WasmTypes.h" namespace js { namespace wasm { struct ModuleGeneratorData; typedef Vector<jit::MIRType, 8, SystemAllocPolicy> MIRTypeVector; typedef jit::ABIArgIter<MIRTypeVector> ABIArgMIRTypeIter;
--- a/js/src/wasm/WasmTextToBinary.h +++ b/js/src/wasm/WasmTextToBinary.h @@ -14,19 +14,17 @@ * 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. */ #ifndef wasm_text_to_binary_h #define wasm_text_to_binary_h -#include "js/Utility.h" - -#include "wasm/WasmBinary.h" +#include "wasm/WasmTypes.h" namespace js { namespace wasm { // Translate the textual representation of a wasm module (given by a // null-terminated char16_t array) into serialized bytes. If there is an error // other than out-of-memory an error message string will be stored in 'error'.
--- a/js/src/wasm/WasmTextUtils.cpp +++ b/js/src/wasm/WasmTextUtils.cpp @@ -14,17 +14,17 @@ * 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/WasmTextUtils.h" #include "vm/StringBuffer.h" -#include "wasm/WasmBinary.h" +#include "wasm/WasmTypes.h" using namespace js; using namespace js::jit; using mozilla::IsNaN; template<size_t base> bool
--- a/js/src/wasm/WasmTypes.h +++ b/js/src/wasm/WasmTypes.h @@ -30,17 +30,17 @@ #include "NamespaceImports.h" #include "ds/LifoAlloc.h" #include "jit/IonTypes.h" #include "js/UniquePtr.h" #include "js/Utility.h" #include "js/Vector.h" #include "vm/MallocProvider.h" -#include "wasm/WasmBinary.h" +#include "wasm/WasmBinaryConstants.h" namespace js { class PropertyName; namespace jit { struct BaselineScript; } // This is a widespread header, so lets keep out the core wasm impl types. @@ -77,16 +77,22 @@ using mozilla::Nothing; using mozilla::PodZero; using mozilla::PodCopy; using mozilla::PodEqual; using mozilla::RefCounted; using mozilla::Some; using mozilla::Unused; typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector; +typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes; + +typedef int8_t I8x16[16]; +typedef int16_t I16x8[8]; +typedef int32_t I32x4[4]; +typedef float F32x4[4]; class Code; class CodeRange; class Memory; class Module; class Instance; class Table; @@ -139,36 +145,17 @@ struct ShareableBase : RefCounted<T> if (p) return 0; bool ok = seen->add(p, self); (void)ok; // oh well return mallocSizeOf(self) + self->sizeOfExcludingThis(mallocSizeOf); } }; -// ValType/ExprType utilities - -static inline bool -IsVoid(ExprType et) -{ - return et == ExprType::Void; -} - -static inline ValType -NonVoidToValType(ExprType et) -{ - MOZ_ASSERT(!IsVoid(et)); - return ValType(et); -} - -static inline ExprType -ToExprType(ValType vt) -{ - return ExprType(vt); -} +// ValType utilities static inline bool IsSimdType(ValType vt) { switch (vt) { case ValType::I8x16: case ValType::I16x8: case ValType::I32x4: @@ -238,22 +225,16 @@ SimdBoolType(ValType vt) case ValType::B32x4: return ValType::B32x4; default: MOZ_CRASH("Unhandled SIMD type"); } } static inline bool -IsSimdType(ExprType et) -{ - return IsVoid(et) ? false : IsSimdType(ValType(et)); -} - -static inline bool IsSimdBoolType(ValType vt) { return vt == ValType::B8x16 || vt == ValType::B16x8 || vt == ValType::B32x4; } static inline jit::MIRType ToMIRType(ValType vt) { @@ -268,16 +249,66 @@ ToMIRType(ValType vt) case ValType::F32x4: return jit::MIRType::Float32x4; case ValType::B8x16: return jit::MIRType::Bool8x16; case ValType::B16x8: return jit::MIRType::Bool16x8; case ValType::B32x4: return jit::MIRType::Bool32x4; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad type"); } +// The ExprType enum represents the type of a WebAssembly expression or return +// value and may either be a value type or void. Soon, expression types will be +// generalized to a list of ValType and this enum will go away, replaced, +// wherever it is used, by a varU32 + list of ValType. + +enum class ExprType : uint32_t // fix type so we can cast from any u8 in decoder +{ + Void = uint8_t(TypeCode::BlockVoid), + + I32 = uint8_t(TypeCode::I32), + I64 = uint8_t(TypeCode::I64), + F32 = uint8_t(TypeCode::F32), + F64 = uint8_t(TypeCode::F64), + + I8x16 = uint8_t(TypeCode::I8x16), + I16x8 = uint8_t(TypeCode::I16x8), + I32x4 = uint8_t(TypeCode::I32x4), + F32x4 = uint8_t(TypeCode::F32x4), + B8x16 = uint8_t(TypeCode::B8x16), + B16x8 = uint8_t(TypeCode::B16x8), + B32x4 = uint8_t(TypeCode::B32x4), + + Limit = uint8_t(TypeCode::Limit) +}; + +static inline bool +IsVoid(ExprType et) +{ + return et == ExprType::Void; +} + +static inline ValType +NonVoidToValType(ExprType et) +{ + MOZ_ASSERT(!IsVoid(et)); + return ValType(et); +} + +static inline ExprType +ToExprType(ValType vt) +{ + return ExprType(vt); +} + +static inline bool +IsSimdType(ExprType et) +{ + return IsVoid(et) ? false : IsSimdType(ValType(et)); +} + static inline jit::MIRType ToMIRType(ExprType et) { return IsVoid(et) ? jit::MIRType::None : ToMIRType(ValType(et)); } static inline const char* ToCString(ExprType type) @@ -301,16 +332,51 @@ ToCString(ExprType type) } static inline const char* ToCString(ValType type) { return ToCString(ToExprType(type)); } +// Because WebAssembly allows one to define the payload of a NaN value, +// including the signal/quiet bit (highest order bit of payload), another +// represenation of floating-point values is required: on some platforms (x86 +// without SSE2), passing a floating-point argument to a function call may use +// the x87 stack, which has the side-effect of clearing the signal/quiet bit. +// Because the signal/quiet bit must be preserved (by spec), we use the raw +// punned integer representation of floating points instead, in function calls. +// +// When we leave the WebAssembly sandbox back to JS, NaNs are canonicalized, so +// this isn't observable from JS. + +template<class T> +class Raw +{ + typedef typename mozilla::FloatingPoint<T>::Bits Bits; + Bits value_; + + public: + Raw() : value_(0) {} + + explicit Raw(T value) + : value_(mozilla::BitwiseCast<Bits>(value)) + {} + + template<class U> MOZ_IMPLICIT Raw(U) = delete; + + static Raw fromBits(Bits bits) { Raw r; r.value_ = bits; return r; } + + Bits bits() const { return value_; } + T fp() const { return mozilla::BitwiseCast<T>(value_); } +}; + +using RawF64 = Raw<double>; +using RawF32 = Raw<float>; + // The Val class represents a single WebAssembly value of a given value type, // mostly for the purpose of numeric literals and initializers. A Val does not // directly map to a JS value since there is not (currently) a precise // representation of i64 values. A Val may contain non-canonical NaNs since, // within WebAssembly, floats are not canonicalized. Canonicalization must // happen at the JS boundary. class Val