diff options
Diffstat (limited to 'js/src/wasm/WasmTextToBinary.cpp')
-rw-r--r-- | js/src/wasm/WasmTextToBinary.cpp | 4843 |
1 files changed, 4843 insertions, 0 deletions
diff --git a/js/src/wasm/WasmTextToBinary.cpp b/js/src/wasm/WasmTextToBinary.cpp new file mode 100644 index 000000000..e61a82650 --- /dev/null +++ b/js/src/wasm/WasmTextToBinary.cpp @@ -0,0 +1,4843 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * + * Copyright 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<uint64_t> 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<uint32_t> 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; + MOZ_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_); + 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 <typename T> +static T +ushl(T lhs, unsigned rhs) +{ + return rhs < sizeof(T) * CHAR_BIT ? (lhs << rhs) : 0; +} + +template <typename T> +static T +ushr(T lhs, unsigned rhs) +{ + return rhs < sizeof(T) * CHAR_BIT ? (lhs >> rhs) : 0; +} + +template<typename Float> +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<Float> 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<Bits> 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<Float>::fromBits(value))); + + error: + c.ts.generateError(token, c.error); + return nullptr; +} + +template <typename Float> +static bool +ParseHexFloatLiteral(const char16_t* cur, const char16_t* end, Float* result) +{ + MOZ_ALWAYS_TRUE(*cur++ == '0' && *cur++ == 'x'); + typedef FloatingPoint<Float> 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<int32_t> 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<ptrdiff_t> 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<int32_t> 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<Float>(bits); + return true; +} + +template <typename Float> +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<Float>(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<Float>(); + break; + } + case WasmToken::NaN: { + return ParseNaNLiteral<Float>(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<char>(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<Float>(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<int32_t> 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<float>(c, val); + } + case ValType::F64: { + return ParseFloatLiteral<double>(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<uint32_t> 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<size_t>(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<AstDrop>()); + case AstExprKind::BinaryOperator: + return ResolveBinaryOperator(r, expr.as<AstBinaryOperator>()); + case AstExprKind::Block: + return ResolveBlock(r, expr.as<AstBlock>()); + case AstExprKind::Branch: + return ResolveBranch(r, expr.as<AstBranch>()); + case AstExprKind::Call: + return ResolveCall(r, expr.as<AstCall>()); + case AstExprKind::CallIndirect: + return ResolveCallIndirect(r, expr.as<AstCallIndirect>()); + case AstExprKind::ComparisonOperator: + return ResolveComparisonOperator(r, expr.as<AstComparisonOperator>()); + case AstExprKind::Const: + return true; + case AstExprKind::ConversionOperator: + return ResolveConversionOperator(r, expr.as<AstConversionOperator>()); + case AstExprKind::First: + return ResolveFirst(r, expr.as<AstFirst>()); + case AstExprKind::GetGlobal: + return ResolveGetGlobal(r, expr.as<AstGetGlobal>()); + case AstExprKind::GetLocal: + return ResolveGetLocal(r, expr.as<AstGetLocal>()); + case AstExprKind::If: + return ResolveIfElse(r, expr.as<AstIf>()); + case AstExprKind::Load: + return ResolveLoad(r, expr.as<AstLoad>()); + case AstExprKind::Return: + return ResolveReturn(r, expr.as<AstReturn>()); + case AstExprKind::SetGlobal: + return ResolveSetGlobal(r, expr.as<AstSetGlobal>()); + case AstExprKind::SetLocal: + return ResolveSetLocal(r, expr.as<AstSetLocal>()); + case AstExprKind::Store: + return ResolveStore(r, expr.as<AstStore>()); + case AstExprKind::BranchTable: + return ResolveBranchTable(r, expr.as<AstBranchTable>()); + case AstExprKind::TeeLocal: + return ResolveTeeLocal(r, expr.as<AstTeeLocal>()); + case AstExprKind::TernaryOperator: + return ResolveTernaryOperator(r, expr.as<AstTernaryOperator>()); + case AstExprKind::UnaryOperator: + return ResolveUnaryOperator(r, expr.as<AstUnaryOperator>()); + case AstExprKind::GrowMemory: + return ResolveGrowMemory(r, expr.as<AstGrowMemory>()); + } + 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<AstBinaryOperator>()); + case AstExprKind::Block: + return EncodeBlock(e, expr.as<AstBlock>()); + case AstExprKind::Branch: + return EncodeBranch(e, expr.as<AstBranch>()); + case AstExprKind::Call: + return EncodeCall(e, expr.as<AstCall>()); + case AstExprKind::CallIndirect: + return EncodeCallIndirect(e, expr.as<AstCallIndirect>()); + case AstExprKind::ComparisonOperator: + return EncodeComparisonOperator(e, expr.as<AstComparisonOperator>()); + case AstExprKind::Const: + return EncodeConst(e, expr.as<AstConst>()); + case AstExprKind::ConversionOperator: + return EncodeConversionOperator(e, expr.as<AstConversionOperator>()); + case AstExprKind::Drop: + return EncodeDrop(e, expr.as<AstDrop>()); + case AstExprKind::First: + return EncodeFirst(e, expr.as<AstFirst>()); + case AstExprKind::GetLocal: + return EncodeGetLocal(e, expr.as<AstGetLocal>()); + case AstExprKind::GetGlobal: + return EncodeGetGlobal(e, expr.as<AstGetGlobal>()); + case AstExprKind::If: + return EncodeIf(e, expr.as<AstIf>()); + case AstExprKind::Load: + return EncodeLoad(e, expr.as<AstLoad>()); + case AstExprKind::Return: + return EncodeReturn(e, expr.as<AstReturn>()); + case AstExprKind::SetLocal: + return EncodeSetLocal(e, expr.as<AstSetLocal>()); + case AstExprKind::TeeLocal: + return EncodeTeeLocal(e, expr.as<AstTeeLocal>()); + case AstExprKind::SetGlobal: + return EncodeSetGlobal(e, expr.as<AstSetGlobal>()); + case AstExprKind::Store: + return EncodeStore(e, expr.as<AstStore>()); + case AstExprKind::BranchTable: + return EncodeBranchTable(e, expr.as<AstBranchTable>()); + case AstExprKind::TernaryOperator: + return EncodeTernaryOperator(e, expr.as<AstTernaryOperator>()); + case AstExprKind::UnaryOperator: + return EncodeUnaryOperator(e, expr.as<AstUnaryOperator>()); + case AstExprKind::CurrentMemory: + return EncodeCurrentMemory(e, expr.as<AstCurrentMemory>()); + case AstExprKind::GrowMemory: + return EncodeGrowMemory(e, expr.as<AstGrowMemory>()); + } + 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<uint8_t, 0, SystemAllocPolicy> 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); +} |