/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * Copyright 2015 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/WasmTextToBinary.h" #include "mozilla/CheckedInt.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" #include "jsdtoa.h" #include "jsnum.h" #include "jsprf.h" #include "jsstr.h" #include "ds/LifoAlloc.h" #include "js/CharacterEncoding.h" #include "js/HashTable.h" #include "wasm/WasmAST.h" #include "wasm/WasmBinaryFormat.h" #include "wasm/WasmTypes.h" using namespace js; using namespace js::wasm; using mozilla::BitwiseCast; using mozilla::CeilingLog2; using mozilla::CountLeadingZeroes32; using mozilla::CheckedInt; using mozilla::FloatingPoint; using mozilla::IsPowerOfTwo; using mozilla::Maybe; using mozilla::PositiveInfinity; using mozilla::SpecificNaN; /*****************************************************************************/ // wasm text token stream namespace { class WasmToken { public: enum FloatLiteralKind { HexNumber, DecNumber, Infinity, NaN }; enum Kind { Align, AnyFunc, BinaryOpcode, Block, Br, BrIf, BrTable, Call, CallIndirect, CloseParen, ComparisonOpcode, Const, ConversionOpcode, CurrentMemory, Data, Drop, Elem, Else, End, EndOfFile, Equal, Error, Export, Float, Func, GetGlobal, GetLocal, Global, GrowMemory, If, Import, Index, Memory, NegativeZero, Load, Local, Loop, Module, Mutable, Name, Nop, Offset, OpenParen, Param, Result, Return, SetGlobal, SetLocal, SignedInteger, Start, Store, Table, TeeLocal, TernaryOpcode, Text, Then, Type, UnaryOpcode, Unreachable, UnsignedInteger, ValueType }; private: Kind kind_; const char16_t* begin_; const char16_t* end_; union { uint32_t index_; uint64_t uint_; int64_t sint_; FloatLiteralKind floatLiteralKind_; ValType valueType_; Op op_; } u; public: WasmToken() : kind_(Kind(-1)), begin_(nullptr), end_(nullptr), u() { } WasmToken(Kind kind, const char16_t* begin, const char16_t* end) : kind_(kind), begin_(begin), end_(end) { MOZ_ASSERT(kind_ != Error); MOZ_ASSERT((kind == EndOfFile) == (begin == end)); } explicit WasmToken(uint32_t index, const char16_t* begin, const char16_t* end) : kind_(Index), begin_(begin), end_(end) { MOZ_ASSERT(begin != end); u.index_ = index; } explicit WasmToken(uint64_t uint, const char16_t* begin, const char16_t* end) : kind_(UnsignedInteger), begin_(begin), end_(end) { MOZ_ASSERT(begin != end); u.uint_ = uint; } explicit WasmToken(int64_t sint, const char16_t* begin, const char16_t* end) : kind_(SignedInteger), begin_(begin), end_(end) { MOZ_ASSERT(begin != end); u.sint_ = sint; } explicit WasmToken(FloatLiteralKind floatLiteralKind, const char16_t* begin, const char16_t* end) : kind_(Float), begin_(begin), end_(end) { MOZ_ASSERT(begin != end); u.floatLiteralKind_ = floatLiteralKind; } explicit WasmToken(Kind kind, ValType valueType, const char16_t* begin, const char16_t* end) : kind_(kind), begin_(begin), end_(end) { MOZ_ASSERT(begin != end); MOZ_ASSERT(kind_ == ValueType || kind_ == Const); u.valueType_ = valueType; } explicit WasmToken(Kind kind, Op op, const char16_t* begin, const char16_t* end) : kind_(kind), begin_(begin), end_(end) { MOZ_ASSERT(begin != end); MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode || kind_ == ComparisonOpcode || kind_ == ConversionOpcode || kind_ == Load || kind_ == Store); u.op_ = op; } explicit WasmToken(const char16_t* begin) : kind_(Error), begin_(begin), end_(begin) {} Kind kind() const { MOZ_ASSERT(kind_ != Kind(-1)); return kind_; } const char16_t* begin() const { return begin_; } const char16_t* end() const { return end_; } AstName text() const { MOZ_ASSERT(kind_ == Text); MOZ_ASSERT(begin_[0] == '"'); MOZ_ASSERT(end_[-1] == '"'); MOZ_ASSERT(end_ - begin_ >= 2); return AstName(begin_ + 1, end_ - begin_ - 2); } AstName name() const { return AstName(begin_, end_ - begin_); } uint32_t index() const { MOZ_ASSERT(kind_ == Index); return u.index_; } uint64_t uint() const { MOZ_ASSERT(kind_ == UnsignedInteger); return u.uint_; } int64_t sint() const { MOZ_ASSERT(kind_ == SignedInteger); return u.sint_; } FloatLiteralKind floatLiteralKind() const { MOZ_ASSERT(kind_ == Float); return u.floatLiteralKind_; } ValType valueType() const { MOZ_ASSERT(kind_ == ValueType || kind_ == Const); return u.valueType_; } Op op() const { MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode || kind_ == ComparisonOpcode || kind_ == ConversionOpcode || kind_ == Load || kind_ == Store); return u.op_; } bool isOpcode() const { switch (kind_) { case BinaryOpcode: case Block: case Br: case BrIf: case BrTable: case Call: case CallIndirect: case ComparisonOpcode: case Const: case ConversionOpcode: case CurrentMemory: case Drop: case GetGlobal: case GetLocal: case GrowMemory: case If: case Load: case Loop: case Nop: case Return: case SetGlobal: case SetLocal: case Store: case TeeLocal: case TernaryOpcode: case UnaryOpcode: case Unreachable: return true; case Align: case AnyFunc: case CloseParen: case Data: case Elem: case Else: case EndOfFile: case Equal: case End: case Error: case Export: case Float: case Func: case Global: case Mutable: case Import: case Index: case Memory: case NegativeZero: case Local: case Module: case Name: case Offset: case OpenParen: case Param: case Result: case SignedInteger: case Start: case Table: case Text: case Then: case Type: case UnsignedInteger: case ValueType: return false; } MOZ_CRASH("unexpected token kind"); } }; struct InlineImport { WasmToken module; WasmToken field; }; } // end anonymous namespace static bool IsWasmNewLine(char16_t c) { return c == '\n'; } static bool IsWasmSpace(char16_t c) { switch (c) { case ' ': case '\n': case '\r': case '\t': case '\v': case '\f': return true; default: return false; } } static bool IsWasmDigit(char16_t c) { return c >= '0' && c <= '9'; } static bool IsWasmLetter(char16_t c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } static bool IsNameAfterDollar(char16_t c) { return IsWasmLetter(c) || IsWasmDigit(c) || c == '_' || c == '$' || c == '-' || 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 WasmToken LexHexFloatLiteral(const char16_t* begin, const char16_t* end, const char16_t** curp) { const char16_t* cur = begin; if (cur != end && (*cur == '-' || *cur == '+')) cur++; MOZ_ASSERT(cur != end && *cur == '0'); cur++; MOZ_ASSERT(cur != end && *cur == 'x'); cur++; uint8_t digit; while (cur != end && IsHexDigit(*cur, &digit)) cur++; if (cur != end && *cur == '.') cur++; while (cur != end && IsHexDigit(*cur, &digit)) cur++; if (cur != end && *cur == 'p') { cur++; if (cur != end && (*cur == '-' || *cur == '+')) cur++; while (cur != end && IsWasmDigit(*cur)) cur++; } *curp = cur; return WasmToken(WasmToken::HexNumber, begin, cur); } static WasmToken LexDecFloatLiteral(const char16_t* begin, const char16_t* end, const char16_t** curp) { const char16_t* cur = begin; if (cur != end && (*cur == '-' || *cur == '+')) cur++; while (cur != end && IsWasmDigit(*cur)) cur++; if (cur != end && *cur == '.') cur++; while (cur != end && IsWasmDigit(*cur)) cur++; if (cur != end && *cur == 'e') { cur++; if (cur != end && (*cur == '-' || *cur == '+')) cur++; while (cur != end && IsWasmDigit(*cur)) cur++; } *curp = cur; return WasmToken(WasmToken::DecNumber, begin, cur); } 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 highNibble; if (!IsHexDigit(*cur, &highNibble)) return false; if (++cur == end) return false; uint8_t lowNibble; if (!IsHexDigit(*cur, &lowNibble)) return false; u8 = lowNibble | (highNibble << 4); break; } } if (byte) *byte = u8; cur++; return true; } namespace { class WasmTokenStream { static const uint32_t LookaheadSize = 2; const char16_t* cur_; const char16_t* const end_; const char16_t* lineStart_; unsigned line_; uint32_t lookaheadIndex_; uint32_t lookaheadDepth_; WasmToken lookahead_[LookaheadSize]; bool consume(const char16_t* match) { const char16_t* p = cur_; for (; *match; p++, match++) { if (p == end_ || *p != *match) return false; } cur_ = p; return true; } WasmToken fail(const char16_t* begin) const { return WasmToken(begin); } WasmToken nan(const char16_t* begin); WasmToken literal(const char16_t* begin); WasmToken next(); void skipSpaces(); public: WasmTokenStream(const char16_t* text, UniqueChars* error) : cur_(text), end_(text + js_strlen(text)), lineStart_(text), line_(1), lookaheadIndex_(0), lookaheadDepth_(0) {} void generateError(WasmToken token, UniqueChars* error) { unsigned column = token.begin() - lineStart_ + 1; error->reset(JS_smprintf("parsing wasm text at %u:%u", line_, column)); } void generateError(WasmToken token, const char* msg, UniqueChars* error) { unsigned column = token.begin() - lineStart_ + 1; error->reset(JS_smprintf("parsing wasm text at %u:%u: %s", line_, column, msg)); } WasmToken peek() { if (!lookaheadDepth_) { lookahead_[lookaheadIndex_] = next(); lookaheadDepth_ = 1; } return lookahead_[lookaheadIndex_]; } WasmToken get() { static_assert(LookaheadSize == 2, "can just flip"); if (lookaheadDepth_) { lookaheadDepth_--; WasmToken ret = lookahead_[lookaheadIndex_]; lookaheadIndex_ ^= 1; return ret; } return next(); } void unget(WasmToken token) { static_assert(LookaheadSize == 2, "can just flip"); lookaheadDepth_++; lookaheadIndex_ ^= 1; lookahead_[lookaheadIndex_] = token; } // Helpers: bool getIf(WasmToken::Kind kind, WasmToken* token) { if (peek().kind() == kind) { *token = get(); return true; } return false; } bool getIf(WasmToken::Kind kind) { WasmToken token; if (getIf(kind, &token)) return true; return false; } AstName getIfName() { WasmToken token; if (getIf(WasmToken::Name, &token)) return token.name(); return AstName(); } AstName getIfText() { WasmToken token; if (getIf(WasmToken::Text, &token)) return token.text(); return AstName(); } bool getIfRef(AstRef* ref) { WasmToken token = peek(); if (token.kind() == WasmToken::Name || token.kind() == WasmToken::Index) return matchRef(ref, nullptr); return false; } bool getIfOpcode(WasmToken* token) { *token = peek(); if (token->isOpcode()) { (void)get(); return true; } return false; } bool match(WasmToken::Kind expect, WasmToken* token, UniqueChars* error) { *token = get(); if (token->kind() == expect) return true; generateError(*token, error); return false; } bool match(WasmToken::Kind expect, UniqueChars* error) { WasmToken token; return match(expect, &token, error); } bool matchRef(AstRef* ref, UniqueChars* error) { WasmToken token = get(); switch (token.kind()) { case WasmToken::Name: *ref = AstRef(token.name()); break; case WasmToken::Index: *ref = AstRef(token.index()); break; default: generateError(token, error); return false; } return true; } }; } // end anonymous namespace WasmToken WasmTokenStream::nan(const char16_t* begin) { if (consume(u":")) { if (!consume(u"0x")) return fail(begin); uint8_t digit; while (cur_ != end_ && IsHexDigit(*cur_, &digit)) cur_++; } return WasmToken(WasmToken::NaN, begin, cur_); } WasmToken WasmTokenStream::literal(const char16_t* begin) { CheckedInt u = 0; if (consume(u"0x")) { if (cur_ == end_) return fail(begin); do { if (*cur_ == '.' || *cur_ == 'p') return LexHexFloatLiteral(begin, end_, &cur_); uint8_t digit; if (!IsHexDigit(*cur_, &digit)) break; u *= 16; u += digit; if (!u.isValid()) return LexHexFloatLiteral(begin, end_, &cur_); cur_++; } while (cur_ != end_); if (*begin == '-') { uint64_t value = u.value(); if (value == 0) return WasmToken(WasmToken::NegativeZero, begin, cur_); if (value > uint64_t(INT64_MIN)) return LexHexFloatLiteral(begin, end_, &cur_); value = -value; return WasmToken(int64_t(value), begin, cur_); } } else { while (cur_ != end_) { if (*cur_ == '.' || *cur_ == 'e') return LexDecFloatLiteral(begin, end_, &cur_); if (!IsWasmDigit(*cur_)) break; u *= 10; u += *cur_ - '0'; if (!u.isValid()) return LexDecFloatLiteral(begin, end_, &cur_); cur_++; } if (*begin == '-') { uint64_t value = u.value(); if (value == 0) return WasmToken(WasmToken::NegativeZero, begin, cur_); if (value > uint64_t(INT64_MIN)) return LexDecFloatLiteral(begin, end_, &cur_); value = -value; return WasmToken(int64_t(value), begin, cur_); } } CheckedInt index = u.value(); if (index.isValid()) return WasmToken(index.value(), begin, cur_); return WasmToken(u.value(), begin, cur_); } void WasmTokenStream::skipSpaces() { while (cur_ != end_) { char16_t ch = *cur_; if (ch == ';' && consume(u";;")) { // Skipping single line comment. while (cur_ != end_ && !IsWasmNewLine(*cur_)) cur_++; } else if (ch == '(' && consume(u"(;")) { // Skipping multi-line and possibly nested comments. size_t level = 1; while (cur_ != end_) { char16_t ch = *cur_; if (ch == '(' && consume(u"(;")) { level++; } else if (ch == ';' && consume(u";)")) { if (--level == 0) break; } else { cur_++; if (IsWasmNewLine(ch)) { lineStart_ = cur_; line_++; } } } } else if (IsWasmSpace(ch)) { cur_++; if (IsWasmNewLine(ch)) { lineStart_ = cur_; line_++; } } else break; // non-whitespace found } } WasmToken WasmTokenStream::next() { skipSpaces(); if (cur_ == end_) return WasmToken(WasmToken::EndOfFile, cur_, cur_); const char16_t* begin = cur_; switch (*begin) { case '"': cur_++; while (true) { if (cur_ == end_) return fail(begin); 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_); case '(': cur_++; return WasmToken(WasmToken::OpenParen, begin, cur_); case ')': cur_++; return WasmToken(WasmToken::CloseParen, begin, cur_); case '=': cur_++; return WasmToken(WasmToken::Equal, begin, cur_); case '+': case '-': cur_++; if (consume(u"infinity")) return WasmToken(WasmToken::Infinity, begin, cur_); if (consume(u"nan")) return nan(begin); if (!IsWasmDigit(*cur_)) break; [[fallthrough]]; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return literal(begin); case 'a': if (consume(u"align")) return WasmToken(WasmToken::Align, begin, cur_); if (consume(u"anyfunc")) return WasmToken(WasmToken::AnyFunc, begin, cur_); break; case 'b': if (consume(u"block")) return WasmToken(WasmToken::Block, begin, cur_); if (consume(u"br")) { if (consume(u"_table")) return WasmToken(WasmToken::BrTable, begin, cur_); if (consume(u"_if")) return WasmToken(WasmToken::BrIf, begin, cur_); return WasmToken(WasmToken::Br, begin, cur_); } break; case 'c': if (consume(u"call")) { if (consume(u"_indirect")) return WasmToken(WasmToken::CallIndirect, begin, cur_); return WasmToken(WasmToken::Call, begin, cur_); } if (consume(u"current_memory")) return WasmToken(WasmToken::CurrentMemory, begin, cur_); break; case 'd': if (consume(u"data")) return WasmToken(WasmToken::Data, begin, cur_); if (consume(u"drop")) return WasmToken(WasmToken::Drop, begin, cur_); break; case 'e': if (consume(u"elem")) return WasmToken(WasmToken::Elem, begin, cur_); if (consume(u"else")) return WasmToken(WasmToken::Else, begin, cur_); if (consume(u"end")) return WasmToken(WasmToken::End, begin, cur_); if (consume(u"export")) return WasmToken(WasmToken::Export, begin, cur_); break; case 'f': if (consume(u"func")) return WasmToken(WasmToken::Func, begin, cur_); if (consume(u"f32")) { if (!consume(u".")) return WasmToken(WasmToken::ValueType, ValType::F32, begin, cur_); switch (*cur_) { case 'a': if (consume(u"abs")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Abs, begin, cur_); if (consume(u"add")) return WasmToken(WasmToken::BinaryOpcode, Op::F32Add, begin, cur_); break; case 'c': if (consume(u"ceil")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Ceil, begin, cur_); if (consume(u"const")) return WasmToken(WasmToken::Const, ValType::F32, begin, cur_); if (consume(u"convert_s/i32")) { return WasmToken(WasmToken::ConversionOpcode, Op::F32ConvertSI32, begin, cur_); } if (consume(u"convert_u/i32")) { return WasmToken(WasmToken::ConversionOpcode, Op::F32ConvertUI32, begin, cur_); } if (consume(u"convert_s/i64")) { return WasmToken(WasmToken::ConversionOpcode, Op::F32ConvertSI64, begin, cur_); } if (consume(u"convert_u/i64")) { return WasmToken(WasmToken::ConversionOpcode, Op::F32ConvertUI64, begin, cur_); } if (consume(u"copysign")) return WasmToken(WasmToken::BinaryOpcode, Op::F32CopySign, begin, cur_); break; case 'd': if (consume(u"demote/f64")) return WasmToken(WasmToken::ConversionOpcode, Op::F32DemoteF64, begin, cur_); if (consume(u"div")) return WasmToken(WasmToken::BinaryOpcode, Op::F32Div, begin, cur_); break; case 'e': if (consume(u"eq")) return WasmToken(WasmToken::ComparisonOpcode, Op::F32Eq, begin, cur_); if (consume(u"extend8_s")) return WasmToken(WasmToken::ConversionOpcode, Op::I32Extend8S, begin, cur_); if (consume(u"extend16_s")) return WasmToken(WasmToken::ConversionOpcode, Op::I32Extend16S, begin, cur_); if (consume(u"extend8_s")) return WasmToken(WasmToken::ConversionOpcode, Op::I64Extend8S, begin, cur_); if (consume(u"extend16_s")) return WasmToken(WasmToken::ConversionOpcode, Op::I64Extend16S, begin, cur_); if (consume(u"extend32_s")) return WasmToken(WasmToken::ConversionOpcode, Op::I64Extend32S, begin, cur_); break; case 'f': if (consume(u"floor")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Floor, begin, cur_); break; case 'g': if (consume(u"ge")) return WasmToken(WasmToken::ComparisonOpcode, Op::F32Ge, begin, cur_); if (consume(u"gt")) return WasmToken(WasmToken::ComparisonOpcode, Op::F32Gt, begin, cur_); break; case 'l': if (consume(u"le")) return WasmToken(WasmToken::ComparisonOpcode, Op::F32Le, begin, cur_); if (consume(u"lt")) return WasmToken(WasmToken::ComparisonOpcode, Op::F32Lt, begin, cur_); if (consume(u"load")) return WasmToken(WasmToken::Load, Op::F32Load, begin, cur_); break; case 'm': if (consume(u"max")) return WasmToken(WasmToken::BinaryOpcode, Op::F32Max, begin, cur_); if (consume(u"min")) return WasmToken(WasmToken::BinaryOpcode, Op::F32Min, begin, cur_); if (consume(u"mul")) return WasmToken(WasmToken::BinaryOpcode, Op::F32Mul, begin, cur_); break; case 'n': if (consume(u"nearest")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Nearest, begin, cur_); if (consume(u"neg")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Neg, begin, cur_); if (consume(u"ne")) return WasmToken(WasmToken::ComparisonOpcode, Op::F32Ne, begin, cur_); break; case 'r': if (consume(u"reinterpret/i32")) return WasmToken(WasmToken::ConversionOpcode, Op::F32ReinterpretI32, begin, cur_); break; case 's': if (consume(u"sqrt")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Sqrt, begin, cur_); if (consume(u"sub")) return WasmToken(WasmToken::BinaryOpcode, Op::F32Sub, begin, cur_); if (consume(u"store")) return WasmToken(WasmToken::Store, Op::F32Store, begin, cur_); break; case 't': if (consume(u"trunc")) return WasmToken(WasmToken::UnaryOpcode, Op::F32Trunc, begin, cur_); break; } break; } if (consume(u"f64")) { if (!consume(u".")) return WasmToken(WasmToken::ValueType, ValType::F64, begin, cur_); switch (*cur_) { case 'a': if (consume(u"abs")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Abs, begin, cur_); if (consume(u"add")) return WasmToken(WasmToken::BinaryOpcode, Op::F64Add, begin, cur_); break; case 'c': if (consume(u"ceil")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Ceil, begin, cur_); if (consume(u"const")) return WasmToken(WasmToken::Const, ValType::F64, begin, cur_); if (consume(u"convert_s/i32")) { return WasmToken(WasmToken::ConversionOpcode, Op::F64ConvertSI32, begin, cur_); } if (consume(u"convert_u/i32")) { return WasmToken(WasmToken::ConversionOpcode, Op::F64ConvertUI32, begin, cur_); } if (consume(u"convert_s/i64")) { return WasmToken(WasmToken::ConversionOpcode, Op::F64ConvertSI64, begin, cur_); } if (consume(u"convert_u/i64")) { return WasmToken(WasmToken::ConversionOpcode, Op::F64ConvertUI64, begin, cur_); } if (consume(u"copysign")) return WasmToken(WasmToken::BinaryOpcode, Op::F64CopySign, begin, cur_); break; case 'd': if (consume(u"div")) return WasmToken(WasmToken::BinaryOpcode, Op::F64Div, begin, cur_); break; case 'e': if (consume(u"eq")) return WasmToken(WasmToken::ComparisonOpcode, Op::F64Eq, begin, cur_); break; case 'f': if (consume(u"floor")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Floor, begin, cur_); break; case 'g': if (consume(u"ge")) return WasmToken(WasmToken::ComparisonOpcode, Op::F64Ge, begin, cur_); if (consume(u"gt")) return WasmToken(WasmToken::ComparisonOpcode, Op::F64Gt, begin, cur_); break; case 'l': if (consume(u"le")) return WasmToken(WasmToken::ComparisonOpcode, Op::F64Le, begin, cur_); if (consume(u"lt")) return WasmToken(WasmToken::ComparisonOpcode, Op::F64Lt, begin, cur_); if (consume(u"load")) return WasmToken(WasmToken::Load, Op::F64Load, begin, cur_); break; case 'm': if (consume(u"max")) return WasmToken(WasmToken::BinaryOpcode, Op::F64Max, begin, cur_); if (consume(u"min")) return WasmToken(WasmToken::BinaryOpcode, Op::F64Min, begin, cur_); if (consume(u"mul")) return WasmToken(WasmToken::BinaryOpcode, Op::F64Mul, begin, cur_); break; case 'n': if (consume(u"nearest")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Nearest, begin, cur_); if (consume(u"neg")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Neg, begin, cur_); if (consume(u"ne")) return WasmToken(WasmToken::ComparisonOpcode, Op::F64Ne, begin, cur_); break; case 'p': if (consume(u"promote/f32")) return WasmToken(WasmToken::ConversionOpcode, Op::F64PromoteF32, begin, cur_); break; case 'r': if (consume(u"reinterpret/i64")) return WasmToken(WasmToken::UnaryOpcode, Op::F64ReinterpretI64, begin, cur_); break; case 's': if (consume(u"sqrt")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Sqrt, begin, cur_); if (consume(u"sub")) return WasmToken(WasmToken::BinaryOpcode, Op::F64Sub, begin, cur_); if (consume(u"store")) return WasmToken(WasmToken::Store, Op::F64Store, begin, cur_); break; case 't': if (consume(u"trunc")) return WasmToken(WasmToken::UnaryOpcode, Op::F64Trunc, begin, cur_); break; } break; } break; case 'g': if (consume(u"get_global")) return WasmToken(WasmToken::GetGlobal, begin, cur_); if (consume(u"get_local")) return WasmToken(WasmToken::GetLocal, begin, cur_); if (consume(u"global")) return WasmToken(WasmToken::Global, begin, cur_); if (consume(u"grow_memory")) return WasmToken(WasmToken::GrowMemory, begin, cur_); break; case 'i': if (consume(u"i32")) { if (!consume(u".")) return WasmToken(WasmToken::ValueType, ValType::I32, begin, cur_); switch (*cur_) { case 'a': if (consume(u"add")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Add, begin, cur_); if (consume(u"and")) return WasmToken(WasmToken::BinaryOpcode, Op::I32And, begin, cur_); break; case 'c': if (consume(u"const")) return WasmToken(WasmToken::Const, ValType::I32, begin, cur_); if (consume(u"clz")) return WasmToken(WasmToken::UnaryOpcode, Op::I32Clz, begin, cur_); if (consume(u"ctz")) return WasmToken(WasmToken::UnaryOpcode, Op::I32Ctz, begin, cur_); break; case 'd': if (consume(u"div_s")) return WasmToken(WasmToken::BinaryOpcode, Op::I32DivS, begin, cur_); if (consume(u"div_u")) return WasmToken(WasmToken::BinaryOpcode, Op::I32DivU, begin, cur_); break; case 'e': if (consume(u"eqz")) return WasmToken(WasmToken::UnaryOpcode, Op::I32Eqz, begin, cur_); if (consume(u"eq")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32Eq, begin, cur_); break; case 'g': if (consume(u"ge_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32GeS, begin, cur_); if (consume(u"ge_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32GeU, begin, cur_); if (consume(u"gt_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32GtS, begin, cur_); if (consume(u"gt_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32GtU, begin, cur_); break; case 'l': if (consume(u"le_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32LeS, begin, cur_); if (consume(u"le_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32LeU, begin, cur_); if (consume(u"lt_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32LtS, begin, cur_); if (consume(u"lt_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32LtU, begin, cur_); if (consume(u"load")) { if (IsWasmSpace(*cur_)) return WasmToken(WasmToken::Load, Op::I32Load, begin, cur_); if (consume(u"8_s")) return WasmToken(WasmToken::Load, Op::I32Load8S, begin, cur_); if (consume(u"8_u")) return WasmToken(WasmToken::Load, Op::I32Load8U, begin, cur_); if (consume(u"16_s")) return WasmToken(WasmToken::Load, Op::I32Load16S, begin, cur_); if (consume(u"16_u")) return WasmToken(WasmToken::Load, Op::I32Load16U, begin, cur_); break; } break; case 'm': if (consume(u"mul")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Mul, begin, cur_); break; case 'n': if (consume(u"ne")) return WasmToken(WasmToken::ComparisonOpcode, Op::I32Ne, begin, cur_); break; case 'o': if (consume(u"or")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Or, begin, cur_); break; case 'p': if (consume(u"popcnt")) return WasmToken(WasmToken::UnaryOpcode, Op::I32Popcnt, begin, cur_); break; case 'r': if (consume(u"reinterpret/f32")) return WasmToken(WasmToken::UnaryOpcode, Op::I32ReinterpretF32, begin, cur_); if (consume(u"rem_s")) return WasmToken(WasmToken::BinaryOpcode, Op::I32RemS, begin, cur_); if (consume(u"rem_u")) return WasmToken(WasmToken::BinaryOpcode, Op::I32RemU, begin, cur_); if (consume(u"rotr")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Rotr, begin, cur_); if (consume(u"rotl")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Rotl, begin, cur_); break; case 's': if (consume(u"sub")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Sub, begin, cur_); if (consume(u"shl")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Shl, begin, cur_); if (consume(u"shr_s")) return WasmToken(WasmToken::BinaryOpcode, Op::I32ShrS, begin, cur_); if (consume(u"shr_u")) return WasmToken(WasmToken::BinaryOpcode, Op::I32ShrU, begin, cur_); if (consume(u"store")) { if (IsWasmSpace(*cur_)) return WasmToken(WasmToken::Store, Op::I32Store, begin, cur_); if (consume(u"8")) return WasmToken(WasmToken::Store, Op::I32Store8, begin, cur_); if (consume(u"16")) return WasmToken(WasmToken::Store, Op::I32Store16, begin, cur_); break; } break; case 't': if (consume(u"trunc_s/f32")) return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncSF32, begin, cur_); if (consume(u"trunc_s/f64")) return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncSF64, begin, cur_); if (consume(u"trunc_u/f32")) return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncUF32, begin, cur_); if (consume(u"trunc_u/f64")) return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncUF64, begin, cur_); break; case 'w': if (consume(u"wrap/i64")) return WasmToken(WasmToken::ConversionOpcode, Op::I32WrapI64, begin, cur_); break; case 'x': if (consume(u"xor")) return WasmToken(WasmToken::BinaryOpcode, Op::I32Xor, begin, cur_); break; } break; } if (consume(u"i64")) { if (!consume(u".")) return WasmToken(WasmToken::ValueType, ValType::I64, begin, cur_); switch (*cur_) { case 'a': if (consume(u"add")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Add, begin, cur_); if (consume(u"and")) return WasmToken(WasmToken::BinaryOpcode, Op::I64And, begin, cur_); break; case 'c': if (consume(u"const")) return WasmToken(WasmToken::Const, ValType::I64, begin, cur_); if (consume(u"clz")) return WasmToken(WasmToken::UnaryOpcode, Op::I64Clz, begin, cur_); if (consume(u"ctz")) return WasmToken(WasmToken::UnaryOpcode, Op::I64Ctz, begin, cur_); break; case 'd': if (consume(u"div_s")) return WasmToken(WasmToken::BinaryOpcode, Op::I64DivS, begin, cur_); if (consume(u"div_u")) return WasmToken(WasmToken::BinaryOpcode, Op::I64DivU, begin, cur_); break; case 'e': if (consume(u"eqz")) return WasmToken(WasmToken::UnaryOpcode, Op::I64Eqz, begin, cur_); if (consume(u"eq")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64Eq, begin, cur_); if (consume(u"extend_s/i32")) return WasmToken(WasmToken::ConversionOpcode, Op::I64ExtendSI32, begin, cur_); if (consume(u"extend_u/i32")) return WasmToken(WasmToken::ConversionOpcode, Op::I64ExtendUI32, begin, cur_); break; case 'g': if (consume(u"ge_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64GeS, begin, cur_); if (consume(u"ge_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64GeU, begin, cur_); if (consume(u"gt_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64GtS, begin, cur_); if (consume(u"gt_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64GtU, begin, cur_); break; case 'l': if (consume(u"le_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64LeS, begin, cur_); if (consume(u"le_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64LeU, begin, cur_); if (consume(u"lt_s")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64LtS, begin, cur_); if (consume(u"lt_u")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64LtU, begin, cur_); if (consume(u"load")) { if (IsWasmSpace(*cur_)) return WasmToken(WasmToken::Load, Op::I64Load, begin, cur_); if (consume(u"8_s")) return WasmToken(WasmToken::Load, Op::I64Load8S, begin, cur_); if (consume(u"8_u")) return WasmToken(WasmToken::Load, Op::I64Load8U, begin, cur_); if (consume(u"16_s")) return WasmToken(WasmToken::Load, Op::I64Load16S, begin, cur_); if (consume(u"16_u")) return WasmToken(WasmToken::Load, Op::I64Load16U, begin, cur_); if (consume(u"32_s")) return WasmToken(WasmToken::Load, Op::I64Load32S, begin, cur_); if (consume(u"32_u")) return WasmToken(WasmToken::Load, Op::I64Load32U, begin, cur_); break; } break; case 'm': if (consume(u"mul")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Mul, begin, cur_); break; case 'n': if (consume(u"ne")) return WasmToken(WasmToken::ComparisonOpcode, Op::I64Ne, begin, cur_); break; case 'o': if (consume(u"or")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Or, begin, cur_); break; case 'p': if (consume(u"popcnt")) return WasmToken(WasmToken::UnaryOpcode, Op::I64Popcnt, begin, cur_); break; case 'r': if (consume(u"reinterpret/f64")) return WasmToken(WasmToken::UnaryOpcode, Op::I64ReinterpretF64, begin, cur_); if (consume(u"rem_s")) return WasmToken(WasmToken::BinaryOpcode, Op::I64RemS, begin, cur_); if (consume(u"rem_u")) return WasmToken(WasmToken::BinaryOpcode, Op::I64RemU, begin, cur_); if (consume(u"rotr")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Rotr, begin, cur_); if (consume(u"rotl")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Rotl, begin, cur_); break; case 's': if (consume(u"sub")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Sub, begin, cur_); if (consume(u"shl")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Shl, begin, cur_); if (consume(u"shr_s")) return WasmToken(WasmToken::BinaryOpcode, Op::I64ShrS, begin, cur_); if (consume(u"shr_u")) return WasmToken(WasmToken::BinaryOpcode, Op::I64ShrU, begin, cur_); if (consume(u"store")) { if (IsWasmSpace(*cur_)) return WasmToken(WasmToken::Store, Op::I64Store, begin, cur_); if (consume(u"8")) return WasmToken(WasmToken::Store, Op::I64Store8, begin, cur_); if (consume(u"16")) return WasmToken(WasmToken::Store, Op::I64Store16, begin, cur_); if (consume(u"32")) return WasmToken(WasmToken::Store, Op::I64Store32, begin, cur_); break; } break; case 't': if (consume(u"trunc_s/f32")) return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncSF32, begin, cur_); if (consume(u"trunc_s/f64")) return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncSF64, begin, cur_); if (consume(u"trunc_u/f32")) return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncUF32, begin, cur_); if (consume(u"trunc_u/f64")) return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncUF64, begin, cur_); break; case 'x': if (consume(u"xor")) return WasmToken(WasmToken::BinaryOpcode, Op::I64Xor, begin, cur_); break; } break; } if (consume(u"import")) return WasmToken(WasmToken::Import, begin, cur_); if (consume(u"infinity")) return WasmToken(WasmToken::Infinity, begin, cur_); if (consume(u"if")) return WasmToken(WasmToken::If, begin, cur_); break; case 'l': if (consume(u"local")) return WasmToken(WasmToken::Local, begin, cur_); if (consume(u"loop")) return WasmToken(WasmToken::Loop, begin, cur_); break; case 'm': if (consume(u"module")) return WasmToken(WasmToken::Module, begin, cur_); if (consume(u"memory")) return WasmToken(WasmToken::Memory, begin, cur_); if (consume(u"mut")) return WasmToken(WasmToken::Mutable, begin, cur_); break; case 'n': if (consume(u"nan")) return nan(begin); if (consume(u"nop")) return WasmToken(WasmToken::Nop, begin, cur_); break; case 'o': if (consume(u"offset")) return WasmToken(WasmToken::Offset, begin, cur_); break; case 'p': if (consume(u"param")) return WasmToken(WasmToken::Param, begin, cur_); break; case 'r': if (consume(u"result")) return WasmToken(WasmToken::Result, begin, cur_); if (consume(u"return")) return WasmToken(WasmToken::Return, begin, cur_); break; case 's': if (consume(u"select")) return WasmToken(WasmToken::TernaryOpcode, Op::Select, begin, cur_); if (consume(u"set_global")) return WasmToken(WasmToken::SetGlobal, begin, cur_); if (consume(u"set_local")) return WasmToken(WasmToken::SetLocal, begin, cur_); if (consume(u"start")) return WasmToken(WasmToken::Start, begin, cur_); break; case 't': if (consume(u"table")) return WasmToken(WasmToken::Table, begin, cur_); if (consume(u"tee_local")) return WasmToken(WasmToken::TeeLocal, begin, cur_); if (consume(u"then")) return WasmToken(WasmToken::Then, begin, cur_); if (consume(u"type")) return WasmToken(WasmToken::Type, begin, cur_); break; case 'u': if (consume(u"unreachable")) return WasmToken(WasmToken::Unreachable, begin, cur_); break; default: break; } return fail(begin); } /*****************************************************************************/ // wasm text format parser namespace { struct WasmParseContext { WasmTokenStream ts; LifoAlloc& lifo; UniqueChars* error; DtoaState* dtoaState; WasmParseContext(const char16_t* text, LifoAlloc& lifo, UniqueChars* error) : ts(text, error), lifo(lifo), error(error), dtoaState(NewDtoaState()) {} bool fail(const char* message) { error->reset(js_strdup(message)); return false; } ~WasmParseContext() { DestroyDtoaState(dtoaState); } }; } // end anonymous namespace static AstExpr* ParseExprInsideParens(WasmParseContext& c); static AstExpr* ParseExprBody(WasmParseContext& c, WasmToken token, bool inParens); static AstExpr* ParseExpr(WasmParseContext& c, bool inParens) { WasmToken openParen; if (!inParens || !c.ts.getIf(WasmToken::OpenParen, &openParen)) return new(c.lifo) AstPop(); // Special case: If we have an open paren, but it's a "(then ...", then // we don't have an expresion following us, so we pop here too. This // handles "(if (then ...))" which pops the condition. if (c.ts.peek().kind() == WasmToken::Then) { c.ts.unget(openParen); return new(c.lifo) AstPop(); } AstExpr* expr = ParseExprInsideParens(c); if (!expr) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return expr; } static bool ParseExprList(WasmParseContext& c, AstExprVector* exprs, bool inParens) { for (;;) { if (c.ts.getIf(WasmToken::OpenParen)) { AstExpr* expr = ParseExprInsideParens(c); if (!expr || !exprs->append(expr)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; continue; } WasmToken token; if (c.ts.getIfOpcode(&token)) { AstExpr* expr = ParseExprBody(c, token, false); if (!expr || !exprs->append(expr)) return false; continue; } break; } return true; } static bool ParseBlockSignature(WasmParseContext& c, ExprType* type) { WasmToken token; if (c.ts.getIf(WasmToken::ValueType, &token)) *type = ToExprType(token.valueType()); else *type = ExprType::Void; return true; } static AstBlock* ParseBlock(WasmParseContext& c, Op op, bool inParens) { AstExprVector exprs(c.lifo); AstName name = c.ts.getIfName(); // Compatibility syntax sugar: If a second label is present, we'll wrap // this loop in a block. AstName otherName; if (op == Op::Loop) { AstName maybeName = c.ts.getIfName(); if (!maybeName.empty()) { otherName = name; name = maybeName; } } ExprType type; if (!ParseBlockSignature(c, &type)) return nullptr; if (!ParseExprList(c, &exprs, inParens)) return nullptr; if (!inParens) { if (!c.ts.match(WasmToken::End, c.error)) return nullptr; } AstBlock* result = new(c.lifo) AstBlock(op, type, name, Move(exprs)); if (op == Op::Loop && !otherName.empty()) { if (!exprs.append(result)) return nullptr; result = new(c.lifo) AstBlock(Op::Block, type, otherName, Move(exprs)); } return result; } static AstBranch* ParseBranch(WasmParseContext& c, Op op, bool inParens) { MOZ_ASSERT(op == Op::Br || op == Op::BrIf); AstRef target; if (!c.ts.matchRef(&target, c.error)) return nullptr; AstExpr* value = nullptr; if (inParens) { if (c.ts.getIf(WasmToken::OpenParen)) { value = ParseExprInsideParens(c); if (!value) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } } AstExpr* cond = nullptr; if (op == Op::BrIf) { if (inParens && c.ts.getIf(WasmToken::OpenParen)) { cond = ParseExprInsideParens(c); if (!cond) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } else { cond = new(c.lifo) AstPop(); if (!cond) return nullptr; } } return new(c.lifo) AstBranch(op, ExprType::Void, cond, target, value); } static bool ParseArgs(WasmParseContext& c, AstExprVector* args) { while (c.ts.getIf(WasmToken::OpenParen)) { AstExpr* arg = ParseExprInsideParens(c); if (!arg || !args->append(arg)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } return true; } static AstCall* ParseCall(WasmParseContext& c, bool inParens) { AstRef func; if (!c.ts.matchRef(&func, c.error)) return nullptr; AstExprVector args(c.lifo); if (inParens) { if (!ParseArgs(c, &args)) return nullptr; } return new(c.lifo) AstCall(Op::Call, ExprType::Void, func, Move(args)); } static AstCallIndirect* ParseCallIndirect(WasmParseContext& c, bool inParens) { AstRef sig; if (!c.ts.matchRef(&sig, c.error)) return nullptr; AstExprVector args(c.lifo); AstExpr* index; if (inParens) { if (!ParseArgs(c, &args)) return nullptr; if (args.empty()) index = new(c.lifo) AstPop(); else index = args.popCopy(); } else { index = new(c.lifo) AstPop(); } return new(c.lifo) AstCallIndirect(sig, ExprType::Void, Move(args), index); } static uint_fast8_t CountLeadingZeroes4(uint8_t x) { MOZ_ASSERT((x & -0x10) == 0); return CountLeadingZeroes32(x) - 28; } template static T ushl(T lhs, unsigned rhs) { return rhs < sizeof(T) * CHAR_BIT ? (lhs << rhs) : 0; } template static T ushr(T lhs, unsigned rhs) { return rhs < sizeof(T) * CHAR_BIT ? (lhs >> rhs) : 0; } template static AstConst* ParseNaNLiteral(WasmParseContext& c, WasmToken token, const char16_t* cur, bool isNegated) { const char16_t* end = token.end(); MOZ_ALWAYS_TRUE(*cur++ == 'n' && *cur++ == 'a' && *cur++ == 'n'); typedef FloatingPoint Traits; typedef typename Traits::Bits Bits; Bits value; if (cur != end) { MOZ_ALWAYS_TRUE(*cur++ == ':' && *cur++ == '0' && *cur++ == 'x'); if (cur == end) goto error; CheckedInt u = 0; do { uint8_t digit = 0; MOZ_ALWAYS_TRUE(IsHexDigit(*cur, &digit)); u *= 16; u += digit; cur++; } while (cur != end); if (!u.isValid()) goto error; value = u.value(); if ((value & ~Traits::kSignificandBits) != 0) goto error; // NaN payloads must contain at least one set bit. if (value == 0) goto error; } else { // Produce the spec's default NaN. value = (Traits::kSignificandBits + 1) >> 1; } value = (isNegated ? Traits::kSignBit : 0) | Traits::kExponentBits | value; return new (c.lifo) AstConst(Val(Raw::fromBits(value))); error: c.ts.generateError(token, c.error); return nullptr; } template static bool ParseHexFloatLiteral(const char16_t* cur, const char16_t* end, Float* result) { MOZ_ALWAYS_TRUE(*cur++ == '0' && *cur++ == 'x'); typedef FloatingPoint Traits; typedef typename Traits::Bits Bits; static const unsigned numBits = sizeof(Float) * CHAR_BIT; static const Bits allOnes = ~Bits(0); static const Bits mostSignificantBit = ~(allOnes >> 1); // Significand part. Bits significand = 0; CheckedInt exponent = 0; bool sawFirstNonZero = false; bool discardedExtraNonZero = false; const char16_t* dot = nullptr; int significandPos; for (; cur != end; cur++) { if (*cur == '.') { MOZ_ASSERT(!dot); dot = cur; continue; } uint8_t digit; if (!IsHexDigit(*cur, &digit)) break; if (!sawFirstNonZero) { if (digit == 0) continue; // We've located the first non-zero digit; we can now determine the // initial exponent. If we're after the dot, count the number of // zeros from the dot to here, and adjust for the number of leading // zero bits in the digit. Set up significandPos to put the first // nonzero at the most significant bit. int_fast8_t lz = CountLeadingZeroes4(digit); ptrdiff_t zeroAdjustValue = !dot ? 1 : dot + 1 - cur; CheckedInt zeroAdjust = zeroAdjustValue; zeroAdjust *= 4; zeroAdjust -= lz + 1; if (!zeroAdjust.isValid()) return false; exponent = zeroAdjust.value(); significandPos = numBits - (4 - lz); sawFirstNonZero = true; } else { // We've already seen a non-zero; just take 4 more bits. if (!dot) exponent += 4; if (significandPos > -4) significandPos -= 4; } // Or the newly parsed digit into significand at signicandPos. if (significandPos >= 0) { significand |= ushl(Bits(digit), significandPos); } else if (significandPos > -4) { significand |= ushr(digit, 4 - significandPos); discardedExtraNonZero = (digit & ~ushl(allOnes, 4 - significandPos)) != 0; } else if (digit != 0) { discardedExtraNonZero = true; } } // Exponent part. if (cur != end) { MOZ_ALWAYS_TRUE(*cur++ == 'p'); bool isNegated = false; if (cur != end && (*cur == '-' || *cur == '+')) isNegated = *cur++ == '-'; CheckedInt parsedExponent = 0; while (cur != end && IsWasmDigit(*cur)) parsedExponent = parsedExponent * 10 + (*cur++ - '0'); if (isNegated) parsedExponent = -parsedExponent; exponent += parsedExponent; } MOZ_ASSERT(cur == end); if (!exponent.isValid()) return false; // Create preliminary exponent and significand encodings of the results. Bits encodedExponent, encodedSignificand, discardedSignificandBits; if (significand == 0) { // Zero. The exponent is encoded non-biased. encodedExponent = 0; encodedSignificand = 0; discardedSignificandBits = 0; } else if (MOZ_UNLIKELY(exponent.value() <= int32_t(-Traits::kExponentBias))) { // Underflow to subnormal or zero. encodedExponent = 0; encodedSignificand = ushr(significand, numBits - Traits::kExponentShift - exponent.value() - Traits::kExponentBias); discardedSignificandBits = ushl(significand, Traits::kExponentShift + exponent.value() + Traits::kExponentBias); } else if (MOZ_LIKELY(exponent.value() <= int32_t(Traits::kExponentBias))) { // Normal (non-zero). The significand's leading 1 is encoded implicitly. encodedExponent = (Bits(exponent.value()) + Traits::kExponentBias) << Traits::kExponentShift; MOZ_ASSERT(significand & mostSignificantBit); encodedSignificand = ushr(significand, numBits - Traits::kExponentShift - 1) & Traits::kSignificandBits; discardedSignificandBits = ushl(significand, Traits::kExponentShift + 1); } else { // Overflow to infinity. encodedExponent = Traits::kExponentBits; encodedSignificand = 0; discardedSignificandBits = 0; } MOZ_ASSERT((encodedExponent & ~Traits::kExponentBits) == 0); MOZ_ASSERT((encodedSignificand & ~Traits::kSignificandBits) == 0); MOZ_ASSERT(encodedExponent != Traits::kExponentBits || encodedSignificand == 0); Bits bits = encodedExponent | encodedSignificand; // Apply rounding. If this overflows the significand, it carries into the // exponent bit according to the magic of the IEEE 754 encoding. bits += (discardedSignificandBits & mostSignificantBit) && ((discardedSignificandBits & ~mostSignificantBit) || discardedExtraNonZero || // ties to even (encodedSignificand & 1)); *result = BitwiseCast(bits); return true; } template static AstConst* ParseFloatLiteral(WasmParseContext& c, WasmToken token) { Float result; switch (token.kind()) { case WasmToken::Index: result = token.index(); break; case WasmToken::UnsignedInteger: result = token.uint(); break; case WasmToken::SignedInteger: result = token.sint(); break; case WasmToken::NegativeZero: result = -0.; break; case WasmToken::Float: break; default: c.ts.generateError(token, c.error); return nullptr; } if (token.kind() != WasmToken::Float) return new (c.lifo) AstConst(Val(Raw(result))); const char16_t* begin = token.begin(); const char16_t* end = token.end(); const char16_t* cur = begin; bool isNegated = false; if (*cur == '-' || *cur == '+') isNegated = *cur++ == '-'; switch (token.floatLiteralKind()) { case WasmToken::Infinity: { result = PositiveInfinity(); break; } case WasmToken::NaN: { return ParseNaNLiteral(c, token, cur, isNegated); } case WasmToken::HexNumber: { if (!ParseHexFloatLiteral(cur, end, &result)) { c.ts.generateError(token, c.error); return nullptr; } break; } case WasmToken::DecNumber: { // Call into JS' strtod. Tokenization has already required that the // string is well-behaved. LifoAlloc::Mark mark = c.lifo.mark(); char* buffer = c.lifo.newArray(end - cur + 1); if (!buffer) return nullptr; for (ptrdiff_t i = 0; i < end - cur; ++i) buffer[i] = char(cur[i]); buffer[end - cur] = '\0'; char* strtod_end; int err; result = (Float)js_strtod_harder(c.dtoaState, buffer, &strtod_end, &err); if (err != 0 || strtod_end == buffer) { c.lifo.release(mark); c.ts.generateError(token, c.error); return nullptr; } c.lifo.release(mark); break; } } if (isNegated) result = -result; return new (c.lifo) AstConst(Val(Raw(result))); } static AstConst* ParseConst(WasmParseContext& c, WasmToken constToken) { WasmToken val = c.ts.get(); switch (constToken.valueType()) { case ValType::I32: { switch (val.kind()) { case WasmToken::Index: return new(c.lifo) AstConst(Val(val.index())); case WasmToken::SignedInteger: { CheckedInt sint = val.sint(); if (!sint.isValid()) break; return new(c.lifo) AstConst(Val(uint32_t(sint.value()))); } case WasmToken::NegativeZero: return new(c.lifo) AstConst(Val(uint32_t(0))); default: break; } break; } case ValType::I64: { switch (val.kind()) { case WasmToken::Index: return new(c.lifo) AstConst(Val(uint64_t(val.index()))); case WasmToken::UnsignedInteger: return new(c.lifo) AstConst(Val(val.uint())); case WasmToken::SignedInteger: return new(c.lifo) AstConst(Val(uint64_t(val.sint()))); case WasmToken::NegativeZero: return new(c.lifo) AstConst(Val(uint64_t(0))); default: break; } break; } case ValType::F32: { return ParseFloatLiteral(c, val); } case ValType::F64: { return ParseFloatLiteral(c, val); } default: break; } c.ts.generateError(constToken, c.error); return nullptr; } static AstGetLocal* ParseGetLocal(WasmParseContext& c) { AstRef local; if (!c.ts.matchRef(&local, c.error)) return nullptr; return new(c.lifo) AstGetLocal(local); } static AstGetGlobal* ParseGetGlobal(WasmParseContext& c) { AstRef local; if (!c.ts.matchRef(&local, c.error)) return nullptr; return new(c.lifo) AstGetGlobal(local); } static AstSetGlobal* ParseSetGlobal(WasmParseContext& c, bool inParens) { AstRef global; if (!c.ts.matchRef(&global, c.error)) return nullptr; AstExpr* value = ParseExpr(c, inParens); if (!value) return nullptr; return new(c.lifo) AstSetGlobal(global, *value); } static AstSetLocal* ParseSetLocal(WasmParseContext& c, bool inParens) { AstRef local; if (!c.ts.matchRef(&local, c.error)) return nullptr; AstExpr* value = ParseExpr(c, inParens); if (!value) return nullptr; return new(c.lifo) AstSetLocal(local, *value); } static AstTeeLocal* ParseTeeLocal(WasmParseContext& c, bool inParens) { AstRef local; if (!c.ts.matchRef(&local, c.error)) return nullptr; AstExpr* value = ParseExpr(c, inParens); if (!value) return nullptr; return new(c.lifo) AstTeeLocal(local, *value); } static AstReturn* ParseReturn(WasmParseContext& c, bool inParens) { AstExpr* maybeExpr = nullptr; if (c.ts.peek().kind() != WasmToken::CloseParen) { maybeExpr = ParseExpr(c, inParens); if (!maybeExpr) return nullptr; } return new(c.lifo) AstReturn(maybeExpr); } static AstUnaryOperator* ParseUnaryOperator(WasmParseContext& c, Op op, bool inParens) { AstExpr* operand = ParseExpr(c, inParens); if (!operand) return nullptr; return new(c.lifo) AstUnaryOperator(op, operand); } static AstBinaryOperator* ParseBinaryOperator(WasmParseContext& c, Op op, bool inParens) { AstExpr* lhs = ParseExpr(c, inParens); if (!lhs) return nullptr; AstExpr* rhs = ParseExpr(c, inParens); if (!rhs) return nullptr; return new(c.lifo) AstBinaryOperator(op, lhs, rhs); } static AstComparisonOperator* ParseComparisonOperator(WasmParseContext& c, Op op, bool inParens) { AstExpr* lhs = ParseExpr(c, inParens); if (!lhs) return nullptr; AstExpr* rhs = ParseExpr(c, inParens); if (!rhs) return nullptr; return new(c.lifo) AstComparisonOperator(op, lhs, rhs); } static AstTernaryOperator* ParseTernaryOperator(WasmParseContext& c, Op op, bool inParens) { AstExpr* op0 = ParseExpr(c, inParens); if (!op0) return nullptr; AstExpr* op1 = ParseExpr(c, inParens); if (!op1) return nullptr; AstExpr* op2 = ParseExpr(c, inParens); if (!op2) return nullptr; return new(c.lifo) AstTernaryOperator(op, op0, op1, op2); } static AstConversionOperator* ParseConversionOperator(WasmParseContext& c, Op op, bool inParens) { AstExpr* operand = ParseExpr(c, inParens); if (!operand) return nullptr; return new(c.lifo) AstConversionOperator(op, operand); } static AstDrop* ParseDrop(WasmParseContext& c, bool inParens) { AstExpr* value = ParseExpr(c, inParens); if (!value) return nullptr; return new(c.lifo) AstDrop(*value); } static AstIf* ParseIf(WasmParseContext& c, bool inParens) { AstName name = c.ts.getIfName(); ExprType type; if (!ParseBlockSignature(c, &type)) return nullptr; AstExpr* cond = ParseExpr(c, inParens); if (!cond) return nullptr; if (inParens) { if (!c.ts.match(WasmToken::OpenParen, c.error)) return nullptr; } AstExprVector thenExprs(c.lifo); if (!inParens || c.ts.getIf(WasmToken::Then)) { if (!ParseExprList(c, &thenExprs, inParens)) return nullptr; } else { AstExpr* thenBranch = ParseExprInsideParens(c); if (!thenBranch || !thenExprs.append(thenBranch)) return nullptr; } if (inParens) { if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } AstExprVector elseExprs(c.lifo); if (!inParens || c.ts.getIf(WasmToken::OpenParen)) { if (c.ts.getIf(WasmToken::Else)) { if (!ParseExprList(c, &elseExprs, inParens)) return nullptr; } else if (inParens) { AstExpr* elseBranch = ParseExprInsideParens(c); if (!elseBranch || !elseExprs.append(elseBranch)) return nullptr; } if (inParens) { if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } else { if (!c.ts.match(WasmToken::End, c.error)) return nullptr; } } return new(c.lifo) AstIf(type, cond, name, Move(thenExprs), Move(elseExprs)); } static bool ParseLoadStoreAddress(WasmParseContext& c, int32_t* offset, uint32_t* alignLog2, AstExpr** base, bool inParens) { *offset = 0; if (c.ts.getIf(WasmToken::Offset)) { if (!c.ts.match(WasmToken::Equal, c.error)) return false; WasmToken val = c.ts.get(); switch (val.kind()) { case WasmToken::Index: *offset = val.index(); break; default: c.ts.generateError(val, c.error); return false; } } *alignLog2 = UINT32_MAX; if (c.ts.getIf(WasmToken::Align)) { if (!c.ts.match(WasmToken::Equal, c.error)) return false; WasmToken val = c.ts.get(); switch (val.kind()) { case WasmToken::Index: if (!IsPowerOfTwo(val.index())) { c.ts.generateError(val, "non-power-of-two alignment", c.error); return false; } *alignLog2 = CeilingLog2(val.index()); break; default: c.ts.generateError(val, c.error); return false; } } *base = ParseExpr(c, inParens); if (!*base) return false; return true; } static AstLoad* ParseLoad(WasmParseContext& c, Op op, bool inParens) { int32_t offset; uint32_t alignLog2; AstExpr* base; if (!ParseLoadStoreAddress(c, &offset, &alignLog2, &base, inParens)) return nullptr; if (alignLog2 == UINT32_MAX) { switch (op) { case Op::I32Load8S: case Op::I32Load8U: case Op::I64Load8S: case Op::I64Load8U: alignLog2 = 0; break; case Op::I32Load16S: case Op::I32Load16U: case Op::I64Load16S: case Op::I64Load16U: alignLog2 = 1; break; case Op::I32Load: case Op::F32Load: case Op::I64Load32S: case Op::I64Load32U: alignLog2 = 2; break; case Op::I64Load: case Op::F64Load: alignLog2 = 3; break; default: MOZ_CRASH("Bad load op"); } } uint32_t flags = alignLog2; return new(c.lifo) AstLoad(op, AstLoadStoreAddress(base, flags, offset)); } static AstStore* ParseStore(WasmParseContext& c, Op op, bool inParens) { int32_t offset; uint32_t alignLog2; AstExpr* base; if (!ParseLoadStoreAddress(c, &offset, &alignLog2, &base, inParens)) return nullptr; if (alignLog2 == UINT32_MAX) { switch (op) { case Op::I32Store8: case Op::I64Store8: alignLog2 = 0; break; case Op::I32Store16: case Op::I64Store16: alignLog2 = 1; break; case Op::I32Store: case Op::F32Store: case Op::I64Store32: alignLog2 = 2; break; case Op::I64Store: case Op::F64Store: alignLog2 = 3; break; default: MOZ_CRASH("Bad load op"); } } AstExpr* value = ParseExpr(c, inParens); if (!value) return nullptr; uint32_t flags = alignLog2; return new(c.lifo) AstStore(op, AstLoadStoreAddress(base, flags, offset), value); } static AstBranchTable* ParseBranchTable(WasmParseContext& c, WasmToken brTable, bool inParens) { AstRefVector table(c.lifo); AstRef target; while (c.ts.getIfRef(&target)) { if (!table.append(target)) return nullptr; } if (table.empty()) { c.ts.generateError(c.ts.get(), c.error); return nullptr; } AstRef def = table.popCopy(); AstExpr* index = ParseExpr(c, inParens); if (!index) return nullptr; AstExpr* value = nullptr; if (inParens) { if (c.ts.getIf(WasmToken::OpenParen)) { value = index; index = ParseExprInsideParens(c); if (!index) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } } return new(c.lifo) AstBranchTable(*index, def, Move(table), value); } static AstGrowMemory* ParseGrowMemory(WasmParseContext& c, bool inParens) { AstExpr* operand = ParseExpr(c, inParens); if (!operand) return nullptr; return new(c.lifo) AstGrowMemory(operand); } static AstExpr* ParseExprBody(WasmParseContext& c, WasmToken token, bool inParens) { switch (token.kind()) { case WasmToken::Unreachable: return new(c.lifo) AstUnreachable; case WasmToken::BinaryOpcode: return ParseBinaryOperator(c, token.op(), inParens); case WasmToken::Block: return ParseBlock(c, Op::Block, inParens); case WasmToken::Br: return ParseBranch(c, Op::Br, inParens); case WasmToken::BrIf: return ParseBranch(c, Op::BrIf, inParens); case WasmToken::BrTable: return ParseBranchTable(c, token, inParens); case WasmToken::Call: return ParseCall(c, inParens); case WasmToken::CallIndirect: return ParseCallIndirect(c, inParens); case WasmToken::ComparisonOpcode: return ParseComparisonOperator(c, token.op(), inParens); case WasmToken::Const: return ParseConst(c, token); case WasmToken::ConversionOpcode: return ParseConversionOperator(c, token.op(), inParens); case WasmToken::Drop: return ParseDrop(c, inParens); case WasmToken::If: return ParseIf(c, inParens); case WasmToken::GetGlobal: return ParseGetGlobal(c); case WasmToken::GetLocal: return ParseGetLocal(c); case WasmToken::Load: return ParseLoad(c, token.op(), inParens); case WasmToken::Loop: return ParseBlock(c, Op::Loop, inParens); case WasmToken::Return: return ParseReturn(c, inParens); case WasmToken::SetGlobal: return ParseSetGlobal(c, inParens); case WasmToken::SetLocal: return ParseSetLocal(c, inParens); case WasmToken::Store: return ParseStore(c, token.op(), inParens); case WasmToken::TeeLocal: return ParseTeeLocal(c, inParens); case WasmToken::TernaryOpcode: return ParseTernaryOperator(c, token.op(), inParens); case WasmToken::UnaryOpcode: return ParseUnaryOperator(c, token.op(), inParens); case WasmToken::Nop: return new(c.lifo) AstNop(); case WasmToken::CurrentMemory: return new(c.lifo) AstCurrentMemory(); case WasmToken::GrowMemory: return ParseGrowMemory(c, inParens); default: c.ts.generateError(token, c.error); return nullptr; } } static AstExpr* ParseExprInsideParens(WasmParseContext& c) { WasmToken token = c.ts.get(); return ParseExprBody(c, token, true); } static bool ParseValueTypeList(WasmParseContext& c, AstValTypeVector* vec) { WasmToken token; while (c.ts.getIf(WasmToken::ValueType, &token)) { if (!vec->append(token.valueType())) return false; } return true; } static bool ParseResult(WasmParseContext& c, ExprType* result) { if (*result != ExprType::Void) { c.ts.generateError(c.ts.peek(), c.error); return false; } WasmToken token; if (!c.ts.match(WasmToken::ValueType, &token, c.error)) return false; *result = ToExprType(token.valueType()); return true; } static bool ParseLocalOrParam(WasmParseContext& c, AstNameVector* locals, AstValTypeVector* localTypes) { if (c.ts.peek().kind() != WasmToken::Name) return locals->append(AstName()) && ParseValueTypeList(c, localTypes); WasmToken token; return locals->append(c.ts.get().name()) && c.ts.match(WasmToken::ValueType, &token, c.error) && localTypes->append(token.valueType()); } static bool ParseInlineImport(WasmParseContext& c, InlineImport* import) { return c.ts.match(WasmToken::Text, &import->module, c.error) && c.ts.match(WasmToken::Text, &import->field, c.error); } static bool ParseInlineExport(WasmParseContext& c, DefinitionKind kind, AstModule* module, AstRef ref) { WasmToken name; if (!c.ts.match(WasmToken::Text, &name, c.error)) return false; AstExport* exp = new(c.lifo) AstExport(name.text(), kind, ref); return exp && module->append(exp); } static bool MaybeParseTypeUse(WasmParseContext& c, AstRef* sig) { WasmToken openParen; if (c.ts.getIf(WasmToken::OpenParen, &openParen)) { if (c.ts.getIf(WasmToken::Type)) { if (!c.ts.matchRef(sig, c.error)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } else { c.ts.unget(openParen); } } return true; } static bool ParseFuncSig(WasmParseContext& c, AstSig* sig) { AstValTypeVector args(c.lifo); ExprType result = ExprType::Void; while (c.ts.getIf(WasmToken::OpenParen)) { WasmToken token = c.ts.get(); switch (token.kind()) { case WasmToken::Param: if (!ParseValueTypeList(c, &args)) return false; break; case WasmToken::Result: if (!ParseResult(c, &result)) return false; break; default: c.ts.generateError(token, c.error); return false; } if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } *sig = AstSig(Move(args), result); return true; } static bool ParseFuncType(WasmParseContext& c, AstRef* ref, AstModule* module) { if (!MaybeParseTypeUse(c, ref)) return false; if (ref->isInvalid()) { AstSig sig(c.lifo); if (!ParseFuncSig(c, &sig)) return false; uint32_t sigIndex; if (!module->declare(Move(sig), &sigIndex)) return false; ref->setIndex(sigIndex); } return true; } static bool ParseFunc(WasmParseContext& c, AstModule* module) { AstValTypeVector vars(c.lifo); AstValTypeVector args(c.lifo); AstNameVector locals(c.lifo); AstName funcName = c.ts.getIfName(); // Inline imports and exports. WasmToken openParen; if (c.ts.getIf(WasmToken::OpenParen, &openParen)) { if (c.ts.getIf(WasmToken::Import)) { if (module->funcs().length()) { c.ts.generateError(openParen, "import after function definition", c.error); return false; } InlineImport names; if (!ParseInlineImport(c, &names)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; AstRef sig; if (!ParseFuncType(c, &sig, module)) return false; auto* imp = new(c.lifo) AstImport(funcName, names.module.text(), names.field.text(), sig); return imp && module->append(imp); } if (c.ts.getIf(WasmToken::Export)) { AstRef ref = funcName.empty() ? AstRef(module->funcImportNames().length() + module->funcs().length()) : AstRef(funcName); if (!ParseInlineExport(c, DefinitionKind::Function, module, ref)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } else { c.ts.unget(openParen); } } AstRef sigRef; if (!MaybeParseTypeUse(c, &sigRef)) return false; AstExprVector body(c.lifo); ExprType result = ExprType::Void; while (c.ts.getIf(WasmToken::OpenParen)) { WasmToken token = c.ts.get(); switch (token.kind()) { case WasmToken::Local: if (!ParseLocalOrParam(c, &locals, &vars)) return false; break; case WasmToken::Param: if (!vars.empty()) { c.ts.generateError(token, c.error); return false; } if (!ParseLocalOrParam(c, &locals, &args)) return false; break; case WasmToken::Result: if (!ParseResult(c, &result)) return false; break; default: c.ts.unget(token); AstExpr* expr = ParseExprInsideParens(c); if (!expr || !body.append(expr)) return false; break; } if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } if (!ParseExprList(c, &body, true)) return false; if (sigRef.isInvalid()) { uint32_t sigIndex; if (!module->declare(AstSig(Move(args), result), &sigIndex)) return false; sigRef.setIndex(sigIndex); } auto* func = new(c.lifo) AstFunc(funcName, sigRef, Move(vars), Move(locals), Move(body)); return func && module->append(func); } static AstSig* ParseTypeDef(WasmParseContext& c) { AstName name = c.ts.getIfName(); if (!c.ts.match(WasmToken::OpenParen, c.error)) return nullptr; if (!c.ts.match(WasmToken::Func, c.error)) return nullptr; AstSig sig(c.lifo); if (!ParseFuncSig(c, &sig)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return new(c.lifo) AstSig(name, Move(sig)); } static bool MaybeParseOwnerIndex(WasmParseContext& c) { if (c.ts.peek().kind() == WasmToken::Index) { WasmToken elemIndex = c.ts.get(); if (elemIndex.index()) { c.ts.generateError(elemIndex, "can't handle non-default memory/table yet", c.error); return false; } } return true; } static AstExpr* ParseInitializerExpression(WasmParseContext& c) { if (!c.ts.match(WasmToken::OpenParen, c.error)) return nullptr; AstExpr* initExpr = ParseExprInsideParens(c); if (!initExpr) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return initExpr; } static AstDataSegment* ParseDataSegment(WasmParseContext& c) { if (!MaybeParseOwnerIndex(c)) return nullptr; AstExpr* offset = ParseInitializerExpression(c); if (!offset) return nullptr; AstNameVector fragments(c.lifo); WasmToken text; while (c.ts.getIf(WasmToken::Text, &text)) { if (!fragments.append(text.text())) return nullptr; } return new(c.lifo) AstDataSegment(offset, Move(fragments)); } static bool ParseLimits(WasmParseContext& c, Limits* limits) { WasmToken initial; if (!c.ts.match(WasmToken::Index, &initial, c.error)) return false; Maybe maximum; WasmToken token; if (c.ts.getIf(WasmToken::Index, &token)) maximum.emplace(token.index()); Limits r = { initial.index(), maximum }; *limits = r; return true; } static bool ParseMemory(WasmParseContext& c, WasmToken token, AstModule* module) { AstName name = c.ts.getIfName(); WasmToken openParen; if (c.ts.getIf(WasmToken::OpenParen, &openParen)) { if (c.ts.getIf(WasmToken::Import)) { InlineImport names; if (!ParseInlineImport(c, &names)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; Limits memory; if (!ParseLimits(c, &memory)) return false; auto* imp = new(c.lifo) AstImport(name, names.module.text(), names.field.text(), DefinitionKind::Memory, memory); return imp && module->append(imp); } if (c.ts.getIf(WasmToken::Export)) { AstRef ref = name.empty() ? AstRef(module->memories().length()) : AstRef(name); if (!ParseInlineExport(c, DefinitionKind::Memory, module, ref)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } else { c.ts.unget(openParen); } } if (c.ts.getIf(WasmToken::OpenParen)) { if (!c.ts.match(WasmToken::Data, c.error)) return false; AstNameVector fragments(c.lifo); WasmToken data; size_t pages = 0; size_t totalLength = 0; while (c.ts.getIf(WasmToken::Text, &data)) { if (!fragments.append(data.text())) return false; totalLength += data.text().length(); } if (fragments.length()) { AstExpr* offset = new(c.lifo) AstConst(Val(uint32_t(0))); if (!offset) return false; AstDataSegment* segment = new(c.lifo) AstDataSegment(offset, Move(fragments)); if (!segment || !module->append(segment)) return false; pages = AlignBytes(totalLength, PageSize) / PageSize; if (pages != uint32_t(pages)) return false; } Limits memory = { uint32_t(pages), Some(uint32_t(pages)) }; if (!module->addMemory(name, memory)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; return true; } Limits memory; if (!ParseLimits(c, &memory)) return false; return module->addMemory(name, memory); } static bool ParseStartFunc(WasmParseContext& c, WasmToken token, AstModule* module) { AstRef func; if (!c.ts.matchRef(&func, c.error)) return false; if (!module->setStartFunc(AstStartFunc(func))) { c.ts.generateError(token, c.error); return false; } return true; } static bool ParseGlobalType(WasmParseContext& c, WasmToken* typeToken, bool* isMutable) { *isMutable = false; // Either (mut i32) or i32. if (c.ts.getIf(WasmToken::OpenParen)) { // Immutable by default. *isMutable = c.ts.getIf(WasmToken::Mutable); if (!c.ts.match(WasmToken::ValueType, typeToken, c.error)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; return true; } return c.ts.match(WasmToken::ValueType, typeToken, c.error); } static bool ParseElemType(WasmParseContext& c) { // Only AnyFunc is allowed at the moment. return c.ts.match(WasmToken::AnyFunc, c.error); } static bool ParseTableSig(WasmParseContext& c, Limits* table) { return ParseLimits(c, table) && ParseElemType(c); } static AstImport* ParseImport(WasmParseContext& c, AstModule* module) { AstName name = c.ts.getIfName(); WasmToken moduleName; if (!c.ts.match(WasmToken::Text, &moduleName, c.error)) return nullptr; WasmToken fieldName; if (!c.ts.match(WasmToken::Text, &fieldName, c.error)) return nullptr; AstRef sigRef; WasmToken openParen; if (c.ts.getIf(WasmToken::OpenParen, &openParen)) { if (c.ts.getIf(WasmToken::Memory)) { if (name.empty()) name = c.ts.getIfName(); Limits memory; if (!ParseLimits(c, &memory)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), DefinitionKind::Memory, memory); } if (c.ts.getIf(WasmToken::Table)) { if (name.empty()) name = c.ts.getIfName(); Limits table; if (!ParseTableSig(c, &table)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), DefinitionKind::Table, table); } if (c.ts.getIf(WasmToken::Global)) { if (name.empty()) name = c.ts.getIfName(); WasmToken typeToken; bool isMutable; if (!ParseGlobalType(c, &typeToken, &isMutable)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), AstGlobal(AstName(), typeToken.valueType(), isMutable)); } if (c.ts.getIf(WasmToken::Func)) { if (name.empty()) name = c.ts.getIfName(); AstRef sigRef; if (!ParseFuncType(c, &sigRef, module)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), sigRef); } if (c.ts.getIf(WasmToken::Type)) { if (!c.ts.matchRef(&sigRef, c.error)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } else { c.ts.unget(openParen); } } if (sigRef.isInvalid()) { AstSig sig(c.lifo); if (!ParseFuncSig(c, &sig)) return nullptr; uint32_t sigIndex; if (!module->declare(Move(sig), &sigIndex)) return nullptr; sigRef.setIndex(sigIndex); } return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(), sigRef); } static AstExport* ParseExport(WasmParseContext& c) { WasmToken name; if (!c.ts.match(WasmToken::Text, &name, c.error)) return nullptr; WasmToken exportee = c.ts.get(); switch (exportee.kind()) { case WasmToken::Index: return new(c.lifo) AstExport(name.text(), DefinitionKind::Function, AstRef(exportee.index())); case WasmToken::Name: return new(c.lifo) AstExport(name.text(), DefinitionKind::Function, AstRef(exportee.name())); case WasmToken::Table: { AstRef ref; if (!c.ts.getIfRef(&ref)) ref = AstRef(0); return new(c.lifo) AstExport(name.text(), DefinitionKind::Table, ref); } case WasmToken::Memory: { AstRef ref; if (!c.ts.getIfRef(&ref)) ref = AstRef(0); return new(c.lifo) AstExport(name.text(), DefinitionKind::Memory, ref); } case WasmToken::Global: { AstRef ref; if (!c.ts.matchRef(&ref, c.error)) return nullptr; return new(c.lifo) AstExport(name.text(), DefinitionKind::Global, ref); } case WasmToken::OpenParen: { exportee = c.ts.get(); DefinitionKind kind; switch (exportee.kind()) { case WasmToken::Func: kind = DefinitionKind::Function; break; case WasmToken::Table: kind = DefinitionKind::Table; break; case WasmToken::Memory: kind = DefinitionKind::Memory; break; case WasmToken::Global: kind = DefinitionKind::Global; break; default: c.ts.generateError(exportee, c.error); return nullptr; } AstRef ref; if (!c.ts.matchRef(&ref, c.error)) return nullptr; if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; return new(c.lifo) AstExport(name.text(), kind, ref); } default: break; } c.ts.generateError(exportee, c.error); return nullptr; } static bool ParseTable(WasmParseContext& c, WasmToken token, AstModule* module) { AstName name = c.ts.getIfName(); if (c.ts.getIf(WasmToken::OpenParen)) { // Either an import and we're done, or an export and continue. if (c.ts.getIf(WasmToken::Import)) { InlineImport names; if (!ParseInlineImport(c, &names)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; Limits table; if (!ParseTableSig(c, &table)) return false; auto* import = new(c.lifo) AstImport(name, names.module.text(), names.field.text(), DefinitionKind::Table, table); return import && module->append(import); } if (!c.ts.match(WasmToken::Export, c.error)) { c.ts.generateError(token, c.error); return false; } AstRef ref = name.empty() ? AstRef(module->tables().length()) : AstRef(name); if (!ParseInlineExport(c, DefinitionKind::Table, module, ref)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } // Either: min max? anyfunc if (c.ts.peek().kind() == WasmToken::Index) { Limits table; if (!ParseTableSig(c, &table)) return false; return module->addTable(name, table); } // Or: anyfunc (elem 1 2 ...) if (!ParseElemType(c)) return false; if (!c.ts.match(WasmToken::OpenParen, c.error)) return false; if (!c.ts.match(WasmToken::Elem, c.error)) return false; AstRefVector elems(c.lifo); AstRef elem; while (c.ts.getIfRef(&elem)) { if (!elems.append(elem)) return false; } if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; uint32_t numElements = uint32_t(elems.length()); if (numElements != elems.length()) return false; Limits r = { numElements, Some(numElements) }; if (!module->addTable(name, r)) return false; auto* zero = new(c.lifo) AstConst(Val(uint32_t(0))); if (!zero) return false; AstElemSegment* segment = new(c.lifo) AstElemSegment(zero, Move(elems)); return segment && module->append(segment); } static AstElemSegment* ParseElemSegment(WasmParseContext& c) { if (!MaybeParseOwnerIndex(c)) return nullptr; AstExpr* offset = ParseInitializerExpression(c); if (!offset) return nullptr; AstRefVector elems(c.lifo); AstRef elem; while (c.ts.getIfRef(&elem)) { if (!elems.append(elem)) return nullptr; } return new(c.lifo) AstElemSegment(offset, Move(elems)); } static bool ParseGlobal(WasmParseContext& c, AstModule* module) { AstName name = c.ts.getIfName(); WasmToken typeToken; bool isMutable; WasmToken openParen; if (c.ts.getIf(WasmToken::OpenParen, &openParen)) { if (c.ts.getIf(WasmToken::Import)) { if (module->globals().length()) { c.ts.generateError(openParen, "import after global definition", c.error); return false; } InlineImport names; if (!ParseInlineImport(c, &names)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; if (!ParseGlobalType(c, &typeToken, &isMutable)) return false; auto* imp = new(c.lifo) AstImport(name, names.module.text(), names.field.text(), AstGlobal(AstName(), typeToken.valueType(), isMutable)); return imp && module->append(imp); } if (c.ts.getIf(WasmToken::Export)) { AstRef ref = name.empty() ? AstRef(module->globals().length()) : AstRef(name); if (!ParseInlineExport(c, DefinitionKind::Global, module, ref)) return false; if (!c.ts.match(WasmToken::CloseParen, c.error)) return false; } else { c.ts.unget(openParen); } } if (!ParseGlobalType(c, &typeToken, &isMutable)) return false; AstExpr* init = ParseInitializerExpression(c); if (!init) return false; auto* glob = new(c.lifo) AstGlobal(name, typeToken.valueType(), isMutable, Some(init)); return glob && module->append(glob); } static AstModule* ParseBinaryModule(WasmParseContext& c, AstModule* module) { // By convention with EncodeBinaryModule, a binary module only contains a // data section containing the raw bytes contained in the module. AstNameVector fragments(c.lifo); WasmToken text; while (c.ts.getIf(WasmToken::Text, &text)) { if (!fragments.append(text.text())) return nullptr; } auto* data = new(c.lifo) AstDataSegment(nullptr, Move(fragments)); if (!data || !module->append(data)) return nullptr; return module; } static AstModule* ParseModule(const char16_t* text, LifoAlloc& lifo, UniqueChars* error, bool* binary) { WasmParseContext c(text, lifo, error); *binary = false; if (!c.ts.match(WasmToken::OpenParen, c.error)) return nullptr; if (!c.ts.match(WasmToken::Module, c.error)) return nullptr; auto* module = new(c.lifo) AstModule(c.lifo); if (!module || !module->init()) return nullptr; if (c.ts.peek().kind() == WasmToken::Text) { *binary = true; return ParseBinaryModule(c, module); } while (c.ts.getIf(WasmToken::OpenParen)) { WasmToken section = c.ts.get(); switch (section.kind()) { case WasmToken::Type: { AstSig* sig = ParseTypeDef(c); if (!sig || !module->append(sig)) return nullptr; break; } case WasmToken::Start: { if (!ParseStartFunc(c, section, module)) return nullptr; break; } case WasmToken::Memory: { if (!ParseMemory(c, section, module)) return nullptr; break; } case WasmToken::Global: { if (!ParseGlobal(c, module)) return nullptr; break; } case WasmToken::Data: { AstDataSegment* segment = ParseDataSegment(c); if (!segment || !module->append(segment)) return nullptr; break; } case WasmToken::Import: { AstImport* imp = ParseImport(c, module); if (!imp || !module->append(imp)) return nullptr; break; } case WasmToken::Export: { AstExport* exp = ParseExport(c); if (!exp || !module->append(exp)) return nullptr; break; } case WasmToken::Table: { if (!ParseTable(c, section, module)) return nullptr; break; } case WasmToken::Elem: { AstElemSegment* segment = ParseElemSegment(c); if (!segment || !module->append(segment)) return nullptr; break; } case WasmToken::Func: { if (!ParseFunc(c, module)) return nullptr; break; } default: c.ts.generateError(section, c.error); return nullptr; } if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; } if (!c.ts.match(WasmToken::CloseParen, c.error)) return nullptr; if (!c.ts.match(WasmToken::EndOfFile, c.error)) return nullptr; return module; } /*****************************************************************************/ // wasm name resolution namespace { class Resolver { UniqueChars* error_; AstNameMap varMap_; AstNameMap globalMap_; AstNameMap sigMap_; AstNameMap funcMap_; AstNameMap importMap_; AstNameMap tableMap_; AstNameMap memoryMap_; AstNameVector targetStack_; bool registerName(AstNameMap& map, AstName name, size_t index) { AstNameMap::AddPtr p = map.lookupForAdd(name); if (!p) { if (!map.add(p, name, index)) return false; } else { return false; } return true; } bool resolveName(AstNameMap& map, AstName name, size_t* index) { AstNameMap::Ptr p = map.lookup(name); if (p) { *index = p->value(); return true; } return false; } bool resolveRef(AstNameMap& map, AstRef& ref) { AstNameMap::Ptr p = map.lookup(ref.name()); if (p) { ref.setIndex(p->value()); return true; } return false; } bool failResolveLabel(const char* kind, AstName name) { TwoByteChars chars(name.begin(), name.length()); UniqueChars utf8Chars(CharsToNewUTF8CharsZ(nullptr, chars).c_str()); error_->reset(JS_smprintf("%s label '%s' not found", kind, utf8Chars.get())); return false; } public: explicit Resolver(LifoAlloc& lifo, UniqueChars* error) : error_(error), varMap_(lifo), globalMap_(lifo), sigMap_(lifo), funcMap_(lifo), importMap_(lifo), tableMap_(lifo), memoryMap_(lifo), targetStack_(lifo) {} bool init() { return sigMap_.init() && funcMap_.init() && importMap_.init() && tableMap_.init() && memoryMap_.init() && varMap_.init() && globalMap_.init(); } void beginFunc() { varMap_.clear(); MOZ_ASSERT(targetStack_.empty()); } #define REGISTER(what, map) \ bool register##what##Name(AstName name, size_t index) { \ return name.empty() || registerName(map, name, index); \ } REGISTER(Sig, sigMap_) REGISTER(Func, funcMap_) REGISTER(Import, importMap_) REGISTER(Var, varMap_) REGISTER(Global, globalMap_) REGISTER(Table, tableMap_) REGISTER(Memory, memoryMap_) #undef REGISTER bool pushTarget(AstName name) { return targetStack_.append(name); } void popTarget(AstName name) { MOZ_ASSERT(targetStack_.back() == name); targetStack_.popBack(); } #define RESOLVE(map, label) \ bool resolve##label(AstRef& ref) { \ MOZ_ASSERT(!ref.isInvalid()); \ if (!ref.name().empty() && !resolveRef(map, ref)) \ return failResolveLabel(#label, ref.name()); \ return true; \ } RESOLVE(sigMap_, Signature) RESOLVE(funcMap_, Function) RESOLVE(importMap_, Import) RESOLVE(varMap_, Local) RESOLVE(globalMap_, Global) RESOLVE(tableMap_, Table) RESOLVE(memoryMap_, Memory) #undef RESOLVE bool resolveBranchTarget(AstRef& ref) { if (ref.name().empty()) return true; for (size_t i = 0, e = targetStack_.length(); i < e; i++) { if (targetStack_[e - i - 1] == ref.name()) { ref.setIndex(i); return true; } } return failResolveLabel("branch target", ref.name()); } bool fail(const char* message) { error_->reset(JS_smprintf("%s", message)); return false; } }; } // end anonymous namespace static bool ResolveExpr(Resolver& r, AstExpr& expr); static bool ResolveExprList(Resolver& r, const AstExprVector& v) { for (size_t i = 0; i < v.length(); i++) { if (!ResolveExpr(r, *v[i])) return false; } return true; } static bool ResolveBlock(Resolver& r, AstBlock& b) { if (!r.pushTarget(b.name())) return false; if (!ResolveExprList(r, b.exprs())) return false; r.popTarget(b.name()); return true; } static bool ResolveDropOperator(Resolver& r, AstDrop& drop) { return ResolveExpr(r, drop.value()); } static bool ResolveBranch(Resolver& r, AstBranch& br) { if (!r.resolveBranchTarget(br.target())) return false; if (br.maybeValue() && !ResolveExpr(r, *br.maybeValue())) return false; if (br.op() == Op::BrIf) { if (!ResolveExpr(r, br.cond())) return false; } return true; } static bool ResolveArgs(Resolver& r, const AstExprVector& args) { for (AstExpr* arg : args) { if (!ResolveExpr(r, *arg)) return false; } return true; } static bool ResolveCall(Resolver& r, AstCall& c) { MOZ_ASSERT(c.op() == Op::Call); if (!ResolveArgs(r, c.args())) return false; if (!r.resolveFunction(c.func())) return false; return true; } static bool ResolveCallIndirect(Resolver& r, AstCallIndirect& c) { if (!ResolveArgs(r, c.args())) return false; if (!ResolveExpr(r, *c.index())) return false; if (!r.resolveSignature(c.sig())) return false; return true; } static bool ResolveFirst(Resolver& r, AstFirst& f) { return ResolveExprList(r, f.exprs()); } static bool ResolveGetLocal(Resolver& r, AstGetLocal& gl) { return r.resolveLocal(gl.local()); } static bool ResolveSetLocal(Resolver& r, AstSetLocal& sl) { if (!ResolveExpr(r, sl.value())) return false; if (!r.resolveLocal(sl.local())) return false; return true; } static bool ResolveGetGlobal(Resolver& r, AstGetGlobal& gl) { return r.resolveGlobal(gl.global()); } static bool ResolveSetGlobal(Resolver& r, AstSetGlobal& sl) { if (!ResolveExpr(r, sl.value())) return false; if (!r.resolveGlobal(sl.global())) return false; return true; } static bool ResolveTeeLocal(Resolver& r, AstTeeLocal& sl) { if (!ResolveExpr(r, sl.value())) return false; if (!r.resolveLocal(sl.local())) return false; return true; } static bool ResolveUnaryOperator(Resolver& r, AstUnaryOperator& b) { return ResolveExpr(r, *b.operand()); } static bool ResolveGrowMemory(Resolver& r, AstGrowMemory& gm) { return ResolveExpr(r, *gm.operand()); } static bool ResolveBinaryOperator(Resolver& r, AstBinaryOperator& b) { return ResolveExpr(r, *b.lhs()) && ResolveExpr(r, *b.rhs()); } static bool ResolveTernaryOperator(Resolver& r, AstTernaryOperator& b) { return ResolveExpr(r, *b.op0()) && ResolveExpr(r, *b.op1()) && ResolveExpr(r, *b.op2()); } static bool ResolveComparisonOperator(Resolver& r, AstComparisonOperator& b) { return ResolveExpr(r, *b.lhs()) && ResolveExpr(r, *b.rhs()); } static bool ResolveConversionOperator(Resolver& r, AstConversionOperator& b) { return ResolveExpr(r, *b.operand()); } static bool ResolveIfElse(Resolver& r, AstIf& i) { if (!ResolveExpr(r, i.cond())) return false; if (!r.pushTarget(i.name())) return false; if (!ResolveExprList(r, i.thenExprs())) return false; if (i.hasElse()) { if (!ResolveExprList(r, i.elseExprs())) return false; } r.popTarget(i.name()); return true; } static bool ResolveLoadStoreAddress(Resolver& r, const AstLoadStoreAddress &address) { return ResolveExpr(r, address.base()); } static bool ResolveLoad(Resolver& r, AstLoad& l) { return ResolveLoadStoreAddress(r, l.address()); } static bool ResolveStore(Resolver& r, AstStore& s) { return ResolveLoadStoreAddress(r, s.address()) && ResolveExpr(r, s.value()); } static bool ResolveReturn(Resolver& r, AstReturn& ret) { return !ret.maybeExpr() || ResolveExpr(r, *ret.maybeExpr()); } static bool ResolveBranchTable(Resolver& r, AstBranchTable& bt) { if (!r.resolveBranchTarget(bt.def())) return false; for (AstRef& elem : bt.table()) { if (!r.resolveBranchTarget(elem)) return false; } if (bt.maybeValue() && !ResolveExpr(r, *bt.maybeValue())) return false; return ResolveExpr(r, bt.index()); } static bool ResolveExpr(Resolver& r, AstExpr& expr) { switch (expr.kind()) { case AstExprKind::Nop: case AstExprKind::Pop: case AstExprKind::Unreachable: case AstExprKind::CurrentMemory: return true; case AstExprKind::Drop: return ResolveDropOperator(r, expr.as()); case AstExprKind::BinaryOperator: return ResolveBinaryOperator(r, expr.as()); case AstExprKind::Block: return ResolveBlock(r, expr.as()); case AstExprKind::Branch: return ResolveBranch(r, expr.as()); case AstExprKind::Call: return ResolveCall(r, expr.as()); case AstExprKind::CallIndirect: return ResolveCallIndirect(r, expr.as()); case AstExprKind::ComparisonOperator: return ResolveComparisonOperator(r, expr.as()); case AstExprKind::Const: return true; case AstExprKind::ConversionOperator: return ResolveConversionOperator(r, expr.as()); case AstExprKind::First: return ResolveFirst(r, expr.as()); case AstExprKind::GetGlobal: return ResolveGetGlobal(r, expr.as()); case AstExprKind::GetLocal: return ResolveGetLocal(r, expr.as()); case AstExprKind::If: return ResolveIfElse(r, expr.as()); case AstExprKind::Load: return ResolveLoad(r, expr.as()); case AstExprKind::Return: return ResolveReturn(r, expr.as()); case AstExprKind::SetGlobal: return ResolveSetGlobal(r, expr.as()); case AstExprKind::SetLocal: return ResolveSetLocal(r, expr.as()); case AstExprKind::Store: return ResolveStore(r, expr.as()); case AstExprKind::BranchTable: return ResolveBranchTable(r, expr.as()); case AstExprKind::TeeLocal: return ResolveTeeLocal(r, expr.as()); case AstExprKind::TernaryOperator: return ResolveTernaryOperator(r, expr.as()); case AstExprKind::UnaryOperator: return ResolveUnaryOperator(r, expr.as()); case AstExprKind::GrowMemory: return ResolveGrowMemory(r, expr.as()); } MOZ_CRASH("Bad expr kind"); } static bool ResolveFunc(Resolver& r, AstFunc& func) { r.beginFunc(); for (size_t i = 0; i < func.locals().length(); i++) { if (!r.registerVarName(func.locals()[i], i)) return r.fail("duplicate var"); } for (AstExpr* expr : func.body()) { if (!ResolveExpr(r, *expr)) return false; } return true; } static bool ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error) { Resolver r(lifo, error); if (!r.init()) return false; size_t numSigs = module->sigs().length(); for (size_t i = 0; i < numSigs; i++) { AstSig* sig = module->sigs()[i]; if (!r.registerSigName(sig->name(), i)) return r.fail("duplicate signature"); } size_t lastFuncIndex = 0; size_t lastGlobalIndex = 0; size_t lastMemoryIndex = 0; size_t lastTableIndex = 0; for (AstImport* imp : module->imports()) { switch (imp->kind()) { case DefinitionKind::Function: if (!r.registerFuncName(imp->name(), lastFuncIndex++)) return r.fail("duplicate import"); if (!r.resolveSignature(imp->funcSig())) return false; break; case DefinitionKind::Global: if (!r.registerGlobalName(imp->name(), lastGlobalIndex++)) return r.fail("duplicate import"); break; case DefinitionKind::Memory: if (!r.registerMemoryName(imp->name(), lastMemoryIndex++)) return r.fail("duplicate import"); break; case DefinitionKind::Table: if (!r.registerTableName(imp->name(), lastTableIndex++)) return r.fail("duplicate import"); break; } } for (AstFunc* func : module->funcs()) { if (!r.resolveSignature(func->sig())) return false; if (!r.registerFuncName(func->name(), lastFuncIndex++)) return r.fail("duplicate function"); } for (const AstGlobal* global : module->globals()) { if (!r.registerGlobalName(global->name(), lastGlobalIndex++)) return r.fail("duplicate import"); if (global->hasInit() && !ResolveExpr(r, global->init())) return false; } for (const AstResizable& table : module->tables()) { if (table.imported) continue; if (!r.registerTableName(table.name, lastTableIndex++)) return r.fail("duplicate import"); } for (const AstResizable& memory : module->memories()) { if (memory.imported) continue; if (!r.registerMemoryName(memory.name, lastMemoryIndex++)) return r.fail("duplicate import"); } for (AstExport* export_ : module->exports()) { switch (export_->kind()) { case DefinitionKind::Function: if (!r.resolveFunction(export_->ref())) return false; break; case DefinitionKind::Global: if (!r.resolveGlobal(export_->ref())) return false; break; case DefinitionKind::Table: if (!r.resolveTable(export_->ref())) return false; break; case DefinitionKind::Memory: if (!r.resolveMemory(export_->ref())) return false; break; } } for (AstFunc* func : module->funcs()) { if (!ResolveFunc(r, *func)) return false; } if (module->hasStartFunc()) { if (!r.resolveFunction(module->startFunc().func())) return false; } for (AstDataSegment* segment : module->dataSegments()) { if (!ResolveExpr(r, *segment->offset())) return false; } for (AstElemSegment* segment : module->elemSegments()) { if (!ResolveExpr(r, *segment->offset())) return false; for (AstRef& ref : segment->elems()) { if (!r.resolveFunction(ref)) return false; } } return true; } /*****************************************************************************/ // wasm function body serialization static bool EncodeExpr(Encoder& e, AstExpr& expr); static bool EncodeExprList(Encoder& e, const AstExprVector& v) { for (size_t i = 0; i < v.length(); i++) { if (!EncodeExpr(e, *v[i])) return false; } return true; } static bool EncodeBlock(Encoder& e, AstBlock& b) { if (!e.writeOp(b.op())) return false; if (!e.writeBlockType(b.type())) return false; if (!EncodeExprList(e, b.exprs())) return false; if (!e.writeOp(Op::End)) return false; return true; } static bool EncodeBranch(Encoder& e, AstBranch& br) { MOZ_ASSERT(br.op() == Op::Br || br.op() == Op::BrIf); if (br.maybeValue()) { if (!EncodeExpr(e, *br.maybeValue())) return false; } if (br.op() == Op::BrIf) { if (!EncodeExpr(e, br.cond())) return false; } if (!e.writeOp(br.op())) return false; if (!e.writeVarU32(br.target().index())) return false; return true; } static bool EncodeFirst(Encoder& e, AstFirst& f) { return EncodeExprList(e, f.exprs()); } static bool EncodeArgs(Encoder& e, const AstExprVector& args) { for (AstExpr* arg : args) { if (!EncodeExpr(e, *arg)) return false; } return true; } static bool EncodeCall(Encoder& e, AstCall& c) { if (!EncodeArgs(e, c.args())) return false; if (!e.writeOp(c.op())) return false; if (!e.writeVarU32(c.func().index())) return false; return true; } static bool EncodeCallIndirect(Encoder& e, AstCallIndirect& c) { if (!EncodeArgs(e, c.args())) return false; if (!EncodeExpr(e, *c.index())) return false; if (!e.writeOp(Op::CallIndirect)) return false; if (!e.writeVarU32(c.sig().index())) return false; if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default))) return false; return true; } static bool EncodeConst(Encoder& e, AstConst& c) { switch (c.val().type()) { case ValType::I32: return e.writeOp(Op::I32Const) && e.writeVarS32(c.val().i32()); case ValType::I64: return e.writeOp(Op::I64Const) && e.writeVarS64(c.val().i64()); case ValType::F32: return e.writeOp(Op::F32Const) && e.writeFixedF32(c.val().f32()); case ValType::F64: return e.writeOp(Op::F64Const) && e.writeFixedF64(c.val().f64()); default: break; } MOZ_CRASH("Bad value type"); } static bool EncodeDrop(Encoder& e, AstDrop &drop) { return EncodeExpr(e, drop.value()) && e.writeOp(Op::Drop); } static bool EncodeGetLocal(Encoder& e, AstGetLocal& gl) { return e.writeOp(Op::GetLocal) && e.writeVarU32(gl.local().index()); } static bool EncodeSetLocal(Encoder& e, AstSetLocal& sl) { return EncodeExpr(e, sl.value()) && e.writeOp(Op::SetLocal) && e.writeVarU32(sl.local().index()); } static bool EncodeTeeLocal(Encoder& e, AstTeeLocal& sl) { return EncodeExpr(e, sl.value()) && e.writeOp(Op::TeeLocal) && e.writeVarU32(sl.local().index()); } static bool EncodeGetGlobal(Encoder& e, AstGetGlobal& gg) { return e.writeOp(Op::GetGlobal) && e.writeVarU32(gg.global().index()); } static bool EncodeSetGlobal(Encoder& e, AstSetGlobal& sg) { return EncodeExpr(e, sg.value()) && e.writeOp(Op::SetGlobal) && e.writeVarU32(sg.global().index()); } static bool EncodeUnaryOperator(Encoder& e, AstUnaryOperator& b) { return EncodeExpr(e, *b.operand()) && e.writeOp(b.op()); } static bool EncodeBinaryOperator(Encoder& e, AstBinaryOperator& b) { return EncodeExpr(e, *b.lhs()) && EncodeExpr(e, *b.rhs()) && e.writeOp(b.op()); } static bool EncodeTernaryOperator(Encoder& e, AstTernaryOperator& b) { return EncodeExpr(e, *b.op0()) && EncodeExpr(e, *b.op1()) && EncodeExpr(e, *b.op2()) && e.writeOp(b.op()); } static bool EncodeComparisonOperator(Encoder& e, AstComparisonOperator& b) { return EncodeExpr(e, *b.lhs()) && EncodeExpr(e, *b.rhs()) && e.writeOp(b.op()); } static bool EncodeConversionOperator(Encoder& e, AstConversionOperator& b) { return EncodeExpr(e, *b.operand()) && e.writeOp(b.op()); } static bool EncodeIf(Encoder& e, AstIf& i) { if (!EncodeExpr(e, i.cond()) || !e.writeOp(Op::If)) return false; if (!e.writeBlockType(i.type())) return false; if (!EncodeExprList(e, i.thenExprs())) return false; if (i.hasElse()) { if (!e.writeOp(Op::Else)) return false; if (!EncodeExprList(e, i.elseExprs())) return false; } return e.writeOp(Op::End); } static bool EncodeLoadStoreAddress(Encoder &e, const AstLoadStoreAddress &address) { return EncodeExpr(e, address.base()); } static bool EncodeLoadStoreFlags(Encoder &e, const AstLoadStoreAddress &address) { return e.writeVarU32(address.flags()) && e.writeVarU32(address.offset()); } static bool EncodeLoad(Encoder& e, AstLoad& l) { return EncodeLoadStoreAddress(e, l.address()) && e.writeOp(l.op()) && EncodeLoadStoreFlags(e, l.address()); } static bool EncodeStore(Encoder& e, AstStore& s) { return EncodeLoadStoreAddress(e, s.address()) && EncodeExpr(e, s.value()) && e.writeOp(s.op()) && EncodeLoadStoreFlags(e, s.address()); } static bool EncodeReturn(Encoder& e, AstReturn& r) { if (r.maybeExpr()) { if (!EncodeExpr(e, *r.maybeExpr())) return false; } if (!e.writeOp(Op::Return)) return false; return true; } static bool EncodeBranchTable(Encoder& e, AstBranchTable& bt) { if (bt.maybeValue()) { if (!EncodeExpr(e, *bt.maybeValue())) return false; } if (!EncodeExpr(e, bt.index())) return false; if (!e.writeOp(Op::BrTable)) return false; if (!e.writeVarU32(bt.table().length())) return false; for (const AstRef& elem : bt.table()) { if (!e.writeVarU32(elem.index())) return false; } if (!e.writeVarU32(bt.def().index())) return false; return true; } static bool EncodeCurrentMemory(Encoder& e, AstCurrentMemory& cm) { if (!e.writeOp(Op::CurrentMemory)) return false; if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default))) return false; return true; } static bool EncodeGrowMemory(Encoder& e, AstGrowMemory& gm) { if (!EncodeExpr(e, *gm.operand())) return false; if (!e.writeOp(Op::GrowMemory)) return false; if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default))) return false; return true; } static bool EncodeExpr(Encoder& e, AstExpr& expr) { switch (expr.kind()) { case AstExprKind::Pop: return true; case AstExprKind::Nop: return e.writeOp(Op::Nop); case AstExprKind::Unreachable: return e.writeOp(Op::Unreachable); case AstExprKind::BinaryOperator: return EncodeBinaryOperator(e, expr.as()); case AstExprKind::Block: return EncodeBlock(e, expr.as()); case AstExprKind::Branch: return EncodeBranch(e, expr.as()); case AstExprKind::Call: return EncodeCall(e, expr.as()); case AstExprKind::CallIndirect: return EncodeCallIndirect(e, expr.as()); case AstExprKind::ComparisonOperator: return EncodeComparisonOperator(e, expr.as()); case AstExprKind::Const: return EncodeConst(e, expr.as()); case AstExprKind::ConversionOperator: return EncodeConversionOperator(e, expr.as()); case AstExprKind::Drop: return EncodeDrop(e, expr.as()); case AstExprKind::First: return EncodeFirst(e, expr.as()); case AstExprKind::GetLocal: return EncodeGetLocal(e, expr.as()); case AstExprKind::GetGlobal: return EncodeGetGlobal(e, expr.as()); case AstExprKind::If: return EncodeIf(e, expr.as()); case AstExprKind::Load: return EncodeLoad(e, expr.as()); case AstExprKind::Return: return EncodeReturn(e, expr.as()); case AstExprKind::SetLocal: return EncodeSetLocal(e, expr.as()); case AstExprKind::TeeLocal: return EncodeTeeLocal(e, expr.as()); case AstExprKind::SetGlobal: return EncodeSetGlobal(e, expr.as()); case AstExprKind::Store: return EncodeStore(e, expr.as()); case AstExprKind::BranchTable: return EncodeBranchTable(e, expr.as()); case AstExprKind::TernaryOperator: return EncodeTernaryOperator(e, expr.as()); case AstExprKind::UnaryOperator: return EncodeUnaryOperator(e, expr.as()); case AstExprKind::CurrentMemory: return EncodeCurrentMemory(e, expr.as()); case AstExprKind::GrowMemory: return EncodeGrowMemory(e, expr.as()); } MOZ_CRASH("Bad expr kind"); } /*****************************************************************************/ // wasm AST binary serialization static bool EncodeTypeSection(Encoder& e, AstModule& module) { if (module.sigs().empty()) return true; size_t offset; if (!e.startSection(SectionId::Type, &offset)) return false; if (!e.writeVarU32(module.sigs().length())) return false; for (AstSig* sig : module.sigs()) { if (!e.writeVarU32(uint32_t(TypeCode::Func))) return false; if (!e.writeVarU32(sig->args().length())) return false; for (ValType t : sig->args()) { if (!e.writeValType(t)) return false; } if (!e.writeVarU32(!IsVoid(sig->ret()))) return false; if (!IsVoid(sig->ret())) { if (!e.writeValType(NonVoidToValType(sig->ret()))) return false; } } e.finishSection(offset); return true; } static bool EncodeFunctionSection(Encoder& e, AstModule& module) { if (module.funcs().empty()) return true; size_t offset; if (!e.startSection(SectionId::Function, &offset)) return false; if (!e.writeVarU32(module.funcs().length())) return false; for (AstFunc* func : module.funcs()) { if (!e.writeVarU32(func->sig().index())) return false; } e.finishSection(offset); return true; } static bool EncodeBytes(Encoder& e, AstName wasmName) { TwoByteChars range(wasmName.begin(), wasmName.length()); UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str()); return utf8 && e.writeBytes(utf8.get(), strlen(utf8.get())); } static bool EncodeLimits(Encoder& e, const Limits& limits) { uint32_t flags = limits.maximum ? 1 : 0; if (!e.writeVarU32(flags)) return false; if (!e.writeVarU32(limits.initial)) return false; if (limits.maximum) { if (!e.writeVarU32(*limits.maximum)) return false; } return true; } static bool EncodeTableLimits(Encoder& e, const Limits& limits) { if (!e.writeVarU32(uint32_t(TypeCode::AnyFunc))) return false; return EncodeLimits(e, limits); } static bool EncodeGlobalType(Encoder& e, const AstGlobal* global) { return e.writeValType(global->type()) && e.writeVarU32(global->isMutable() ? uint32_t(GlobalTypeImmediate::IsMutable) : 0); } static bool EncodeImport(Encoder& e, AstImport& imp) { if (!EncodeBytes(e, imp.module())) return false; if (!EncodeBytes(e, imp.field())) return false; if (!e.writeVarU32(uint32_t(imp.kind()))) return false; switch (imp.kind()) { case DefinitionKind::Function: if (!e.writeVarU32(imp.funcSig().index())) return false; break; case DefinitionKind::Global: MOZ_ASSERT(!imp.global().hasInit()); if (!EncodeGlobalType(e, &imp.global())) return false; break; case DefinitionKind::Table: if (!EncodeTableLimits(e, imp.limits())) return false; break; case DefinitionKind::Memory: if (!EncodeLimits(e, imp.limits())) return false; break; } return true; } static bool EncodeImportSection(Encoder& e, AstModule& module) { if (module.imports().empty()) return true; size_t offset; if (!e.startSection(SectionId::Import, &offset)) return false; if (!e.writeVarU32(module.imports().length())) return false; for (AstImport* imp : module.imports()) { if (!EncodeImport(e, *imp)) return false; } e.finishSection(offset); return true; } static bool EncodeMemorySection(Encoder& e, AstModule& module) { size_t numOwnMemories = 0; for (const AstResizable& memory : module.memories()) { if (!memory.imported) numOwnMemories++; } if (!numOwnMemories) return true; size_t offset; if (!e.startSection(SectionId::Memory, &offset)) return false; if (!e.writeVarU32(numOwnMemories)) return false; for (const AstResizable& memory : module.memories()) { if (memory.imported) continue; if (!EncodeLimits(e, memory.limits)) return false; } e.finishSection(offset); return true; } static bool EncodeGlobalSection(Encoder& e, AstModule& module) { size_t offset; if (!e.startSection(SectionId::Global, &offset)) return false; const AstGlobalVector& globals = module.globals(); if (!e.writeVarU32(globals.length())) return false; for (const AstGlobal* global : globals) { MOZ_ASSERT(global->hasInit()); if (!EncodeGlobalType(e, global)) return false; if (!EncodeExpr(e, global->init())) return false; if (!e.writeOp(Op::End)) return false; } e.finishSection(offset); return true; } static bool EncodeExport(Encoder& e, AstExport& exp) { if (!EncodeBytes(e, exp.name())) return false; if (!e.writeVarU32(uint32_t(exp.kind()))) return false; if (!e.writeVarU32(exp.ref().index())) return false; return true; } static bool EncodeExportSection(Encoder& e, AstModule& module) { uint32_t numExports = module.exports().length(); if (!numExports) return true; size_t offset; if (!e.startSection(SectionId::Export, &offset)) return false; if (!e.writeVarU32(numExports)) return false; for (AstExport* exp : module.exports()) { if (!EncodeExport(e, *exp)) return false; } e.finishSection(offset); return true; } static bool EncodeTableSection(Encoder& e, AstModule& module) { size_t numOwnTables = 0; for (const AstResizable& table : module.tables()) { if (!table.imported) numOwnTables++; } if (!numOwnTables) return true; size_t offset; if (!e.startSection(SectionId::Table, &offset)) return false; if (!e.writeVarU32(numOwnTables)) return false; for (const AstResizable& table : module.tables()) { if (table.imported) continue; if (!EncodeTableLimits(e, table.limits)) return false; } e.finishSection(offset); return true; } static bool EncodeFunctionBody(Encoder& e, AstFunc& func) { size_t bodySizeAt; if (!e.writePatchableVarU32(&bodySizeAt)) return false; size_t beforeBody = e.currentOffset(); ValTypeVector varTypes; if (!varTypes.appendAll(func.vars())) return false; if (!EncodeLocalEntries(e, varTypes)) return false; for (AstExpr* expr : func.body()) { if (!EncodeExpr(e, *expr)) return false; } if (!e.writeOp(Op::End)) return false; e.patchVarU32(bodySizeAt, e.currentOffset() - beforeBody); return true; } static bool EncodeStartSection(Encoder& e, AstModule& module) { if (!module.hasStartFunc()) return true; size_t offset; if (!e.startSection(SectionId::Start, &offset)) return false; if (!e.writeVarU32(module.startFunc().func().index())) return false; e.finishSection(offset); return true; } static bool EncodeCodeSection(Encoder& e, AstModule& module) { if (module.funcs().empty()) return true; size_t offset; if (!e.startSection(SectionId::Code, &offset)) return false; if (!e.writeVarU32(module.funcs().length())) return false; for (AstFunc* func : module.funcs()) { if (!EncodeFunctionBody(e, *func)) return false; } e.finishSection(offset); return true; } static bool EncodeDataSegment(Encoder& e, const AstDataSegment& segment) { if (!e.writeVarU32(0)) // linear memory index return false; if (!EncodeExpr(e, *segment.offset())) return false; if (!e.writeOp(Op::End)) return false; size_t totalLength = 0; for (const AstName& fragment : segment.fragments()) totalLength += fragment.length(); Vector bytes; if (!bytes.reserve(totalLength)) return false; for (const AstName& fragment : segment.fragments()) { const char16_t* cur = fragment.begin(); const char16_t* end = fragment.end(); while (cur != end) { uint8_t byte; MOZ_ALWAYS_TRUE(ConsumeTextByte(&cur, end, &byte)); bytes.infallibleAppend(byte); } } return e.writeBytes(bytes.begin(), bytes.length()); } static bool EncodeDataSection(Encoder& e, AstModule& module) { if (module.dataSegments().empty()) return true; size_t offset; if (!e.startSection(SectionId::Data, &offset)) return false; if (!e.writeVarU32(module.dataSegments().length())) return false; for (AstDataSegment* segment : module.dataSegments()) { if (!EncodeDataSegment(e, *segment)) return false; } e.finishSection(offset); return true; } static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) { if (!e.writeVarU32(0)) // table index return false; if (!EncodeExpr(e, *segment.offset())) return false; if (!e.writeOp(Op::End)) return false; if (!e.writeVarU32(segment.elems().length())) return false; for (const AstRef& elem : segment.elems()) { if (!e.writeVarU32(elem.index())) return false; } return true; } static bool EncodeElemSection(Encoder& e, AstModule& module) { if (module.elemSegments().empty()) return true; size_t offset; if (!e.startSection(SectionId::Elem, &offset)) return false; if (!e.writeVarU32(module.elemSegments().length())) return false; for (AstElemSegment* segment : module.elemSegments()) { if (!EncodeElemSegment(e, *segment)) return false; } e.finishSection(offset); return true; } static bool EncodeModule(AstModule& module, Bytes* bytes) { Encoder e(*bytes); if (!e.writeFixedU32(MagicNumber)) return false; if (!e.writeFixedU32(EncodingVersion)) return false; if (!EncodeTypeSection(e, module)) return false; if (!EncodeImportSection(e, module)) return false; if (!EncodeFunctionSection(e, module)) return false; if (!EncodeTableSection(e, module)) return false; if (!EncodeMemorySection(e, module)) return false; if (!EncodeGlobalSection(e, module)) return false; if (!EncodeExportSection(e, module)) return false; if (!EncodeStartSection(e, module)) return false; if (!EncodeElemSection(e, module)) return false; if (!EncodeCodeSection(e, module)) return false; if (!EncodeDataSection(e, module)) return false; return true; } static bool EncodeBinaryModule(const AstModule& module, Bytes* bytes) { Encoder e(*bytes); const AstDataSegmentVector& dataSegments = module.dataSegments(); MOZ_ASSERT(dataSegments.length() == 1); for (const AstName& fragment : dataSegments[0]->fragments()) { const char16_t* cur = fragment.begin(); const char16_t* end = fragment.end(); while (cur != end) { uint8_t byte; MOZ_ALWAYS_TRUE(ConsumeTextByte(&cur, end, &byte)); if (!e.writeFixedU8(byte)) return false; } } return true; } /*****************************************************************************/ bool wasm::TextToBinary(const char16_t* text, Bytes* bytes, UniqueChars* error) { LifoAlloc lifo(AST_LIFO_DEFAULT_CHUNK_SIZE); bool binary = false; AstModule* module = ParseModule(text, lifo, error, &binary); if (!module) return false; if (binary) return EncodeBinaryModule(*module, bytes); if (!ResolveModule(lifo, module, error)) return false; return EncodeModule(*module, bytes); }