summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmTextToBinary.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/wasm/WasmTextToBinary.cpp')
-rw-r--r--js/src/wasm/WasmTextToBinary.cpp4843
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);
+}