diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/wasm2c/src/wast-parser.cc | |
parent | Initial commit. (diff) | |
download | firefox-upstream/124.0.1.tar.xz firefox-upstream/124.0.1.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/wasm2c/src/wast-parser.cc')
-rw-r--r-- | third_party/wasm2c/src/wast-parser.cc | 3616 |
1 files changed, 3616 insertions, 0 deletions
diff --git a/third_party/wasm2c/src/wast-parser.cc b/third_party/wasm2c/src/wast-parser.cc new file mode 100644 index 0000000000..a596f018dc --- /dev/null +++ b/third_party/wasm2c/src/wast-parser.cc @@ -0,0 +1,3616 @@ +/* + * Copyright 2017 WebAssembly Community Group participants + * + * 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 "wabt/wast-parser.h" + +#include "wabt/binary-reader-ir.h" +#include "wabt/binary-reader.h" +#include "wabt/cast.h" +#include "wabt/expr-visitor.h" +#include "wabt/resolve-names.h" +#include "wabt/stream.h" +#include "wabt/utf8.h" +#include "wabt/validator.h" + +#define WABT_TRACING 0 +#include "wabt/tracing.h" + +#define EXPECT(token_type) CHECK_RESULT(Expect(TokenType::token_type)) + +namespace wabt { + +namespace { + +static const size_t kMaxErrorTokenLength = 80; + +bool IsPowerOfTwo(uint32_t x) { + return x && ((x & (x - 1)) == 0); +} + +template <typename OutputIter> +void RemoveEscapes(std::string_view text, OutputIter dest) { + // Remove surrounding quotes; if any. This may be empty if the string was + // invalid (e.g. if it contained a bad escape sequence). + if (text.size() <= 2) { + return; + } + + text = text.substr(1, text.size() - 2); + + const char* src = text.data(); + const char* end = text.data() + text.size(); + + while (src < end) { + if (*src == '\\') { + src++; + switch (*src) { + case 'n': + *dest++ = '\n'; + break; + case 'r': + *dest++ = '\r'; + break; + case 't': + *dest++ = '\t'; + break; + case '\\': + *dest++ = '\\'; + break; + case '\'': + *dest++ = '\''; + break; + case '\"': + *dest++ = '\"'; + break; + case 'u': { + // The string should be validated already, + // so this must be a valid unicode escape sequence. + uint32_t digit; + uint32_t scalar_value = 0; + + // Skip u and { characters. + src += 2; + + do { + if (Succeeded(ParseHexdigit(src[0], &digit))) { + scalar_value = (scalar_value << 4) | digit; + } else { + assert(0); + } + src++; + } while (src[0] != '}'); + + // Maximum value of a unicode scalar value + assert(scalar_value < 0x110000); + + // Encode the unicode scalar value as UTF8 sequence + if (scalar_value < 0x80) { + *dest++ = static_cast<uint8_t>(scalar_value); + } else { + if (scalar_value < 0x800) { + *dest++ = static_cast<uint8_t>(0xc0 | (scalar_value >> 6)); + } else { + if (scalar_value < 0x10000) { + *dest++ = static_cast<uint8_t>(0xe0 | (scalar_value >> 12)); + } else { + *dest++ = static_cast<uint8_t>(0xf0 | (scalar_value >> 18)); + *dest++ = + static_cast<uint8_t>(0x80 | ((scalar_value >> 12) & 0x3f)); + } + + *dest++ = + static_cast<uint8_t>(0x80 | ((scalar_value >> 6) & 0x3f)); + } + + *dest++ = static_cast<uint8_t>(0x80 | (scalar_value & 0x3f)); + } + break; + } + default: { + // The string should be validated already, so we know this is a hex + // sequence. + uint32_t hi; + uint32_t lo; + if (Succeeded(ParseHexdigit(src[0], &hi)) && + Succeeded(ParseHexdigit(src[1], &lo))) { + *dest++ = (hi << 4) | lo; + } else { + assert(0); + } + src++; + break; + } + } + src++; + } else { + *dest++ = *src++; + } + } +} + +using TextVector = std::vector<std::string_view>; + +template <typename OutputIter> +void RemoveEscapes(const TextVector& texts, OutputIter out) { + for (std::string_view text : texts) + RemoveEscapes(text, out); +} + +bool IsPlainInstr(TokenType token_type) { + switch (token_type) { + case TokenType::Unreachable: + case TokenType::Nop: + case TokenType::Drop: + case TokenType::Select: + case TokenType::Br: + case TokenType::BrIf: + case TokenType::BrTable: + case TokenType::Return: + case TokenType::ReturnCall: + case TokenType::ReturnCallIndirect: + case TokenType::Call: + case TokenType::CallIndirect: + case TokenType::CallRef: + case TokenType::LocalGet: + case TokenType::LocalSet: + case TokenType::LocalTee: + case TokenType::GlobalGet: + case TokenType::GlobalSet: + case TokenType::Load: + case TokenType::Store: + case TokenType::Const: + case TokenType::Unary: + case TokenType::Binary: + case TokenType::Compare: + case TokenType::Convert: + case TokenType::MemoryCopy: + case TokenType::DataDrop: + case TokenType::MemoryFill: + case TokenType::MemoryGrow: + case TokenType::MemoryInit: + case TokenType::MemorySize: + case TokenType::TableCopy: + case TokenType::ElemDrop: + case TokenType::TableInit: + case TokenType::TableGet: + case TokenType::TableSet: + case TokenType::TableGrow: + case TokenType::TableSize: + case TokenType::TableFill: + case TokenType::Throw: + case TokenType::Rethrow: + case TokenType::RefFunc: + case TokenType::RefNull: + case TokenType::RefIsNull: + case TokenType::AtomicLoad: + case TokenType::AtomicStore: + case TokenType::AtomicRmw: + case TokenType::AtomicRmwCmpxchg: + case TokenType::AtomicNotify: + case TokenType::AtomicFence: + case TokenType::AtomicWait: + case TokenType::Ternary: + case TokenType::SimdLaneOp: + case TokenType::SimdLoadLane: + case TokenType::SimdStoreLane: + case TokenType::SimdShuffleOp: + return true; + default: + return false; + } +} + +bool IsBlockInstr(TokenType token_type) { + switch (token_type) { + case TokenType::Block: + case TokenType::Loop: + case TokenType::If: + case TokenType::Try: + return true; + default: + return false; + } +} + +bool IsPlainOrBlockInstr(TokenType token_type) { + return IsPlainInstr(token_type) || IsBlockInstr(token_type); +} + +bool IsExpr(TokenTypePair pair) { + return pair[0] == TokenType::Lpar && IsPlainOrBlockInstr(pair[1]); +} + +bool IsInstr(TokenTypePair pair) { + return IsPlainOrBlockInstr(pair[0]) || IsExpr(pair); +} + +bool IsLparAnn(TokenTypePair pair) { + return pair[0] == TokenType::LparAnn; +} + +bool IsCatch(TokenType token_type) { + return token_type == TokenType::Catch || token_type == TokenType::CatchAll; +} + +bool IsModuleField(TokenTypePair pair) { + if (pair[0] != TokenType::Lpar) { + return false; + } + + switch (pair[1]) { + case TokenType::Data: + case TokenType::Elem: + case TokenType::Tag: + case TokenType::Export: + case TokenType::Func: + case TokenType::Type: + case TokenType::Global: + case TokenType::Import: + case TokenType::Memory: + case TokenType::Start: + case TokenType::Table: + return true; + default: + return false; + } +} + +bool IsCommand(TokenTypePair pair) { + if (pair[0] != TokenType::Lpar) { + return false; + } + + switch (pair[1]) { + case TokenType::AssertException: + case TokenType::AssertExhaustion: + case TokenType::AssertInvalid: + case TokenType::AssertMalformed: + case TokenType::AssertReturn: + case TokenType::AssertTrap: + case TokenType::AssertUnlinkable: + case TokenType::Get: + case TokenType::Invoke: + case TokenType::Input: + case TokenType::Module: + case TokenType::Output: + case TokenType::Register: + return true; + default: + return false; + } +} + +bool IsEmptySignature(const FuncSignature& sig) { + return sig.result_types.empty() && sig.param_types.empty(); +} + +bool ResolveFuncTypeWithEmptySignature(const Module& module, + FuncDeclaration* decl) { + // Resolve func type variables where the signature was not specified + // explicitly, e.g.: (func (type 1) ...) + if (decl->has_func_type && IsEmptySignature(decl->sig)) { + const FuncType* func_type = module.GetFuncType(decl->type_var); + if (func_type) { + decl->sig = func_type->sig; + return true; + } + } + return false; +} + +void ResolveTypeName( + const Module& module, + Type& type, + Index index, + const std::unordered_map<uint32_t, std::string>& bindings) { + if (type != Type::Reference || type.GetReferenceIndex() != kInvalidIndex) { + return; + } + + const auto name_iterator = bindings.find(index); + assert(name_iterator != bindings.cend()); + const auto type_index = module.type_bindings.FindIndex(name_iterator->second); + assert(type_index != kInvalidIndex); + type = Type(Type::Reference, type_index); +} + +void ResolveTypeNames(const Module& module, FuncDeclaration* decl) { + assert(decl); + auto& signature = decl->sig; + + for (uint32_t param_index = 0; param_index < signature.GetNumParams(); + ++param_index) { + ResolveTypeName(module, signature.param_types[param_index], param_index, + signature.param_type_names); + } + + for (uint32_t result_index = 0; result_index < signature.GetNumResults(); + ++result_index) { + ResolveTypeName(module, signature.result_types[result_index], result_index, + signature.result_type_names); + } +} + +void ResolveImplicitlyDefinedFunctionType(const Location& loc, + Module* module, + const FuncDeclaration& decl) { + // Resolve implicitly defined function types, e.g.: (func (param i32) ...) + if (!decl.has_func_type) { + Index func_type_index = module->GetFuncTypeIndex(decl.sig); + if (func_type_index == kInvalidIndex) { + auto func_type_field = std::make_unique<TypeModuleField>(loc); + auto func_type = std::make_unique<FuncType>(); + func_type->sig = decl.sig; + func_type_field->type = std::move(func_type); + module->AppendField(std::move(func_type_field)); + } + } +} + +Result CheckTypeIndex(const Location& loc, + Type actual, + Type expected, + const char* desc, + Index index, + const char* index_kind, + Errors* errors) { + // Types must match exactly; no subtyping should be allowed. + if (actual != expected) { + errors->emplace_back( + ErrorLevel::Error, loc, + StringPrintf("type mismatch for %s %" PRIindex + " of %s. got %s, expected %s", + index_kind, index, desc, actual.GetName().c_str(), + expected.GetName().c_str())); + return Result::Error; + } + return Result::Ok; +} + +Result CheckTypes(const Location& loc, + const TypeVector& actual, + const TypeVector& expected, + const char* desc, + const char* index_kind, + Errors* errors) { + Result result = Result::Ok; + if (actual.size() == expected.size()) { + for (size_t i = 0; i < actual.size(); ++i) { + result |= CheckTypeIndex(loc, actual[i], expected[i], desc, i, index_kind, + errors); + } + } else { + errors->emplace_back( + ErrorLevel::Error, loc, + StringPrintf("expected %" PRIzd " %ss, got %" PRIzd, expected.size(), + index_kind, actual.size())); + result = Result::Error; + } + return result; +} + +Result CheckFuncTypeVarMatchesExplicit(const Location& loc, + const Module& module, + const FuncDeclaration& decl, + Errors* errors) { + Result result = Result::Ok; + if (decl.has_func_type) { + const FuncType* func_type = module.GetFuncType(decl.type_var); + if (func_type) { + result |= + CheckTypes(loc, decl.sig.result_types, func_type->sig.result_types, + "function", "result", errors); + result |= + CheckTypes(loc, decl.sig.param_types, func_type->sig.param_types, + "function", "argument", errors); + } else if (!(decl.sig.param_types.empty() && + decl.sig.result_types.empty())) { + // We want to check whether the function type at the explicit index + // matches the given param and result types. If they were omitted then + // they'll be resolved automatically (see + // ResolveFuncTypeWithEmptySignature), but if they are provided then we + // have to check. If we get here then the type var is invalid, so we + // can't check whether they match. + if (decl.type_var.is_index()) { + errors->emplace_back(ErrorLevel::Error, loc, + StringPrintf("invalid func type index %" PRIindex, + decl.type_var.index())); + } else { + errors->emplace_back(ErrorLevel::Error, loc, + StringPrintf("expected func type identifier %s", + decl.type_var.name().c_str())); + } + result = Result::Error; + } + } + return result; +} + +bool IsInlinableFuncSignature(const FuncSignature& sig) { + return sig.GetNumParams() == 0 && sig.GetNumResults() <= 1; +} + +class ResolveFuncTypesExprVisitorDelegate : public ExprVisitor::DelegateNop { + public: + explicit ResolveFuncTypesExprVisitorDelegate(Module* module, Errors* errors) + : module_(module), errors_(errors) {} + + void ResolveBlockDeclaration(const Location& loc, BlockDeclaration* decl) { + ResolveTypeNames(*module_, decl); + ResolveFuncTypeWithEmptySignature(*module_, decl); + if (!IsInlinableFuncSignature(decl->sig)) { + ResolveImplicitlyDefinedFunctionType(loc, module_, *decl); + } + } + + Result BeginBlockExpr(BlockExpr* expr) override { + ResolveBlockDeclaration(expr->loc, &expr->block.decl); + return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, + expr->block.decl, errors_); + } + + Result BeginIfExpr(IfExpr* expr) override { + ResolveBlockDeclaration(expr->loc, &expr->true_.decl); + return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, + expr->true_.decl, errors_); + } + + Result BeginLoopExpr(LoopExpr* expr) override { + ResolveBlockDeclaration(expr->loc, &expr->block.decl); + return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, + expr->block.decl, errors_); + } + + Result BeginTryExpr(TryExpr* expr) override { + ResolveBlockDeclaration(expr->loc, &expr->block.decl); + return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, + expr->block.decl, errors_); + } + + Result OnCallIndirectExpr(CallIndirectExpr* expr) override { + ResolveFuncTypeWithEmptySignature(*module_, &expr->decl); + ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl); + return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl, + errors_); + } + + Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr* expr) override { + ResolveFuncTypeWithEmptySignature(*module_, &expr->decl); + ResolveImplicitlyDefinedFunctionType(expr->loc, module_, expr->decl); + return CheckFuncTypeVarMatchesExplicit(expr->loc, *module_, expr->decl, + errors_); + } + + private: + Module* module_; + Errors* errors_; +}; + +Result ResolveFuncTypes(Module* module, Errors* errors) { + Result result = Result::Ok; + for (ModuleField& field : module->fields) { + Func* func = nullptr; + FuncDeclaration* decl = nullptr; + if (auto* func_field = dyn_cast<FuncModuleField>(&field)) { + func = &func_field->func; + decl = &func->decl; + } else if (auto* tag_field = dyn_cast<TagModuleField>(&field)) { + decl = &tag_field->tag.decl; + } else if (auto* import_field = dyn_cast<ImportModuleField>(&field)) { + if (auto* func_import = + dyn_cast<FuncImport>(import_field->import.get())) { + // Only check the declaration, not the function itself, since it is an + // import. + decl = &func_import->func.decl; + } else if (auto* tag_import = + dyn_cast<TagImport>(import_field->import.get())) { + decl = &tag_import->tag.decl; + } else { + continue; + } + } else { + continue; + } + + bool has_func_type_and_empty_signature = false; + + if (decl) { + ResolveTypeNames(*module, decl); + has_func_type_and_empty_signature = + ResolveFuncTypeWithEmptySignature(*module, decl); + ResolveImplicitlyDefinedFunctionType(field.loc, module, *decl); + result |= + CheckFuncTypeVarMatchesExplicit(field.loc, *module, *decl, errors); + } + + if (func) { + if (has_func_type_and_empty_signature) { + // The call to ResolveFuncTypeWithEmptySignature may have updated the + // function signature so there are parameters. Since parameters and + // local variables share the same index space, we need to increment the + // local indexes bound to a given name by the number of parameters in + // the function. + for (auto& [name, binding] : func->bindings) { + binding.index += func->GetNumParams(); + } + } + + ResolveFuncTypesExprVisitorDelegate delegate(module, errors); + ExprVisitor visitor(&delegate); + result |= visitor.VisitFunc(func); + } + } + return result; +} + +void AppendInlineExportFields(Module* module, + ModuleFieldList* fields, + Index index) { + Location last_field_loc = module->fields.back().loc; + + for (ModuleField& field : *fields) { + auto* export_field = cast<ExportModuleField>(&field); + export_field->export_.var = Var(index, last_field_loc); + } + + module->AppendFields(fields); +} + +} // End of anonymous namespace + +WastParser::WastParser(WastLexer* lexer, + Errors* errors, + WastParseOptions* options) + : lexer_(lexer), errors_(errors), options_(options) {} + +void WastParser::Error(Location loc, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + errors_->emplace_back(ErrorLevel::Error, loc, buffer); +} + +Token WastParser::GetToken() { + if (tokens_.empty()) { + tokens_.push_back(lexer_->GetToken()); + } + return tokens_.front(); +} + +Location WastParser::GetLocation() { + return GetToken().loc; +} + +TokenType WastParser::Peek(size_t n) { + while (tokens_.size() <= n) { + Token cur = lexer_->GetToken(); + if (cur.token_type() != TokenType::LparAnn) { + tokens_.push_back(cur); + } else { + // Custom annotation. For now, discard until matching Rpar, unless it is + // a code metadata annotation. In that case, we know how to parse it. + if (!options_->features.annotations_enabled()) { + Error(cur.loc, "annotations not enabled: %s", cur.to_string().c_str()); + tokens_.push_back(Token(cur.loc, TokenType::Invalid)); + continue; + } + if (options_->features.code_metadata_enabled() && + cur.text().find("metadata.code.") == 0) { + tokens_.push_back(cur); + continue; + } + int indent = 1; + while (indent > 0) { + cur = lexer_->GetToken(); + switch (cur.token_type()) { + case TokenType::Lpar: + case TokenType::LparAnn: + indent++; + break; + + case TokenType::Rpar: + indent--; + break; + + case TokenType::Eof: + indent = 0; + Error(cur.loc, "unterminated annotation"); + break; + + default: + break; + } + } + } + } + return tokens_.at(n).token_type(); +} + +TokenTypePair WastParser::PeekPair() { + return TokenTypePair{{Peek(), Peek(1)}}; +} + +bool WastParser::PeekMatch(TokenType type, size_t n) { + return Peek(n) == type; +} + +bool WastParser::PeekMatchLpar(TokenType type) { + return Peek() == TokenType::Lpar && Peek(1) == type; +} + +bool WastParser::PeekMatchExpr() { + return IsExpr(PeekPair()); +} + +bool WastParser::PeekMatchRefType() { + return options_->features.function_references_enabled() && + PeekMatchLpar(TokenType::Ref); +} + +bool WastParser::Match(TokenType type) { + if (PeekMatch(type)) { + Consume(); + return true; + } + return false; +} + +bool WastParser::MatchLpar(TokenType type) { + if (PeekMatchLpar(type)) { + Consume(); + Consume(); + return true; + } + return false; +} + +Result WastParser::Expect(TokenType type) { + if (!Match(type)) { + Token token = Consume(); + Error(token.loc, "unexpected token %s, expected %s.", + token.to_string_clamp(kMaxErrorTokenLength).c_str(), + GetTokenTypeName(type)); + return Result::Error; + } + + return Result::Ok; +} + +Token WastParser::Consume() { + assert(!tokens_.empty()); + Token token = tokens_.front(); + tokens_.pop_front(); + return token; +} + +Result WastParser::Synchronize(SynchronizeFunc func) { + static const int kMaxConsumed = 10; + for (int i = 0; i < kMaxConsumed; ++i) { + if (func(PeekPair())) { + return Result::Ok; + } + + Token token = Consume(); + if (token.token_type() == TokenType::Reserved) { + Error(token.loc, "unexpected token %s.", + token.to_string_clamp(kMaxErrorTokenLength).c_str()); + } + } + + return Result::Error; +} + +void WastParser::ErrorUnlessOpcodeEnabled(const Token& token) { + Opcode opcode = token.opcode(); + if (!opcode.IsEnabled(options_->features)) { + Error(token.loc, "opcode not allowed: %s", opcode.GetName()); + } +} + +Result WastParser::ErrorExpected(const std::vector<std::string>& expected, + const char* example) { + Token token = Consume(); + std::string expected_str; + if (!expected.empty()) { + expected_str = ", expected "; + for (size_t i = 0; i < expected.size(); ++i) { + if (i != 0) { + if (i == expected.size() - 1) { + expected_str += " or "; + } else { + expected_str += ", "; + } + } + + expected_str += expected[i]; + } + + if (example) { + expected_str += " (e.g. "; + expected_str += example; + expected_str += ")"; + } + } + + Error(token.loc, "unexpected token \"%s\"%s.", + token.to_string_clamp(kMaxErrorTokenLength).c_str(), + expected_str.c_str()); + return Result::Error; +} + +Result WastParser::ErrorIfLpar(const std::vector<std::string>& expected, + const char* example) { + if (Match(TokenType::Lpar)) { + GetToken(); + return ErrorExpected(expected, example); + } + return Result::Ok; +} + +bool WastParser::ParseBindVarOpt(std::string* name) { + WABT_TRACE(ParseBindVarOpt); + if (!PeekMatch(TokenType::Var)) { + return false; + } + Token token = Consume(); + *name = std::string(token.text()); + return true; +} + +Result WastParser::ParseVar(Var* out_var) { + WABT_TRACE(ParseVar); + if (PeekMatch(TokenType::Nat)) { + Token token = Consume(); + std::string_view sv = token.literal().text; + uint64_t index = kInvalidIndex; + if (Failed(ParseUint64(sv, &index))) { + // Print an error, but don't fail parsing. + Error(token.loc, "invalid int \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + + *out_var = Var(index, token.loc); + return Result::Ok; + } else if (PeekMatch(TokenType::Var)) { + Token token = Consume(); + *out_var = Var(token.text(), token.loc); + return Result::Ok; + } else { + return ErrorExpected({"a numeric index", "a name"}, "12 or $foo"); + } +} + +bool WastParser::ParseVarOpt(Var* out_var, Var default_var) { + WABT_TRACE(ParseVarOpt); + if (PeekMatch(TokenType::Nat) || PeekMatch(TokenType::Var)) { + Result result = ParseVar(out_var); + // Should always succeed, the only way it could fail is if the token + // doesn't match. + assert(Succeeded(result)); + WABT_USE(result); + return true; + } else { + *out_var = default_var; + return false; + } +} + +Result WastParser::ParseOffsetExpr(ExprList* out_expr_list) { + WABT_TRACE(ParseOffsetExpr); + if (!ParseOffsetExprOpt(out_expr_list)) { + return ErrorExpected({"an offset expr"}, "(i32.const 123)"); + } + return Result::Ok; +} + +bool WastParser::ParseOffsetExprOpt(ExprList* out_expr_list) { + WABT_TRACE(ParseOffsetExprOpt); + if (MatchLpar(TokenType::Offset)) { + CHECK_RESULT(ParseTerminatingInstrList(out_expr_list)); + EXPECT(Rpar); + return true; + } else if (PeekMatchExpr()) { + CHECK_RESULT(ParseExpr(out_expr_list)); + return true; + } else { + return false; + } +} + +Result WastParser::ParseTextList(std::vector<uint8_t>* out_data) { + WABT_TRACE(ParseTextList); + if (!ParseTextListOpt(out_data)) { + // TODO(binji): Add error message here. + return Result::Error; + } + + return Result::Ok; +} + +bool WastParser::ParseTextListOpt(std::vector<uint8_t>* out_data) { + WABT_TRACE(ParseTextListOpt); + TextVector texts; + while (PeekMatch(TokenType::Text)) + texts.push_back(Consume().text()); + + RemoveEscapes(texts, std::back_inserter(*out_data)); + return !texts.empty(); +} + +Result WastParser::ParseVarList(VarVector* out_var_list) { + WABT_TRACE(ParseVarList); + Var var; + while (ParseVarOpt(&var)) { + out_var_list->emplace_back(var); + } + if (out_var_list->empty()) { + return ErrorExpected({"a var"}, "12 or $foo"); + } else { + return Result::Ok; + } +} + +bool WastParser::ParseElemExprOpt(ExprList* out_elem_expr) { + WABT_TRACE(ParseElemExprOpt); + bool item = MatchLpar(TokenType::Item); + ExprList exprs; + if (item) { + if (ParseTerminatingInstrList(&exprs) != Result::Ok) { + return false; + } + EXPECT(Rpar); + } else { + if (ParseExpr(&exprs) != Result::Ok) { + return false; + } + } + if (!exprs.size()) { + return false; + } + *out_elem_expr = std::move(exprs); + return true; +} + +bool WastParser::ParseElemExprListOpt(ExprListVector* out_list) { + ExprList elem_expr; + while (ParseElemExprOpt(&elem_expr)) { + out_list->push_back(std::move(elem_expr)); + } + return !out_list->empty(); +} + +bool WastParser::ParseElemExprVarListOpt(ExprListVector* out_list) { + WABT_TRACE(ParseElemExprVarListOpt); + Var var; + ExprList init_expr; + while (ParseVarOpt(&var)) { + init_expr.push_back(std::make_unique<RefFuncExpr>(var)); + out_list->push_back(std::move(init_expr)); + } + return !out_list->empty(); +} + +Result WastParser::ParseValueType(Var* out_type) { + WABT_TRACE(ParseValueType); + + const bool is_ref_type = PeekMatchRefType(); + const bool is_value_type = PeekMatch(TokenType::ValueType); + + if (!is_value_type && !is_ref_type) { + return ErrorExpected( + {"i32", "i64", "f32", "f64", "v128", "externref", "funcref"}); + } + + if (is_ref_type) { + EXPECT(Lpar); + EXPECT(Ref); + CHECK_RESULT(ParseVar(out_type)); + EXPECT(Rpar); + return Result::Ok; + } + + Token token = Consume(); + Type type = token.type(); + bool is_enabled; + switch (type) { + case Type::V128: + is_enabled = options_->features.simd_enabled(); + break; + case Type::FuncRef: + case Type::ExternRef: + is_enabled = options_->features.reference_types_enabled(); + break; + default: + is_enabled = true; + break; + } + + if (!is_enabled) { + Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); + return Result::Error; + } + + *out_type = Var(type, GetLocation()); + return Result::Ok; +} + +Result WastParser::ParseValueTypeList( + TypeVector* out_type_list, + std::unordered_map<uint32_t, std::string>* type_names) { + WABT_TRACE(ParseValueTypeList); + while (true) { + if (!PeekMatchRefType() && !PeekMatch(TokenType::ValueType)) { + break; + } + + Var type; + CHECK_RESULT(ParseValueType(&type)); + + if (type.is_index()) { + out_type_list->push_back(Type(type.index())); + } else { + assert(type.is_name()); + assert(options_->features.function_references_enabled()); + type_names->emplace(out_type_list->size(), type.name()); + out_type_list->push_back(Type(Type::Reference, kInvalidIndex)); + } + } + + return Result::Ok; +} + +Result WastParser::ParseRefKind(Type* out_type) { + WABT_TRACE(ParseRefKind); + if (!IsTokenTypeRefKind(Peek())) { + return ErrorExpected({"func", "extern", "exn"}); + } + + Token token = Consume(); + Type type = token.type(); + + if ((type == Type::ExternRef && + !options_->features.reference_types_enabled()) || + ((type == Type::Struct || type == Type::Array) && + !options_->features.gc_enabled())) { + Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); + return Result::Error; + } + + *out_type = type; + return Result::Ok; +} + +Result WastParser::ParseRefType(Type* out_type) { + WABT_TRACE(ParseRefType); + if (!PeekMatch(TokenType::ValueType)) { + return ErrorExpected({"funcref", "externref"}); + } + + Token token = Consume(); + Type type = token.type(); + if (type == Type::ExternRef && + !options_->features.reference_types_enabled()) { + Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); + return Result::Error; + } + + *out_type = type; + return Result::Ok; +} + +bool WastParser::ParseRefTypeOpt(Type* out_type) { + WABT_TRACE(ParseRefTypeOpt); + if (!PeekMatch(TokenType::ValueType)) { + return false; + } + + Token token = Consume(); + Type type = token.type(); + if (type == Type::ExternRef && + !options_->features.reference_types_enabled()) { + return false; + } + + *out_type = type; + return true; +} + +Result WastParser::ParseQuotedText(std::string* text, bool check_utf8) { + WABT_TRACE(ParseQuotedText); + if (!PeekMatch(TokenType::Text)) { + return ErrorExpected({"a quoted string"}, "\"foo\""); + } + + Token token = Consume(); + RemoveEscapes(token.text(), std::back_inserter(*text)); + if (check_utf8 && !IsValidUtf8(text->data(), text->length())) { + Error(token.loc, "quoted string has an invalid utf-8 encoding"); + } + return Result::Ok; +} + +bool WastParser::ParseOffsetOpt(Address* out_offset) { + WABT_TRACE(ParseOffsetOpt); + if (PeekMatch(TokenType::OffsetEqNat)) { + Token token = Consume(); + uint64_t offset64; + std::string_view sv = token.text(); + if (Failed(ParseInt64(sv, &offset64, ParseIntType::SignedAndUnsigned))) { + Error(token.loc, "invalid offset \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + // FIXME: make this depend on the current memory. + if (offset64 > UINT32_MAX) { + Error(token.loc, "offset must be less than or equal to 0xffffffff"); + } + *out_offset = offset64; + return true; + } else { + *out_offset = 0; + return false; + } +} + +bool WastParser::ParseAlignOpt(Address* out_align) { + WABT_TRACE(ParseAlignOpt); + if (PeekMatch(TokenType::AlignEqNat)) { + Token token = Consume(); + std::string_view sv = token.text(); + if (Failed(ParseInt64(sv, out_align, ParseIntType::UnsignedOnly))) { + Error(token.loc, "invalid alignment \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + + if (!IsPowerOfTwo(*out_align)) { + Error(token.loc, "alignment must be power-of-two"); + } + + return true; + } else { + *out_align = WABT_USE_NATURAL_ALIGNMENT; + return false; + } +} + +Result WastParser::ParseMemidx(Location loc, Var* out_memidx) { + WABT_TRACE(ParseMemidx); + if (PeekMatchLpar(TokenType::Memory)) { + if (!options_->features.multi_memory_enabled()) { + Error(loc, "Specifying memory variable is not allowed"); + return Result::Error; + } + EXPECT(Lpar); + EXPECT(Memory); + CHECK_RESULT(ParseVar(out_memidx)); + EXPECT(Rpar); + } else { + if (ParseVarOpt(out_memidx, Var(0, loc)) && + !options_->features.multi_memory_enabled()) { + Error(loc, "Specifying memory variable is not allowed"); + return Result::Error; + } + } + return Result::Ok; +} + +Result WastParser::ParseLimitsIndex(Limits* out_limits) { + WABT_TRACE(ParseLimitsIndex); + + if (PeekMatch(TokenType::ValueType)) { + if (GetToken().type() == Type::I64) { + Consume(); + out_limits->is_64 = true; + } else if (GetToken().type() == Type::I32) { + Consume(); + out_limits->is_64 = false; + } + } + + return Result::Ok; +} + +Result WastParser::ParseLimits(Limits* out_limits) { + WABT_TRACE(ParseLimits); + + CHECK_RESULT(ParseNat(&out_limits->initial, out_limits->is_64)); + if (PeekMatch(TokenType::Nat)) { + CHECK_RESULT(ParseNat(&out_limits->max, out_limits->is_64)); + out_limits->has_max = true; + } else { + out_limits->has_max = false; + } + + if (Match(TokenType::Shared)) { + out_limits->is_shared = true; + } + + return Result::Ok; +} + +Result WastParser::ParseNat(uint64_t* out_nat, bool is_64) { + WABT_TRACE(ParseNat); + if (!PeekMatch(TokenType::Nat)) { + return ErrorExpected({"a natural number"}, "123"); + } + + Token token = Consume(); + std::string_view sv = token.literal().text; + if (Failed(ParseUint64(sv, out_nat)) || (!is_64 && *out_nat > 0xffffffffu)) { + Error(token.loc, "invalid int \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + + return Result::Ok; +} + +Result WastParser::ParseModule(std::unique_ptr<Module>* out_module) { + WABT_TRACE(ParseModule); + auto module = std::make_unique<Module>(); + + if (PeekMatchLpar(TokenType::Module)) { + // Starts with "(module". Allow text and binary modules, but no quoted + // modules. + CommandPtr command; + CHECK_RESULT(ParseModuleCommand(nullptr, &command)); + if (isa<ModuleCommand>(command.get())) { + auto module_command = cast<ModuleCommand>(std::move(command)); + *module = std::move(module_command->module); + } else { + assert(isa<ScriptModuleCommand>(command.get())); + auto module_command = cast<ScriptModuleCommand>(std::move(command)); + *module = std::move(module_command->module); + } + } else if (IsModuleField(PeekPair())) { + // Parse an inline module (i.e. one with no surrounding (module)). + CHECK_RESULT(ParseModuleFieldList(module.get())); + } else { + ConsumeIfLpar(); + ErrorExpected({"a module field", "a module"}); + } + + EXPECT(Eof); + if (errors_->size() == 0) { + *out_module = std::move(module); + return Result::Ok; + } else { + return Result::Error; + } +} + +Result WastParser::ParseScript(std::unique_ptr<Script>* out_script) { + WABT_TRACE(ParseScript); + auto script = std::make_unique<Script>(); + + // Don't consume the Lpar yet, even though it is required. This way the + // sub-parser functions (e.g. ParseFuncModuleField) can consume it and keep + // the parsing structure more regular. + if (IsModuleField(PeekPair())) { + // Parse an inline module (i.e. one with no surrounding (module)). + auto command = std::make_unique<ModuleCommand>(); + command->module.loc = GetLocation(); + CHECK_RESULT(ParseModuleFieldList(&command->module)); + script->commands.emplace_back(std::move(command)); + } else if (IsCommand(PeekPair())) { + CHECK_RESULT(ParseCommandList(script.get(), &script->commands)); + } else { + ConsumeIfLpar(); + ErrorExpected({"a module field", "a command"}); + } + + EXPECT(Eof); + if (errors_->size() == 0) { + *out_script = std::move(script); + return Result::Ok; + } else { + return Result::Error; + } +} + +Result WastParser::ParseModuleFieldList(Module* module) { + WABT_TRACE(ParseModuleFieldList); + while (IsModuleField(PeekPair())) { + if (Failed(ParseModuleField(module))) { + CHECK_RESULT(Synchronize(IsModuleField)); + } + } + CHECK_RESULT(ResolveFuncTypes(module, errors_)); + CHECK_RESULT(ResolveNamesModule(module, errors_)); + return Result::Ok; +} + +Result WastParser::ParseModuleField(Module* module) { + WABT_TRACE(ParseModuleField); + switch (Peek(1)) { + case TokenType::Data: return ParseDataModuleField(module); + case TokenType::Elem: return ParseElemModuleField(module); + case TokenType::Tag: return ParseTagModuleField(module); + case TokenType::Export: return ParseExportModuleField(module); + case TokenType::Func: return ParseFuncModuleField(module); + case TokenType::Type: return ParseTypeModuleField(module); + case TokenType::Global: return ParseGlobalModuleField(module); + case TokenType::Import: return ParseImportModuleField(module); + case TokenType::Memory: return ParseMemoryModuleField(module); + case TokenType::Start: return ParseStartModuleField(module); + case TokenType::Table: return ParseTableModuleField(module); + default: + assert( + !"ParseModuleField should only be called if IsModuleField() is true"); + return Result::Error; + } +} + +Result WastParser::ParseDataModuleField(Module* module) { + WABT_TRACE(ParseDataModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Data); + std::string name; + ParseBindVarOpt(&name); + auto field = std::make_unique<DataSegmentModuleField>(loc, name); + + if (PeekMatchLpar(TokenType::Memory)) { + EXPECT(Lpar); + EXPECT(Memory); + CHECK_RESULT(ParseVar(&field->data_segment.memory_var)); + EXPECT(Rpar); + CHECK_RESULT(ParseOffsetExpr(&field->data_segment.offset)); + } else if (ParseVarOpt(&field->data_segment.memory_var, Var(0, loc))) { + CHECK_RESULT(ParseOffsetExpr(&field->data_segment.offset)); + } else if (!ParseOffsetExprOpt(&field->data_segment.offset)) { + if (!options_->features.bulk_memory_enabled()) { + Error(loc, "passive data segments are not allowed"); + return Result::Error; + } + + field->data_segment.kind = SegmentKind::Passive; + } + + ParseTextListOpt(&field->data_segment.data); + EXPECT(Rpar); + module->AppendField(std::move(field)); + return Result::Ok; +} + +Result WastParser::ParseElemModuleField(Module* module) { + WABT_TRACE(ParseElemModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Elem); + + // With MVP text format the name here was intended to refer to the table + // that the elem segment was part of, but we never did anything with this name + // since there was only one table anyway. + // With bulk-memory enabled this introduces a new name for the particular + // elem segment. + std::string initial_name; + bool has_name = ParseBindVarOpt(&initial_name); + + std::string segment_name = initial_name; + if (!options_->features.bulk_memory_enabled()) { + segment_name = ""; + } + auto field = std::make_unique<ElemSegmentModuleField>(loc, segment_name); + if (options_->features.reference_types_enabled() && + Match(TokenType::Declare)) { + field->elem_segment.kind = SegmentKind::Declared; + } + + // Optional table specifier + if (options_->features.bulk_memory_enabled()) { + if (PeekMatchLpar(TokenType::Table)) { + EXPECT(Lpar); + EXPECT(Table); + CHECK_RESULT(ParseVar(&field->elem_segment.table_var)); + EXPECT(Rpar); + } else { + ParseVarOpt(&field->elem_segment.table_var, Var(0, loc)); + } + } else { + if (has_name) { + field->elem_segment.table_var = Var(initial_name, loc); + } else { + ParseVarOpt(&field->elem_segment.table_var, Var(0, loc)); + } + } + + // Parse offset expression, if not declared/passive segment. + if (options_->features.bulk_memory_enabled()) { + if (field->elem_segment.kind != SegmentKind::Declared && + !ParseOffsetExprOpt(&field->elem_segment.offset)) { + field->elem_segment.kind = SegmentKind::Passive; + } + } else { + CHECK_RESULT(ParseOffsetExpr(&field->elem_segment.offset)); + } + + if (ParseRefTypeOpt(&field->elem_segment.elem_type)) { + ParseElemExprListOpt(&field->elem_segment.elem_exprs); + } else { + field->elem_segment.elem_type = Type::FuncRef; + if (PeekMatch(TokenType::Func)) { + EXPECT(Func); + } + ParseElemExprVarListOpt(&field->elem_segment.elem_exprs); + } + EXPECT(Rpar); + module->AppendField(std::move(field)); + return Result::Ok; +} + +Result WastParser::ParseTagModuleField(Module* module) { + WABT_TRACE(ParseTagModuleField); + if (!options_->features.exceptions_enabled()) { + Error(Consume().loc, "tag not allowed"); + return Result::Error; + } + EXPECT(Lpar); + EXPECT(Tag); + Location loc = GetLocation(); + + std::string name; + ParseBindVarOpt(&name); + + ModuleFieldList export_fields; + CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Tag)); + + if (PeekMatchLpar(TokenType::Import)) { + CheckImportOrdering(module); + auto import = std::make_unique<TagImport>(name); + Tag& tag = import->tag; + CHECK_RESULT(ParseInlineImport(import.get())); + CHECK_RESULT(ParseTypeUseOpt(&tag.decl)); + CHECK_RESULT(ParseUnboundFuncSignature(&tag.decl.sig)); + CHECK_RESULT(ErrorIfLpar({"type", "param", "result"})); + auto field = + std::make_unique<ImportModuleField>(std::move(import), GetLocation()); + module->AppendField(std::move(field)); + } else { + auto field = std::make_unique<TagModuleField>(loc, name); + CHECK_RESULT(ParseTypeUseOpt(&field->tag.decl)); + CHECK_RESULT(ParseUnboundFuncSignature(&field->tag.decl.sig)); + module->AppendField(std::move(field)); + } + + AppendInlineExportFields(module, &export_fields, module->tags.size() - 1); + + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseExportModuleField(Module* module) { + WABT_TRACE(ParseExportModuleField); + EXPECT(Lpar); + auto field = std::make_unique<ExportModuleField>(GetLocation()); + EXPECT(Export); + CHECK_RESULT(ParseQuotedText(&field->export_.name)); + CHECK_RESULT(ParseExportDesc(&field->export_)); + EXPECT(Rpar); + module->AppendField(std::move(field)); + return Result::Ok; +} + +Result WastParser::ParseFuncModuleField(Module* module) { + WABT_TRACE(ParseFuncModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Func); + std::string name; + ParseBindVarOpt(&name); + + ModuleFieldList export_fields; + CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Func)); + + if (PeekMatchLpar(TokenType::Import)) { + CheckImportOrdering(module); + auto import = std::make_unique<FuncImport>(name); + Func& func = import->func; + CHECK_RESULT(ParseInlineImport(import.get())); + CHECK_RESULT(ParseTypeUseOpt(&func.decl)); + CHECK_RESULT(ParseFuncSignature(&func.decl.sig, &func.bindings)); + CHECK_RESULT(ErrorIfLpar({"type", "param", "result"})); + auto field = + std::make_unique<ImportModuleField>(std::move(import), GetLocation()); + module->AppendField(std::move(field)); + } else { + auto field = std::make_unique<FuncModuleField>(loc, name); + Func& func = field->func; + func.loc = GetLocation(); + CHECK_RESULT(ParseTypeUseOpt(&func.decl)); + CHECK_RESULT(ParseFuncSignature(&func.decl.sig, &func.bindings)); + TypeVector local_types; + CHECK_RESULT(ParseBoundValueTypeList( + TokenType::Local, &local_types, &func.bindings, + &func.decl.sig.param_type_names, func.GetNumParams())); + func.local_types.Set(local_types); + CHECK_RESULT(ParseTerminatingInstrList(&func.exprs)); + module->AppendField(std::move(field)); + } + + AppendInlineExportFields(module, &export_fields, module->funcs.size() - 1); + + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseTypeModuleField(Module* module) { + WABT_TRACE(ParseTypeModuleField); + EXPECT(Lpar); + auto field = std::make_unique<TypeModuleField>(GetLocation()); + EXPECT(Type); + + std::string name; + ParseBindVarOpt(&name); + EXPECT(Lpar); + Location loc = GetLocation(); + + if (Match(TokenType::Func)) { + auto func_type = std::make_unique<FuncType>(name); + BindingHash bindings; + CHECK_RESULT(ParseFuncSignature(&func_type->sig, &bindings)); + CHECK_RESULT(ErrorIfLpar({"param", "result"})); + field->type = std::move(func_type); + } else if (Match(TokenType::Struct)) { + if (!options_->features.gc_enabled()) { + Error(loc, "struct not allowed"); + return Result::Error; + } + auto struct_type = std::make_unique<StructType>(name); + CHECK_RESULT(ParseFieldList(&struct_type->fields)); + field->type = std::move(struct_type); + } else if (Match(TokenType::Array)) { + if (!options_->features.gc_enabled()) { + Error(loc, "array type not allowed"); + } + auto array_type = std::make_unique<ArrayType>(name); + CHECK_RESULT(ParseField(&array_type->field)); + field->type = std::move(array_type); + } else { + return ErrorExpected({"func", "struct", "array"}); + } + + EXPECT(Rpar); + EXPECT(Rpar); + module->AppendField(std::move(field)); + return Result::Ok; +} + +Result WastParser::ParseField(Field* field) { + WABT_TRACE(ParseField); + auto parse_mut_valuetype = [&]() -> Result { + // TODO: Share with ParseGlobalType? + if (MatchLpar(TokenType::Mut)) { + field->mutable_ = true; + Var type; + CHECK_RESULT(ParseValueType(&type)); + field->type = Type(type.index()); + EXPECT(Rpar); + } else { + field->mutable_ = false; + Var type; + CHECK_RESULT(ParseValueType(&type)); + field->type = Type(type.index()); + } + return Result::Ok; + }; + + if (MatchLpar(TokenType::Field)) { + ParseBindVarOpt(&field->name); + CHECK_RESULT(parse_mut_valuetype()); + EXPECT(Rpar); + } else { + CHECK_RESULT(parse_mut_valuetype()); + } + + return Result::Ok; +} + +Result WastParser::ParseFieldList(std::vector<Field>* fields) { + WABT_TRACE(ParseFieldList); + while (PeekMatch(TokenType::ValueType) || PeekMatch(TokenType::Lpar)) { + Field field; + CHECK_RESULT(ParseField(&field)); + fields->push_back(field); + } + return Result::Ok; +} + +Result WastParser::ParseGlobalModuleField(Module* module) { + WABT_TRACE(ParseGlobalModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Global); + std::string name; + ParseBindVarOpt(&name); + + ModuleFieldList export_fields; + CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Global)); + + if (PeekMatchLpar(TokenType::Import)) { + CheckImportOrdering(module); + auto import = std::make_unique<GlobalImport>(name); + CHECK_RESULT(ParseInlineImport(import.get())); + CHECK_RESULT(ParseGlobalType(&import->global)); + auto field = + std::make_unique<ImportModuleField>(std::move(import), GetLocation()); + module->AppendField(std::move(field)); + } else { + auto field = std::make_unique<GlobalModuleField>(loc, name); + CHECK_RESULT(ParseGlobalType(&field->global)); + CHECK_RESULT(ParseTerminatingInstrList(&field->global.init_expr)); + module->AppendField(std::move(field)); + } + + AppendInlineExportFields(module, &export_fields, module->globals.size() - 1); + + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseImportModuleField(Module* module) { + WABT_TRACE(ParseImportModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + CheckImportOrdering(module); + EXPECT(Import); + std::string module_name; + std::string field_name; + CHECK_RESULT(ParseQuotedText(&module_name)); + CHECK_RESULT(ParseQuotedText(&field_name)); + EXPECT(Lpar); + + std::unique_ptr<ImportModuleField> field; + std::string name; + + switch (Peek()) { + case TokenType::Func: { + Consume(); + ParseBindVarOpt(&name); + auto import = std::make_unique<FuncImport>(name); + CHECK_RESULT(ParseTypeUseOpt(&import->func.decl)); + CHECK_RESULT( + ParseFuncSignature(&import->func.decl.sig, &import->func.bindings)); + CHECK_RESULT(ErrorIfLpar({"param", "result"})); + EXPECT(Rpar); + field = std::make_unique<ImportModuleField>(std::move(import), loc); + break; + } + + case TokenType::Table: { + Consume(); + ParseBindVarOpt(&name); + auto import = std::make_unique<TableImport>(name); + CHECK_RESULT(ParseLimits(&import->table.elem_limits)); + CHECK_RESULT(ParseRefType(&import->table.elem_type)); + EXPECT(Rpar); + field = std::make_unique<ImportModuleField>(std::move(import), loc); + break; + } + + case TokenType::Memory: { + Consume(); + ParseBindVarOpt(&name); + auto import = std::make_unique<MemoryImport>(name); + CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits)); + CHECK_RESULT(ParseLimits(&import->memory.page_limits)); + EXPECT(Rpar); + field = std::make_unique<ImportModuleField>(std::move(import), loc); + break; + } + + case TokenType::Global: { + Consume(); + ParseBindVarOpt(&name); + auto import = std::make_unique<GlobalImport>(name); + CHECK_RESULT(ParseGlobalType(&import->global)); + EXPECT(Rpar); + field = std::make_unique<ImportModuleField>(std::move(import), loc); + break; + } + + case TokenType::Tag: { + Consume(); + ParseBindVarOpt(&name); + auto import = std::make_unique<TagImport>(name); + CHECK_RESULT(ParseTypeUseOpt(&import->tag.decl)); + CHECK_RESULT(ParseUnboundFuncSignature(&import->tag.decl.sig)); + EXPECT(Rpar); + field = std::make_unique<ImportModuleField>(std::move(import), loc); + break; + } + + default: + return ErrorExpected({"an external kind"}); + } + + field->import->module_name = module_name; + field->import->field_name = field_name; + + module->AppendField(std::move(field)); + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseMemoryModuleField(Module* module) { + WABT_TRACE(ParseMemoryModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Memory); + std::string name; + ParseBindVarOpt(&name); + + ModuleFieldList export_fields; + CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Memory)); + + if (PeekMatchLpar(TokenType::Import)) { + CheckImportOrdering(module); + auto import = std::make_unique<MemoryImport>(name); + CHECK_RESULT(ParseInlineImport(import.get())); + CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits)); + CHECK_RESULT(ParseLimits(&import->memory.page_limits)); + auto field = + std::make_unique<ImportModuleField>(std::move(import), GetLocation()); + module->AppendField(std::move(field)); + } else { + auto field = std::make_unique<MemoryModuleField>(loc, name); + CHECK_RESULT(ParseLimitsIndex(&field->memory.page_limits)); + if (MatchLpar(TokenType::Data)) { + auto data_segment_field = std::make_unique<DataSegmentModuleField>(loc); + DataSegment& data_segment = data_segment_field->data_segment; + data_segment.memory_var = Var(module->memories.size(), GetLocation()); + data_segment.offset.push_back(std::make_unique<ConstExpr>( + field->memory.page_limits.is_64 ? Const::I64(0) : Const::I32(0))); + data_segment.offset.back().loc = loc; + ParseTextListOpt(&data_segment.data); + EXPECT(Rpar); + + uint32_t byte_size = WABT_ALIGN_UP_TO_PAGE(data_segment.data.size()); + uint32_t page_size = WABT_BYTES_TO_PAGES(byte_size); + field->memory.page_limits.initial = page_size; + field->memory.page_limits.max = page_size; + field->memory.page_limits.has_max = true; + + module->AppendField(std::move(field)); + module->AppendField(std::move(data_segment_field)); + } else { + CHECK_RESULT(ParseLimits(&field->memory.page_limits)); + module->AppendField(std::move(field)); + } + } + + AppendInlineExportFields(module, &export_fields, module->memories.size() - 1); + + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseStartModuleField(Module* module) { + WABT_TRACE(ParseStartModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + if (module->starts.size() > 0) { + Error(loc, "multiple start sections"); + return Result::Error; + } + EXPECT(Start); + Var var; + CHECK_RESULT(ParseVar(&var)); + EXPECT(Rpar); + module->AppendField(std::make_unique<StartModuleField>(var, loc)); + return Result::Ok; +} + +Result WastParser::ParseTableModuleField(Module* module) { + WABT_TRACE(ParseTableModuleField); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Table); + std::string name; + ParseBindVarOpt(&name); + + ModuleFieldList export_fields; + CHECK_RESULT(ParseInlineExports(&export_fields, ExternalKind::Table)); + + if (PeekMatchLpar(TokenType::Import)) { + CheckImportOrdering(module); + auto import = std::make_unique<TableImport>(name); + CHECK_RESULT(ParseInlineImport(import.get())); + CHECK_RESULT(ParseLimits(&import->table.elem_limits)); + CHECK_RESULT(ParseRefType(&import->table.elem_type)); + auto field = + std::make_unique<ImportModuleField>(std::move(import), GetLocation()); + module->AppendField(std::move(field)); + } else if (PeekMatch(TokenType::ValueType)) { + Type elem_type; + CHECK_RESULT(ParseRefType(&elem_type)); + + EXPECT(Lpar); + EXPECT(Elem); + + auto elem_segment_field = std::make_unique<ElemSegmentModuleField>(loc); + ElemSegment& elem_segment = elem_segment_field->elem_segment; + elem_segment.table_var = Var(module->tables.size(), GetLocation()); + elem_segment.offset.push_back(std::make_unique<ConstExpr>(Const::I32(0))); + elem_segment.offset.back().loc = loc; + elem_segment.elem_type = elem_type; + // Syntax is either an optional list of var (legacy), or a non-empty list + // of elem expr. + ExprList elem_expr; + if (ParseElemExprOpt(&elem_expr)) { + elem_segment.elem_exprs.push_back(std::move(elem_expr)); + // Parse the rest. + ParseElemExprListOpt(&elem_segment.elem_exprs); + } else { + ParseElemExprVarListOpt(&elem_segment.elem_exprs); + } + EXPECT(Rpar); + + auto table_field = std::make_unique<TableModuleField>(loc, name); + table_field->table.elem_limits.initial = elem_segment.elem_exprs.size(); + table_field->table.elem_limits.max = elem_segment.elem_exprs.size(); + table_field->table.elem_limits.has_max = true; + table_field->table.elem_type = elem_type; + module->AppendField(std::move(table_field)); + module->AppendField(std::move(elem_segment_field)); + } else { + auto field = std::make_unique<TableModuleField>(loc, name); + CHECK_RESULT(ParseLimits(&field->table.elem_limits)); + CHECK_RESULT(ParseRefType(&field->table.elem_type)); + module->AppendField(std::move(field)); + } + + AppendInlineExportFields(module, &export_fields, module->tables.size() - 1); + + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseExportDesc(Export* export_) { + WABT_TRACE(ParseExportDesc); + EXPECT(Lpar); + switch (Peek()) { + case TokenType::Func: export_->kind = ExternalKind::Func; break; + case TokenType::Table: export_->kind = ExternalKind::Table; break; + case TokenType::Memory: export_->kind = ExternalKind::Memory; break; + case TokenType::Global: export_->kind = ExternalKind::Global; break; + case TokenType::Tag: export_->kind = ExternalKind::Tag; break; + default: + return ErrorExpected({"an external kind"}); + } + Consume(); + CHECK_RESULT(ParseVar(&export_->var)); + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseInlineExports(ModuleFieldList* fields, + ExternalKind kind) { + WABT_TRACE(ParseInlineExports); + while (PeekMatchLpar(TokenType::Export)) { + EXPECT(Lpar); + auto field = std::make_unique<ExportModuleField>(GetLocation()); + field->export_.kind = kind; + EXPECT(Export); + CHECK_RESULT(ParseQuotedText(&field->export_.name)); + EXPECT(Rpar); + fields->push_back(std::move(field)); + } + return Result::Ok; +} + +Result WastParser::ParseInlineImport(Import* import) { + WABT_TRACE(ParseInlineImport); + EXPECT(Lpar); + EXPECT(Import); + CHECK_RESULT(ParseQuotedText(&import->module_name)); + CHECK_RESULT(ParseQuotedText(&import->field_name)); + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseTypeUseOpt(FuncDeclaration* decl) { + WABT_TRACE(ParseTypeUseOpt); + if (MatchLpar(TokenType::Type)) { + decl->has_func_type = true; + CHECK_RESULT(ParseVar(&decl->type_var)); + EXPECT(Rpar); + } else { + decl->has_func_type = false; + } + return Result::Ok; +} + +Result WastParser::ParseFuncSignature(FuncSignature* sig, + BindingHash* param_bindings) { + WABT_TRACE(ParseFuncSignature); + CHECK_RESULT(ParseBoundValueTypeList(TokenType::Param, &sig->param_types, + param_bindings, &sig->param_type_names)); + CHECK_RESULT(ParseResultList(&sig->result_types, &sig->result_type_names)); + return Result::Ok; +} + +Result WastParser::ParseUnboundFuncSignature(FuncSignature* sig) { + WABT_TRACE(ParseUnboundFuncSignature); + CHECK_RESULT(ParseUnboundValueTypeList(TokenType::Param, &sig->param_types, + &sig->param_type_names)); + CHECK_RESULT(ParseResultList(&sig->result_types, &sig->result_type_names)); + return Result::Ok; +} + +Result WastParser::ParseBoundValueTypeList( + TokenType token, + TypeVector* types, + BindingHash* bindings, + std::unordered_map<uint32_t, std::string>* type_names, + Index binding_index_offset) { + WABT_TRACE(ParseBoundValueTypeList); + while (MatchLpar(token)) { + if (PeekMatch(TokenType::Var)) { + std::string name; + Var type; + Location loc = GetLocation(); + ParseBindVarOpt(&name); + CHECK_RESULT(ParseValueType(&type)); + bindings->emplace(name, + Binding(loc, binding_index_offset + types->size())); + if (type.is_index()) { + types->push_back(Type(type.index())); + } else { + assert(type.is_name()); + assert(options_->features.function_references_enabled()); + type_names->emplace(binding_index_offset + types->size(), type.name()); + types->push_back(Type(Type::Reference, kInvalidIndex)); + } + } else { + CHECK_RESULT(ParseValueTypeList(types, type_names)); + } + EXPECT(Rpar); + } + return Result::Ok; +} + +Result WastParser::ParseUnboundValueTypeList( + TokenType token, + TypeVector* types, + std::unordered_map<uint32_t, std::string>* type_names) { + WABT_TRACE(ParseUnboundValueTypeList); + while (MatchLpar(token)) { + CHECK_RESULT(ParseValueTypeList(types, type_names)); + EXPECT(Rpar); + } + return Result::Ok; +} + +Result WastParser::ParseResultList( + TypeVector* result_types, + std::unordered_map<uint32_t, std::string>* type_names) { + WABT_TRACE(ParseResultList); + return ParseUnboundValueTypeList(TokenType::Result, result_types, type_names); +} + +Result WastParser::ParseInstrList(ExprList* exprs) { + WABT_TRACE(ParseInstrList); + ExprList new_exprs; + while (true) { + auto pair = PeekPair(); + if (IsInstr(pair)) { + if (Succeeded(ParseInstr(&new_exprs))) { + exprs->splice(exprs->end(), new_exprs); + } else { + CHECK_RESULT(Synchronize(IsInstr)); + } + } else if (IsLparAnn(pair)) { + if (Succeeded(ParseCodeMetadataAnnotation(&new_exprs))) { + exprs->splice(exprs->end(), new_exprs); + } else { + CHECK_RESULT(Synchronize(IsLparAnn)); + } + } else { + break; + } + } + return Result::Ok; +} + +Result WastParser::ParseTerminatingInstrList(ExprList* exprs) { + WABT_TRACE(ParseTerminatingInstrList); + Result result = ParseInstrList(exprs); + // An InstrList often has no further Lpar following it, because it would have + // gobbled it up. So if there is a following Lpar it is an error. If we + // handle it here we can produce a nicer error message. + CHECK_RESULT(ErrorIfLpar({"an instr"})); + return result; +} + +Result WastParser::ParseInstr(ExprList* exprs) { + WABT_TRACE(ParseInstr); + if (IsPlainInstr(Peek())) { + std::unique_ptr<Expr> expr; + CHECK_RESULT(ParsePlainInstr(&expr)); + exprs->push_back(std::move(expr)); + return Result::Ok; + } else if (IsBlockInstr(Peek())) { + std::unique_ptr<Expr> expr; + CHECK_RESULT(ParseBlockInstr(&expr)); + exprs->push_back(std::move(expr)); + return Result::Ok; + } else if (PeekMatchExpr()) { + return ParseExpr(exprs); + } else { + assert(!"ParseInstr should only be called when IsInstr() is true"); + return Result::Error; + } +} + +Result WastParser::ParseCodeMetadataAnnotation(ExprList* exprs) { + WABT_TRACE(ParseCodeMetadataAnnotation); + Token tk = Consume(); + std::string_view name = tk.text(); + name.remove_prefix(sizeof("metadata.code.") - 1); + std::string data_text; + CHECK_RESULT(ParseQuotedText(&data_text, false)); + std::vector<uint8_t> data(data_text.begin(), data_text.end()); + exprs->push_back(std::make_unique<CodeMetadataExpr>(name, std::move(data))); + EXPECT(Rpar); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParsePlainInstrVar(Location loc, + std::unique_ptr<Expr>* out_expr) { + Var var; + CHECK_RESULT(ParseVar(&var)); + out_expr->reset(new T(var, loc)); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseMemoryInstrVar(Location loc, + std::unique_ptr<Expr>* out_expr) { + Var memidx; + Var var; + if (PeekMatchLpar(TokenType::Memory)) { + if (!options_->features.multi_memory_enabled()) { + Error(loc, "Specifying memory variable is not allowed"); + return Result::Error; + } + CHECK_RESULT(ParseMemidx(loc, &memidx)); + CHECK_RESULT(ParseVar(&var)); + out_expr->reset(new T(var, memidx, loc)); + } else { + CHECK_RESULT(ParseVar(&memidx)); + if (ParseVarOpt(&var, Var(0, loc))) { + if (!options_->features.multi_memory_enabled()) { + Error(loc, "Specifiying memory variable is not allowed"); + return Result::Error; + } + out_expr->reset(new T(var, memidx, loc)); + } else { + out_expr->reset(new T(memidx, var, loc)); + } + } + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseLoadStoreInstr(Location loc, + Token token, + std::unique_ptr<Expr>* out_expr) { + Opcode opcode = token.opcode(); + Var memidx; + Address offset; + Address align; + CHECK_RESULT(ParseMemidx(loc, &memidx)); + ParseOffsetOpt(&offset); + ParseAlignOpt(&align); + out_expr->reset(new T(opcode, memidx, align, offset, loc)); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseSIMDLoadStoreInstr(Location loc, + Token token, + std::unique_ptr<Expr>* out_expr) { + ErrorUnlessOpcodeEnabled(token); + + Var memidx(0, loc); + + if (options_->features.multi_memory_enabled()) { + // We have to be a little careful when reading the memeory index. + // If there is just a single integer folloing the instruction that + // represents the lane index, so we check for either a pair of intergers + // or an integers followed by offset= or align=. + bool try_read_mem_index = true; + if (PeekMatch(TokenType::Nat)) { + // The next token could be a memory index or a lane index + if (!PeekMatch(TokenType::OffsetEqNat, 1) && + !PeekMatch(TokenType::AlignEqNat, 1) && + !PeekMatch(TokenType::Nat, 1)) { + try_read_mem_index = false; + } + } + if (try_read_mem_index) { + CHECK_RESULT(ParseMemidx(loc, &memidx)); + } + } + Address offset; + Address align; + ParseOffsetOpt(&offset); + ParseAlignOpt(&align); + + uint64_t lane_idx = 0; + Result result = ParseSimdLane(loc, &lane_idx); + + if (Failed(result)) { + return Result::Error; + } + + out_expr->reset(new T(token.opcode(), memidx, align, offset, lane_idx, loc)); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseMemoryExpr(Location loc, + std::unique_ptr<Expr>* out_expr) { + Var memidx; + CHECK_RESULT(ParseMemidx(loc, &memidx)); + out_expr->reset(new T(memidx, loc)); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseMemoryBinaryExpr(Location loc, + std::unique_ptr<Expr>* out_expr) { + Var srcmemidx; + Var destmemidx; + CHECK_RESULT(ParseMemidx(loc, &srcmemidx)); + CHECK_RESULT(ParseMemidx(loc, &destmemidx)); + out_expr->reset(new T(srcmemidx, destmemidx, loc)); + return Result::Ok; +} + +Result WastParser::ParseSimdLane(Location loc, uint64_t* lane_idx) { + if (!PeekMatch(TokenType::Nat) && !PeekMatch(TokenType::Int)) { + return ErrorExpected({"a natural number in range [0, 32)"}); + } + + Literal literal = Consume().literal(); + + Result result = + ParseInt64(literal.text, lane_idx, ParseIntType::UnsignedOnly); + + if (Failed(result)) { + Error(loc, "invalid literal \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(literal.text)); + return Result::Error; + } + + // The valid range is only [0, 32), but it's only malformed if it can't + // fit in a byte. + if (*lane_idx > 255) { + Error(loc, "lane index \"" PRIstringview "\" out-of-range [0, 32)", + WABT_PRINTF_STRING_VIEW_ARG(literal.text)); + return Result::Error; + } + + return Result::Ok; +} + +Result WastParser::ParsePlainInstr(std::unique_ptr<Expr>* out_expr) { + WABT_TRACE(ParsePlainInstr); + Location loc = GetLocation(); + switch (Peek()) { + case TokenType::Unreachable: + Consume(); + out_expr->reset(new UnreachableExpr(loc)); + break; + + case TokenType::Nop: + Consume(); + out_expr->reset(new NopExpr(loc)); + break; + + case TokenType::Drop: + Consume(); + out_expr->reset(new DropExpr(loc)); + break; + + case TokenType::Select: { + Consume(); + TypeVector result; + if (options_->features.reference_types_enabled() && + PeekMatchLpar(TokenType::Result)) { + CHECK_RESULT(ParseResultList(&result, nullptr)); + } + out_expr->reset(new SelectExpr(result, loc)); + break; + } + + case TokenType::Br: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<BrExpr>(loc, out_expr)); + break; + + case TokenType::BrIf: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<BrIfExpr>(loc, out_expr)); + break; + + case TokenType::BrTable: { + Consume(); + auto expr = std::make_unique<BrTableExpr>(loc); + CHECK_RESULT(ParseVarList(&expr->targets)); + expr->default_target = expr->targets.back(); + expr->targets.pop_back(); + *out_expr = std::move(expr); + break; + } + + case TokenType::Return: + Consume(); + out_expr->reset(new ReturnExpr(loc)); + break; + + case TokenType::Call: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<CallExpr>(loc, out_expr)); + break; + + case TokenType::CallIndirect: { + Consume(); + auto expr = std::make_unique<CallIndirectExpr>(loc); + ParseVarOpt(&expr->table, Var(0, loc)); + CHECK_RESULT(ParseTypeUseOpt(&expr->decl)); + CHECK_RESULT(ParseUnboundFuncSignature(&expr->decl.sig)); + *out_expr = std::move(expr); + break; + } + + case TokenType::CallRef: { + ErrorUnlessOpcodeEnabled(Consume()); + out_expr->reset(new CallRefExpr(loc)); + break; + } + + case TokenType::ReturnCall: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParsePlainInstrVar<ReturnCallExpr>(loc, out_expr)); + break; + + case TokenType::ReturnCallIndirect: { + ErrorUnlessOpcodeEnabled(Consume()); + auto expr = std::make_unique<ReturnCallIndirectExpr>(loc); + ParseVarOpt(&expr->table, Var(0, loc)); + CHECK_RESULT(ParseTypeUseOpt(&expr->decl)); + CHECK_RESULT(ParseUnboundFuncSignature(&expr->decl.sig)); + *out_expr = std::move(expr); + break; + } + + case TokenType::LocalGet: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<LocalGetExpr>(loc, out_expr)); + break; + + case TokenType::LocalSet: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<LocalSetExpr>(loc, out_expr)); + break; + + case TokenType::LocalTee: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<LocalTeeExpr>(loc, out_expr)); + break; + + case TokenType::GlobalGet: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<GlobalGetExpr>(loc, out_expr)); + break; + + case TokenType::GlobalSet: + Consume(); + CHECK_RESULT(ParsePlainInstrVar<GlobalSetExpr>(loc, out_expr)); + break; + + case TokenType::Load: + CHECK_RESULT(ParseLoadStoreInstr<LoadExpr>(loc, Consume(), out_expr)); + break; + + case TokenType::Store: + CHECK_RESULT(ParseLoadStoreInstr<StoreExpr>(loc, Consume(), out_expr)); + break; + + case TokenType::Const: { + Const const_; + CHECK_RESULT(ParseConst(&const_, ConstType::Normal)); + out_expr->reset(new ConstExpr(const_, loc)); + break; + } + + case TokenType::Unary: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + out_expr->reset(new UnaryExpr(token.opcode(), loc)); + break; + } + + case TokenType::Binary: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + out_expr->reset(new BinaryExpr(token.opcode(), loc)); + break; + } + + case TokenType::Compare: + out_expr->reset(new CompareExpr(Consume().opcode(), loc)); + break; + + case TokenType::Convert: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + out_expr->reset(new ConvertExpr(token.opcode(), loc)); + break; + } + + case TokenType::MemoryCopy: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParseMemoryBinaryExpr<MemoryCopyExpr>(loc, out_expr)); + break; + + case TokenType::MemoryFill: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParseMemoryExpr<MemoryFillExpr>(loc, out_expr)); + break; + + case TokenType::DataDrop: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParsePlainInstrVar<DataDropExpr>(loc, out_expr)); + break; + + case TokenType::MemoryInit: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParseMemoryInstrVar<MemoryInitExpr>(loc, out_expr)); + break; + + case TokenType::MemorySize: + Consume(); + CHECK_RESULT(ParseMemoryExpr<MemorySizeExpr>(loc, out_expr)); + break; + + case TokenType::MemoryGrow: + Consume(); + CHECK_RESULT(ParseMemoryExpr<MemoryGrowExpr>(loc, out_expr)); + break; + + case TokenType::TableCopy: { + ErrorUnlessOpcodeEnabled(Consume()); + Var dst(0, loc); + Var src(0, loc); + if (options_->features.reference_types_enabled()) { + ParseVarOpt(&dst, dst); + ParseVarOpt(&src, src); + } + out_expr->reset(new TableCopyExpr(dst, src, loc)); + break; + } + + case TokenType::ElemDrop: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParsePlainInstrVar<ElemDropExpr>(loc, out_expr)); + break; + + case TokenType::TableInit: { + ErrorUnlessOpcodeEnabled(Consume()); + Var segment_index(0, loc); + CHECK_RESULT(ParseVar(&segment_index)); + Var table_index(0, loc); + if (ParseVarOpt(&table_index, table_index)) { + // Here are the two forms: + // + // table.init $elemidx ... + // table.init $tableidx $elemidx ... + // + // So if both indexes are provided, we need to swap them. + std::swap(segment_index, table_index); + } + out_expr->reset(new TableInitExpr(segment_index, table_index, loc)); + break; + } + + case TokenType::TableGet: { + ErrorUnlessOpcodeEnabled(Consume()); + Var table_index(0, loc); + ParseVarOpt(&table_index, table_index); + out_expr->reset(new TableGetExpr(table_index, loc)); + break; + } + + case TokenType::TableSet: { + ErrorUnlessOpcodeEnabled(Consume()); + Var table_index(0, loc); + ParseVarOpt(&table_index, table_index); + out_expr->reset(new TableSetExpr(table_index, loc)); + break; + } + + case TokenType::TableGrow: { + ErrorUnlessOpcodeEnabled(Consume()); + Var table_index(0, loc); + ParseVarOpt(&table_index, table_index); + out_expr->reset(new TableGrowExpr(table_index, loc)); + break; + } + + case TokenType::TableSize: { + ErrorUnlessOpcodeEnabled(Consume()); + Var table_index(0, loc); + ParseVarOpt(&table_index, table_index); + out_expr->reset(new TableSizeExpr(table_index, loc)); + break; + } + + case TokenType::TableFill: { + ErrorUnlessOpcodeEnabled(Consume()); + Var table_index(0, loc); + ParseVarOpt(&table_index, table_index); + out_expr->reset(new TableFillExpr(table_index, loc)); + break; + } + + case TokenType::RefFunc: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParsePlainInstrVar<RefFuncExpr>(loc, out_expr)); + break; + + case TokenType::RefNull: { + ErrorUnlessOpcodeEnabled(Consume()); + Type type; + CHECK_RESULT(ParseRefKind(&type)); + out_expr->reset(new RefNullExpr(type, loc)); + break; + } + + case TokenType::RefIsNull: + ErrorUnlessOpcodeEnabled(Consume()); + out_expr->reset(new RefIsNullExpr(loc)); + break; + + case TokenType::Throw: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParsePlainInstrVar<ThrowExpr>(loc, out_expr)); + break; + + case TokenType::Rethrow: + ErrorUnlessOpcodeEnabled(Consume()); + CHECK_RESULT(ParsePlainInstrVar<RethrowExpr>(loc, out_expr)); + break; + + case TokenType::AtomicNotify: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + CHECK_RESULT(ParseLoadStoreInstr<AtomicNotifyExpr>(loc, token, out_expr)); + break; + } + + case TokenType::AtomicFence: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + uint32_t consistency_model = 0x0; + out_expr->reset(new AtomicFenceExpr(consistency_model, loc)); + break; + } + + case TokenType::AtomicWait: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + CHECK_RESULT(ParseLoadStoreInstr<AtomicWaitExpr>(loc, token, out_expr)); + break; + } + + case TokenType::AtomicLoad: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + CHECK_RESULT(ParseLoadStoreInstr<AtomicLoadExpr>(loc, token, out_expr)); + break; + } + + case TokenType::AtomicStore: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + CHECK_RESULT(ParseLoadStoreInstr<AtomicStoreExpr>(loc, token, out_expr)); + break; + } + + case TokenType::AtomicRmw: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + CHECK_RESULT(ParseLoadStoreInstr<AtomicRmwExpr>(loc, token, out_expr)); + break; + } + + case TokenType::AtomicRmwCmpxchg: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + CHECK_RESULT( + ParseLoadStoreInstr<AtomicRmwCmpxchgExpr>(loc, token, out_expr)); + break; + } + + case TokenType::Ternary: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + out_expr->reset(new TernaryExpr(token.opcode(), loc)); + break; + } + + case TokenType::SimdLaneOp: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + + uint64_t lane_idx = 0; + Result result = ParseSimdLane(loc, &lane_idx); + + if (Failed(result)) { + return Result::Error; + } + + out_expr->reset(new SimdLaneOpExpr(token.opcode(), lane_idx, loc)); + break; + } + + case TokenType::SimdLoadLane: { + CHECK_RESULT( + ParseSIMDLoadStoreInstr<SimdLoadLaneExpr>(loc, Consume(), out_expr)); + break; + } + + case TokenType::SimdStoreLane: { + CHECK_RESULT( + ParseSIMDLoadStoreInstr<SimdStoreLaneExpr>(loc, Consume(), out_expr)); + break; + } + + case TokenType::SimdShuffleOp: { + Token token = Consume(); + ErrorUnlessOpcodeEnabled(token); + v128 values; + for (int lane = 0; lane < 16; ++lane) { + Location loc = GetLocation(); + uint64_t lane_idx; + Result result = ParseSimdLane(loc, &lane_idx); + if (Failed(result)) { + return Result::Error; + } + + values.set_u8(lane, static_cast<uint8_t>(lane_idx)); + } + + out_expr->reset(new SimdShuffleOpExpr(token.opcode(), values, loc)); + break; + } + + default: + assert( + !"ParsePlainInstr should only be called when IsPlainInstr() is true"); + return Result::Error; + } + + return Result::Ok; +} + +Result WastParser::ParseSimdV128Const(Const* const_, + TokenType token_type, + ConstType const_type) { + WABT_TRACE(ParseSimdV128Const); + + uint8_t lane_count = 0; + bool integer = true; + switch (token_type) { + case TokenType::I8X16: { lane_count = 16; break; } + case TokenType::I16X8: { lane_count = 8; break; } + case TokenType::I32X4: { lane_count = 4; break; } + case TokenType::I64X2: { lane_count = 2; break; } + case TokenType::F32X4: { lane_count = 4; integer = false; break; } + case TokenType::F64X2: { lane_count = 2; integer = false; break; } + default: { + Error(const_->loc, + "Unexpected type at start of simd constant. " + "Expected one of: i8x16, i16x8, i32x4, i64x2, f32x4, f64x2. " + "Found \"%s\".", + GetTokenTypeName(token_type)); + return Result::Error; + } + } + Consume(); + + const_->loc = GetLocation(); + + for (int lane = 0; lane < lane_count; ++lane) { + Location loc = GetLocation(); + + // Check that the lane literal type matches the element type of the v128: + Token token = GetToken(); + switch (token.token_type()) { + case TokenType::Nat: + case TokenType::Int: + // OK. + break; + + case TokenType::Float: + case TokenType::NanArithmetic: + case TokenType::NanCanonical: + if (integer) { + goto error; + } + break; + + error: + default: + if (integer) { + return ErrorExpected({"a Nat or Integer literal"}, "123"); + } else { + return ErrorExpected({"a Float literal"}, "42.0"); + } + } + + Result result; + + // For each type, parse the next literal, bound check it, and write it to + // the array of bytes: + if (integer) { + std::string_view sv = Consume().literal().text; + + switch (lane_count) { + case 16: { + uint8_t value = 0; + result = ParseInt8(sv, &value, ParseIntType::SignedAndUnsigned); + const_->set_v128_u8(lane, value); + break; + } + case 8: { + uint16_t value = 0; + result = ParseInt16(sv, &value, ParseIntType::SignedAndUnsigned); + const_->set_v128_u16(lane, value); + break; + } + case 4: { + uint32_t value = 0; + result = ParseInt32(sv, &value, ParseIntType::SignedAndUnsigned); + const_->set_v128_u32(lane, value); + break; + } + case 2: { + uint64_t value = 0; + result = ParseInt64(sv, &value, ParseIntType::SignedAndUnsigned); + const_->set_v128_u64(lane, value); + break; + } + } + } else { + Const lane_const_; + switch (lane_count) { + case 4: + result = ParseF32(&lane_const_, const_type); + const_->set_v128_f32(lane, lane_const_.f32_bits()); + break; + + case 2: + result = ParseF64(&lane_const_, const_type); + const_->set_v128_f64(lane, lane_const_.f64_bits()); + break; + } + + const_->set_expected_nan(lane, lane_const_.expected_nan()); + } + + if (Failed(result)) { + Error(loc, "invalid literal \"%s\"", token.to_string().c_str()); + return Result::Error; + } + } + + return Result::Ok; +} + +Result WastParser::ParseExpectedNan(ExpectedNan* expected) { + WABT_TRACE(ParseExpectedNan); + TokenType token_type = Peek(); + switch (token_type) { + case TokenType::NanArithmetic: + *expected = ExpectedNan::Arithmetic; + break; + case TokenType::NanCanonical: + *expected = ExpectedNan::Canonical; + break; + default: + return Result::Error; + } + Consume(); + return Result::Ok; +} + +Result WastParser::ParseF32(Const* const_, ConstType const_type) { + ExpectedNan expected; + if (const_type == ConstType::Expectation && + Succeeded(ParseExpectedNan(&expected))) { + const_->set_f32(expected); + return Result::Ok; + } + + auto token = Consume(); + if (!token.HasLiteral()) { + return Result::Error; + } + + auto literal = token.literal(); + uint32_t f32_bits; + Result result = ParseFloat(literal.type, literal.text, &f32_bits); + const_->set_f32(f32_bits); + return result; +} + +Result WastParser::ParseF64(Const* const_, ConstType const_type) { + ExpectedNan expected; + if (const_type == ConstType::Expectation && + Succeeded(ParseExpectedNan(&expected))) { + const_->set_f64(expected); + return Result::Ok; + } + + auto token = Consume(); + if (!token.HasLiteral()) { + return Result::Error; + } + + auto literal = token.literal(); + uint64_t f64_bits; + Result result = ParseDouble(literal.type, literal.text, &f64_bits); + const_->set_f64(f64_bits); + return result; +} + +Result WastParser::ParseConst(Const* const_, ConstType const_type) { + WABT_TRACE(ParseConst); + Token opcode_token = Consume(); + Opcode opcode = opcode_token.opcode(); + const_->loc = GetLocation(); + Token token = GetToken(); + + // V128 is fully handled by ParseSimdV128Const: + if (opcode != Opcode::V128Const) { + switch (token.token_type()) { + case TokenType::Nat: + case TokenType::Int: + case TokenType::Float: + // OK. + break; + case TokenType::NanArithmetic: + case TokenType::NanCanonical: + break; + default: + return ErrorExpected({"a numeric literal"}, "123, -45, 6.7e8"); + } + } + + Result result; + switch (opcode) { + case Opcode::I32Const: { + auto token = Consume(); + if (!token.HasLiteral()) { + return Result::Error; + } + auto sv = token.literal().text; + uint32_t u32; + result = ParseInt32(sv, &u32, ParseIntType::SignedAndUnsigned); + const_->set_u32(u32); + break; + } + + case Opcode::I64Const: { + auto token = Consume(); + if (!token.HasLiteral()) { + return Result::Error; + } + auto sv = token.literal().text; + uint64_t u64; + result = ParseInt64(sv, &u64, ParseIntType::SignedAndUnsigned); + const_->set_u64(u64); + break; + } + + case Opcode::F32Const: + result = ParseF32(const_, const_type); + break; + + case Opcode::F64Const: + result = ParseF64(const_, const_type); + break; + + case Opcode::V128Const: + ErrorUnlessOpcodeEnabled(opcode_token); + // Parse V128 Simd Const (16 bytes). + result = ParseSimdV128Const(const_, token.token_type(), const_type); + // ParseSimdV128Const report error already, just return here if parser get + // errors. + if (Failed(result)) { + return Result::Error; + } + break; + + default: + assert(!"ParseConst called with invalid opcode"); + return Result::Error; + } + + if (Failed(result)) { + Error(const_->loc, "invalid literal \"%s\"", token.to_string().c_str()); + // Return if parser get errors. + return Result::Error; + } + + return Result::Ok; +} + +Result WastParser::ParseExternref(Const* const_) { + WABT_TRACE(ParseExternref); + Token token = Consume(); + if (!options_->features.reference_types_enabled()) { + Error(token.loc, "externref not allowed"); + return Result::Error; + } + + Literal literal; + std::string_view sv; + const_->loc = GetLocation(); + TokenType token_type = Peek(); + + switch (token_type) { + case TokenType::Nat: + case TokenType::Int: { + literal = Consume().literal(); + sv = literal.text; + break; + } + default: + return ErrorExpected({"a numeric literal"}, "123"); + } + + uint64_t ref_bits; + Result result = ParseInt64(sv, &ref_bits, ParseIntType::UnsignedOnly); + + const_->set_externref(static_cast<uintptr_t>(ref_bits)); + + if (Failed(result)) { + Error(const_->loc, "invalid literal \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(literal.text)); + // Return if parser get errors. + return Result::Error; + } + + return Result::Ok; +} + +Result WastParser::ParseConstList(ConstVector* consts, ConstType type) { + WABT_TRACE(ParseConstList); + while (PeekMatchLpar(TokenType::Const) || PeekMatchLpar(TokenType::RefNull) || + PeekMatchLpar(TokenType::RefExtern) || + PeekMatchLpar(TokenType::RefFunc)) { + Consume(); + Const const_; + switch (Peek()) { + case TokenType::Const: + CHECK_RESULT(ParseConst(&const_, type)); + break; + case TokenType::RefNull: { + auto token = Consume(); + Type type; + CHECK_RESULT(ParseRefKind(&type)); + ErrorUnlessOpcodeEnabled(token); + const_.loc = GetLocation(); + const_.set_null(type); + break; + } + case TokenType::RefFunc: { + auto token = Consume(); + ErrorUnlessOpcodeEnabled(token); + const_.loc = GetLocation(); + const_.set_funcref(); + break; + } + case TokenType::RefExtern: + CHECK_RESULT(ParseExternref(&const_)); + break; + default: + assert(!"unreachable"); + return Result::Error; + } + EXPECT(Rpar); + consts->push_back(const_); + } + + return Result::Ok; +} + +Result WastParser::ParseBlockInstr(std::unique_ptr<Expr>* out_expr) { + WABT_TRACE(ParseBlockInstr); + Location loc = GetLocation(); + + switch (Peek()) { + case TokenType::Block: { + Consume(); + auto expr = std::make_unique<BlockExpr>(loc); + CHECK_RESULT(ParseLabelOpt(&expr->block.label)); + CHECK_RESULT(ParseBlock(&expr->block)); + EXPECT(End); + CHECK_RESULT(ParseEndLabelOpt(expr->block.label)); + *out_expr = std::move(expr); + break; + } + + case TokenType::Loop: { + Consume(); + auto expr = std::make_unique<LoopExpr>(loc); + CHECK_RESULT(ParseLabelOpt(&expr->block.label)); + CHECK_RESULT(ParseBlock(&expr->block)); + EXPECT(End); + CHECK_RESULT(ParseEndLabelOpt(expr->block.label)); + *out_expr = std::move(expr); + break; + } + + case TokenType::If: { + Consume(); + auto expr = std::make_unique<IfExpr>(loc); + CHECK_RESULT(ParseLabelOpt(&expr->true_.label)); + CHECK_RESULT(ParseBlock(&expr->true_)); + if (Match(TokenType::Else)) { + CHECK_RESULT(ParseEndLabelOpt(expr->true_.label)); + CHECK_RESULT(ParseTerminatingInstrList(&expr->false_)); + expr->false_end_loc = GetLocation(); + } + EXPECT(End); + CHECK_RESULT(ParseEndLabelOpt(expr->true_.label)); + *out_expr = std::move(expr); + break; + } + + case TokenType::Try: { + ErrorUnlessOpcodeEnabled(Consume()); + auto expr = std::make_unique<TryExpr>(loc); + CatchVector catches; + CHECK_RESULT(ParseLabelOpt(&expr->block.label)); + CHECK_RESULT(ParseBlock(&expr->block)); + if (IsCatch(Peek())) { + CHECK_RESULT(ParseCatchInstrList(&expr->catches)); + expr->kind = TryKind::Catch; + } else if (PeekMatch(TokenType::Delegate)) { + Consume(); + Var var; + CHECK_RESULT(ParseVar(&var)); + expr->delegate_target = var; + expr->kind = TryKind::Delegate; + } + CHECK_RESULT(ErrorIfLpar({"a valid try clause"})); + expr->block.end_loc = GetLocation(); + if (expr->kind != TryKind::Delegate) { + EXPECT(End); + } + CHECK_RESULT(ParseEndLabelOpt(expr->block.label)); + *out_expr = std::move(expr); + break; + } + + default: + assert( + !"ParseBlockInstr should only be called when IsBlockInstr() is true"); + return Result::Error; + } + + return Result::Ok; +} + +Result WastParser::ParseLabelOpt(std::string* out_label) { + WABT_TRACE(ParseLabelOpt); + if (PeekMatch(TokenType::Var)) { + *out_label = std::string(Consume().text()); + } else { + out_label->clear(); + } + return Result::Ok; +} + +Result WastParser::ParseEndLabelOpt(const std::string& begin_label) { + WABT_TRACE(ParseEndLabelOpt); + Location loc = GetLocation(); + std::string end_label; + CHECK_RESULT(ParseLabelOpt(&end_label)); + if (!end_label.empty()) { + if (begin_label.empty()) { + Error(loc, "unexpected label \"%s\"", end_label.c_str()); + } else if (begin_label != end_label) { + Error(loc, "mismatching label \"%s\" != \"%s\"", begin_label.c_str(), + end_label.c_str()); + } + } + return Result::Ok; +} + +Result WastParser::ParseBlockDeclaration(BlockDeclaration* decl) { + WABT_TRACE(ParseBlockDeclaration); + FuncDeclaration func_decl; + CHECK_RESULT(ParseTypeUseOpt(&func_decl)); + CHECK_RESULT(ParseUnboundFuncSignature(&func_decl.sig)); + decl->has_func_type = func_decl.has_func_type; + decl->type_var = func_decl.type_var; + decl->sig = func_decl.sig; + return Result::Ok; +} + +Result WastParser::ParseBlock(Block* block) { + WABT_TRACE(ParseBlock); + CHECK_RESULT(ParseBlockDeclaration(&block->decl)); + CHECK_RESULT(ParseInstrList(&block->exprs)); + block->end_loc = GetLocation(); + return Result::Ok; +} + +Result WastParser::ParseExprList(ExprList* exprs) { + WABT_TRACE(ParseExprList); + ExprList new_exprs; + while (PeekMatchExpr()) { + if (Succeeded(ParseExpr(&new_exprs))) { + exprs->splice(exprs->end(), new_exprs); + } else { + CHECK_RESULT(Synchronize(IsExpr)); + } + } + return Result::Ok; +} + +Result WastParser::ParseExpr(ExprList* exprs) { + WABT_TRACE(ParseExpr); + if (!PeekMatch(TokenType::Lpar)) { + return Result::Error; + } + + if (IsPlainInstr(Peek(1))) { + Consume(); + std::unique_ptr<Expr> expr; + CHECK_RESULT(ParsePlainInstr(&expr)); + CHECK_RESULT(ParseExprList(exprs)); + CHECK_RESULT(ErrorIfLpar({"an expr"})); + exprs->push_back(std::move(expr)); + } else { + Location loc = GetLocation(); + + switch (Peek(1)) { + case TokenType::Block: { + Consume(); + Consume(); + auto expr = std::make_unique<BlockExpr>(loc); + CHECK_RESULT(ParseLabelOpt(&expr->block.label)); + CHECK_RESULT(ParseBlock(&expr->block)); + exprs->push_back(std::move(expr)); + break; + } + + case TokenType::Loop: { + Consume(); + Consume(); + auto expr = std::make_unique<LoopExpr>(loc); + CHECK_RESULT(ParseLabelOpt(&expr->block.label)); + CHECK_RESULT(ParseBlock(&expr->block)); + exprs->push_back(std::move(expr)); + break; + } + + case TokenType::If: { + Consume(); + Consume(); + auto expr = std::make_unique<IfExpr>(loc); + + CHECK_RESULT(ParseLabelOpt(&expr->true_.label)); + CHECK_RESULT(ParseBlockDeclaration(&expr->true_.decl)); + + if (PeekMatchExpr()) { + ExprList cond; + CHECK_RESULT(ParseExpr(&cond)); + exprs->splice(exprs->end(), cond); + } + + if (MatchLpar(TokenType::Then)) { + CHECK_RESULT(ParseTerminatingInstrList(&expr->true_.exprs)); + expr->true_.end_loc = GetLocation(); + EXPECT(Rpar); + + if (MatchLpar(TokenType::Else)) { + CHECK_RESULT(ParseTerminatingInstrList(&expr->false_)); + EXPECT(Rpar); + } else if (PeekMatchExpr()) { + CHECK_RESULT(ParseExpr(&expr->false_)); + } + expr->false_end_loc = GetLocation(); + } else if (PeekMatchExpr()) { + CHECK_RESULT(ParseExpr(&expr->true_.exprs)); + expr->true_.end_loc = GetLocation(); + if (PeekMatchExpr()) { + CHECK_RESULT(ParseExpr(&expr->false_)); + expr->false_end_loc = GetLocation(); + } + } else { + ConsumeIfLpar(); + return ErrorExpected({"then block"}, "(then ...)"); + } + + exprs->push_back(std::move(expr)); + break; + } + + case TokenType::Try: { + Consume(); + ErrorUnlessOpcodeEnabled(Consume()); + + auto expr = std::make_unique<TryExpr>(loc); + CHECK_RESULT(ParseLabelOpt(&expr->block.label)); + CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl)); + EXPECT(Lpar); + EXPECT(Do); + CHECK_RESULT(ParseInstrList(&expr->block.exprs)); + EXPECT(Rpar); + if (PeekMatch(TokenType::Lpar)) { + Consume(); + TokenType type = Peek(); + switch (type) { + case TokenType::Catch: + case TokenType::CatchAll: + CHECK_RESULT(ParseCatchExprList(&expr->catches)); + expr->kind = TryKind::Catch; + break; + case TokenType::Delegate: { + Consume(); + Var var; + CHECK_RESULT(ParseVar(&var)); + expr->delegate_target = var; + expr->kind = TryKind::Delegate; + EXPECT(Rpar); + break; + } + default: + ErrorExpected({"catch", "catch_all", "delegate"}); + break; + } + } + CHECK_RESULT(ErrorIfLpar({"a valid try clause"})); + expr->block.end_loc = GetLocation(); + exprs->push_back(std::move(expr)); + break; + } + + default: + assert(!"ParseExpr should only be called when IsExpr() is true"); + return Result::Error; + } + } + + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseCatchInstrList(CatchVector* catches) { + WABT_TRACE(ParseCatchInstrList); + bool parsedCatch = false; + bool parsedCatchAll = false; + + while (IsCatch(Peek())) { + Catch catch_(GetLocation()); + + auto token = Consume(); + if (token.token_type() == TokenType::Catch) { + CHECK_RESULT(ParseVar(&catch_.var)); + } else { + if (parsedCatchAll) { + Error(token.loc, "multiple catch_all clauses not allowed"); + return Result::Error; + } + parsedCatchAll = true; + } + + CHECK_RESULT(ParseInstrList(&catch_.exprs)); + catches->push_back(std::move(catch_)); + parsedCatch = true; + } + + if (!parsedCatch) { + return ErrorExpected({"catch"}); + } + + return Result::Ok; +} + +Result WastParser::ParseCatchExprList(CatchVector* catches) { + WABT_TRACE(ParseCatchExprList); + bool parsedCatchAll = false; + + do { + Catch catch_(GetLocation()); + + auto token = Consume(); + if (token.token_type() == TokenType::Catch) { + CHECK_RESULT(ParseVar(&catch_.var)); + } else { + if (parsedCatchAll) { + Error(token.loc, "multiple catch_all clauses not allowed"); + return Result::Error; + } + parsedCatchAll = true; + } + + CHECK_RESULT(ParseTerminatingInstrList(&catch_.exprs)); + EXPECT(Rpar); + catches->push_back(std::move(catch_)); + } while (Match(TokenType::Lpar) && IsCatch(Peek())); + + return Result::Ok; +} + +Result WastParser::ParseGlobalType(Global* global) { + WABT_TRACE(ParseGlobalType); + if (MatchLpar(TokenType::Mut)) { + global->mutable_ = true; + Var type; + CHECK_RESULT(ParseValueType(&type)); + global->type = Type(type.index()); + CHECK_RESULT(ErrorIfLpar({"i32", "i64", "f32", "f64"})); + EXPECT(Rpar); + } else { + Var type; + CHECK_RESULT(ParseValueType(&type)); + global->type = Type(type.index()); + } + + return Result::Ok; +} + +Result WastParser::ParseCommandList(Script* script, + CommandPtrVector* commands) { + WABT_TRACE(ParseCommandList); + while (IsCommand(PeekPair())) { + CommandPtr command; + if (Succeeded(ParseCommand(script, &command))) { + commands->push_back(std::move(command)); + } else { + CHECK_RESULT(Synchronize(IsCommand)); + } + } + return Result::Ok; +} + +Result WastParser::ParseCommand(Script* script, CommandPtr* out_command) { + WABT_TRACE(ParseCommand); + switch (Peek(1)) { + case TokenType::AssertException: + return ParseAssertExceptionCommand(out_command); + + case TokenType::AssertExhaustion: + return ParseAssertExhaustionCommand(out_command); + + case TokenType::AssertInvalid: + return ParseAssertInvalidCommand(out_command); + + case TokenType::AssertMalformed: + return ParseAssertMalformedCommand(out_command); + + case TokenType::AssertReturn: + return ParseAssertReturnCommand(out_command); + + case TokenType::AssertTrap: + return ParseAssertTrapCommand(out_command); + + case TokenType::AssertUnlinkable: + return ParseAssertUnlinkableCommand(out_command); + + case TokenType::Get: + case TokenType::Invoke: + return ParseActionCommand(out_command); + + case TokenType::Module: + return ParseModuleCommand(script, out_command); + + case TokenType::Register: + return ParseRegisterCommand(out_command); + + case TokenType::Input: + return ParseInputCommand(out_command); + + case TokenType::Output: + return ParseOutputCommand(out_command); + + default: + assert(!"ParseCommand should only be called when IsCommand() is true"); + return Result::Error; + } +} + +Result WastParser::ParseAssertExceptionCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertExceptionCommand); + return ParseAssertActionCommand<AssertExceptionCommand>( + TokenType::AssertException, out_command); +} + +Result WastParser::ParseAssertExhaustionCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertExhaustionCommand); + return ParseAssertActionTextCommand<AssertExhaustionCommand>( + TokenType::AssertExhaustion, out_command); +} + +Result WastParser::ParseAssertInvalidCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertInvalidCommand); + return ParseAssertScriptModuleCommand<AssertInvalidCommand>( + TokenType::AssertInvalid, out_command); +} + +Result WastParser::ParseAssertMalformedCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertMalformedCommand); + return ParseAssertScriptModuleCommand<AssertMalformedCommand>( + TokenType::AssertMalformed, out_command); +} + +Result WastParser::ParseAssertReturnCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertReturnCommand); + EXPECT(Lpar); + EXPECT(AssertReturn); + auto command = std::make_unique<AssertReturnCommand>(); + CHECK_RESULT(ParseAction(&command->action)); + CHECK_RESULT(ParseExpectedValues(&command->expected)); + EXPECT(Rpar); + *out_command = std::move(command); + return Result::Ok; +} + +Result WastParser::ParseAssertTrapCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertTrapCommand); + EXPECT(Lpar); + EXPECT(AssertTrap); + if (PeekMatchLpar(TokenType::Module)) { + auto command = std::make_unique<AssertUninstantiableCommand>(); + CHECK_RESULT(ParseScriptModule(&command->module)); + CHECK_RESULT(ParseQuotedText(&command->text)); + *out_command = std::move(command); + } else { + auto command = std::make_unique<AssertTrapCommand>(); + CHECK_RESULT(ParseAction(&command->action)); + CHECK_RESULT(ParseQuotedText(&command->text)); + *out_command = std::move(command); + } + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseAssertUnlinkableCommand(CommandPtr* out_command) { + WABT_TRACE(ParseAssertUnlinkableCommand); + return ParseAssertScriptModuleCommand<AssertUnlinkableCommand>( + TokenType::AssertUnlinkable, out_command); +} + +Result WastParser::ParseActionCommand(CommandPtr* out_command) { + WABT_TRACE(ParseActionCommand); + auto command = std::make_unique<ActionCommand>(); + CHECK_RESULT(ParseAction(&command->action)); + *out_command = std::move(command); + return Result::Ok; +} + +Result WastParser::ParseModuleCommand(Script* script, CommandPtr* out_command) { + WABT_TRACE(ParseModuleCommand); + std::unique_ptr<ScriptModule> script_module; + CHECK_RESULT(ParseScriptModule(&script_module)); + + Module* module = nullptr; + + switch (script_module->type()) { + case ScriptModuleType::Text: { + auto command = std::make_unique<ModuleCommand>(); + module = &command->module; + *module = std::move(cast<TextScriptModule>(script_module.get())->module); + *out_command = std::move(command); + break; + } + + case ScriptModuleType::Binary: { + auto command = std::make_unique<ScriptModuleCommand>(); + module = &command->module; + auto* bsm = cast<BinaryScriptModule>(script_module.get()); + ReadBinaryOptions options; +#if WABT_TRACING + auto log_stream = FileStream::CreateStdout(); + options.log_stream = log_stream.get(); +#endif + options.features = options_->features; + Errors errors; + const char* filename = "<text>"; + ReadBinaryIr(filename, bsm->data.data(), bsm->data.size(), options, + &errors, module); + module->name = bsm->name; + module->loc = bsm->loc; + for (const auto& error : errors) { + assert(error.error_level == ErrorLevel::Error); + if (error.loc.offset == kInvalidOffset) { + Error(bsm->loc, "error in binary module: %s", error.message.c_str()); + } else { + Error(bsm->loc, "error in binary module: @0x%08" PRIzx ": %s", + error.loc.offset, error.message.c_str()); + } + } + + command->script_module = std::move(script_module); + *out_command = std::move(command); + break; + } + + case ScriptModuleType::Quoted: + return ErrorExpected({"a binary module", "a text module"}); + } + + // script is nullptr when ParseModuleCommand is called from ParseModule. + if (script) { + Index command_index = script->commands.size(); + + if (!module->name.empty()) { + script->module_bindings.emplace(module->name, + Binding(module->loc, command_index)); + } + + last_module_index_ = command_index; + } + + return Result::Ok; +} + +Result WastParser::ParseRegisterCommand(CommandPtr* out_command) { + WABT_TRACE(ParseRegisterCommand); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Register); + std::string text; + Var var; + CHECK_RESULT(ParseQuotedText(&text)); + ParseVarOpt(&var, Var(last_module_index_, loc)); + EXPECT(Rpar); + out_command->reset(new RegisterCommand(text, var)); + return Result::Ok; +} + +Result WastParser::ParseInputCommand(CommandPtr*) { + // Parse the input command, but always fail since this command is not + // actually supported. + WABT_TRACE(ParseInputCommand); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Input); + Error(loc, "input command is not supported"); + Var var; + std::string text; + ParseVarOpt(&var); + CHECK_RESULT(ParseQuotedText(&text)); + EXPECT(Rpar); + return Result::Error; +} + +Result WastParser::ParseOutputCommand(CommandPtr*) { + // Parse the output command, but always fail since this command is not + // actually supported. + WABT_TRACE(ParseOutputCommand); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Output); + Error(loc, "output command is not supported"); + Var var; + std::string text; + ParseVarOpt(&var); + if (Peek() == TokenType::Text) { + CHECK_RESULT(ParseQuotedText(&text)); + } + EXPECT(Rpar); + return Result::Error; +} + +Result WastParser::ParseAction(ActionPtr* out_action) { + WABT_TRACE(ParseAction); + EXPECT(Lpar); + Location loc = GetLocation(); + + switch (Peek()) { + case TokenType::Invoke: { + Consume(); + auto action = std::make_unique<InvokeAction>(loc); + ParseVarOpt(&action->module_var, Var(last_module_index_, loc)); + CHECK_RESULT(ParseQuotedText(&action->name)); + CHECK_RESULT(ParseConstList(&action->args, ConstType::Normal)); + *out_action = std::move(action); + break; + } + + case TokenType::Get: { + Consume(); + auto action = std::make_unique<GetAction>(loc); + ParseVarOpt(&action->module_var, Var(last_module_index_, loc)); + CHECK_RESULT(ParseQuotedText(&action->name)); + *out_action = std::move(action); + break; + } + + default: + return ErrorExpected({"invoke", "get"}); + } + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseExpectedValues(ExpectationPtr* expectation) { + WABT_TRACE(ParseExpectedValues); + Location loc = GetLocation(); + if (PeekMatchLpar(TokenType::Either)) { + auto either = std::make_unique<EitherExpectation>(loc); + CHECK_RESULT(ParseEither(&either->expected)); + *expectation = std::move(either); + } else { + auto values = std::make_unique<ValueExpectation>(loc); + CHECK_RESULT(ParseConstList(&values->expected, ConstType::Expectation)); + *expectation = std::move(values); + } + return Result::Ok; +} + +Result WastParser::ParseEither(ConstVector* alternatives) { + WABT_TRACE(ParseEither); + MatchLpar(TokenType::Either); + CHECK_RESULT(ParseConstList(alternatives, ConstType::Expectation)); + EXPECT(Rpar); + return Result::Ok; +} + +Result WastParser::ParseScriptModule( + std::unique_ptr<ScriptModule>* out_module) { + WABT_TRACE(ParseScriptModule); + EXPECT(Lpar); + Location loc = GetLocation(); + EXPECT(Module); + std::string name; + ParseBindVarOpt(&name); + + switch (Peek()) { + case TokenType::Bin: { + Consume(); + std::vector<uint8_t> data; + // TODO(binji): The spec allows this to be empty, switch to + // ParseTextListOpt. + CHECK_RESULT(ParseTextList(&data)); + + auto bsm = std::make_unique<BinaryScriptModule>(); + bsm->name = name; + bsm->loc = loc; + bsm->data = std::move(data); + *out_module = std::move(bsm); + break; + } + + case TokenType::Quote: { + Consume(); + std::vector<uint8_t> data; + // TODO(binji): The spec allows this to be empty, switch to + // ParseTextListOpt. + CHECK_RESULT(ParseTextList(&data)); + + auto qsm = std::make_unique<QuotedScriptModule>(); + qsm->name = name; + qsm->loc = loc; + qsm->data = std::move(data); + *out_module = std::move(qsm); + break; + } + + default: { + auto tsm = std::make_unique<TextScriptModule>(); + tsm->module.name = name; + tsm->module.loc = loc; + if (IsModuleField(PeekPair())) { + CHECK_RESULT(ParseModuleFieldList(&tsm->module)); + } else if (!PeekMatch(TokenType::Rpar)) { + ConsumeIfLpar(); + return ErrorExpected({"a module field"}); + } + *out_module = std::move(tsm); + break; + } + } + + EXPECT(Rpar); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseAssertActionCommand(TokenType token_type, + CommandPtr* out_command) { + WABT_TRACE(ParseAssertActionCommand); + EXPECT(Lpar); + CHECK_RESULT(Expect(token_type)); + auto command = std::make_unique<T>(); + CHECK_RESULT(ParseAction(&command->action)); + EXPECT(Rpar); + *out_command = std::move(command); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseAssertActionTextCommand(TokenType token_type, + CommandPtr* out_command) { + WABT_TRACE(ParseAssertActionTextCommand); + EXPECT(Lpar); + CHECK_RESULT(Expect(token_type)); + auto command = std::make_unique<T>(); + CHECK_RESULT(ParseAction(&command->action)); + CHECK_RESULT(ParseQuotedText(&command->text)); + EXPECT(Rpar); + *out_command = std::move(command); + return Result::Ok; +} + +template <typename T> +Result WastParser::ParseAssertScriptModuleCommand(TokenType token_type, + CommandPtr* out_command) { + WABT_TRACE(ParseAssertScriptModuleCommand); + EXPECT(Lpar); + CHECK_RESULT(Expect(token_type)); + auto command = std::make_unique<T>(); + CHECK_RESULT(ParseScriptModule(&command->module)); + CHECK_RESULT(ParseQuotedText(&command->text)); + EXPECT(Rpar); + *out_command = std::move(command); + return Result::Ok; +} + +void WastParser::CheckImportOrdering(Module* module) { + if (module->funcs.size() != module->num_func_imports || + module->tables.size() != module->num_table_imports || + module->memories.size() != module->num_memory_imports || + module->globals.size() != module->num_global_imports || + module->tags.size() != module->num_tag_imports) { + Error(GetLocation(), + "imports must occur before all non-import definitions"); + } +} + +Result ParseWatModule(WastLexer* lexer, + std::unique_ptr<Module>* out_module, + Errors* errors, + WastParseOptions* options) { + assert(out_module != nullptr); + assert(options != nullptr); + WastParser parser(lexer, errors, options); + CHECK_RESULT(parser.ParseModule(out_module)); + return Result::Ok; +} + +Result ParseWastScript(WastLexer* lexer, + std::unique_ptr<Script>* out_script, + Errors* errors, + WastParseOptions* options) { + assert(out_script != nullptr); + assert(options != nullptr); + WastParser parser(lexer, errors, options); + CHECK_RESULT(parser.ParseScript(out_script)); + CHECK_RESULT(ResolveNamesScript(out_script->get(), errors)); + return Result::Ok; +} + +} // namespace wabt |