/* * 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::ThrowRef: 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: case TokenType::TryTable: 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 IsTryTableCatch(TokenTypePair pair) { return pair[0] == TokenType::Lpar && (pair[1] == TokenType::Catch || pair[1] == TokenType::CatchAll || pair[1] == TokenType::CatchRef || pair[1] == TokenType::CatchAllRef); } 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) { assert(n <= 1); 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 or custom section. In those cases, 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) || cur.text() == "custom") { 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() || options_->features.exceptions_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) { GetToken(); 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)) { 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 (!IsExpr(PeekPair()) || 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", "exnref", "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; case Type::ExnRef: is_enabled = options_->features.exceptions_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", "exnref"}); } 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)); } // With memory64, offsets > UINT32_MAX for i32 memories are no longer // malformed (just invalid) if ((!options_->features.memory64_enabled()) && (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::ParsePageSize(uint32_t* out_page_size) { WABT_TRACE(ParsePageSize); Result result = Result::Ok; if (PeekMatchLpar(TokenType::PageSize)) { if (!options_->features.custom_page_sizes_enabled()) { Error(GetLocation(), "Specifying memory page size is not allowed"); return Result::Error; } EXPECT(Lpar); EXPECT(PageSize); auto token = GetToken(); if (!token.HasLiteral()) { Error(GetLocation(), "malformed custom page size"); return Result::Error; } auto sv = token.literal().text; result |= ParseInt32(sv, out_page_size, ParseIntType::UnsignedOnly); if (*out_page_size > UINT32_MAX || *out_page_size <= 0 || (*out_page_size & (*out_page_size - 1))) { Error(GetLocation(), "malformed custom page size"); return Result::Error; } Consume(); EXPECT(Rpar); } return result; } 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()) || PeekIsCustom()) { // Parse an inline module (i.e. one with no surrounding (module)). CHECK_RESULT(ParseModuleFieldList(module.get())); } else if (PeekMatch(TokenType::Eof)) { errors_->emplace_back(ErrorLevel::Warning, GetLocation(), "empty module"); } else { ConsumeIfLpar(); ErrorExpected({"a module field", "a module"}); } EXPECT(Eof); if (!HasError()) { *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()) || PeekIsCustom()) { // 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 if (PeekMatch(TokenType::Eof)) { errors_->emplace_back(ErrorLevel::Warning, GetLocation(), "empty script"); } else { ConsumeIfLpar(); ErrorExpected({"a module field", "a command"}); } EXPECT(Eof); if (!HasError()) { *out_script = std::move(script); return Result::Ok; } else { return Result::Error; } } Result WastParser::ParseCustomSectionAnnotation(Module* module) { WABT_TRACE(ParseCustomSectionAnnotation); Location loc = GetLocation(); Token token = Consume(); if (token.text() != "custom") { assert( !"ParseCustomSectionAnnotation should only be called if PeekIsCustom() is true"); return Result::Error; } std::string section_name; CHECK_RESULT(ParseQuotedText(§ion_name)); if (Match(TokenType::Lpar)) { if (!PeekMatch(TokenType::After) && !PeekMatch(TokenType::Before)) { return ErrorExpected({"before", "after"}); } Consume(); switch (Peek()) { case TokenType::Function: case TokenType::Type: case TokenType::Import: case TokenType::Export: case TokenType::Table: case TokenType::Global: case TokenType::Elem: case TokenType::Data: case TokenType::Memory: case TokenType::Code: case TokenType::Start: { Consume(); break; } default: { return ErrorExpected({"type", "import", "function", "table", "memory", "global", "export", "start", "elem", "code", "data"}); } } EXPECT(Rpar); } std::vector<uint8_t> data; CHECK_RESULT(ParseTextList(&data)); EXPECT(Rpar); Custom custom = Custom(loc, section_name, data); module->customs.push_back(custom); return Result::Ok; } bool WastParser::PeekIsCustom() { // If IsLparAnn succeeds, tokens_.front() must have text, as it is an LparAnn // token. return options_->features.annotations_enabled() && IsLparAnn(PeekPair()) && tokens_.front().text() == "custom"; } Result WastParser::ParseModuleFieldList(Module* module) { WABT_TRACE(ParseModuleFieldList); while (IsModuleField(PeekPair()) || PeekIsCustom()) { if (PeekIsCustom()) { CHECK_RESULT(ParseCustomSectionAnnotation(module)); continue; } 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(ParseLimitsIndex(&import->table.elem_limits)); 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); import->memory.page_size = WABT_DEFAULT_PAGE_SIZE; CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits)); CHECK_RESULT(ParseLimits(&import->memory.page_limits)); CHECK_RESULT(ParsePageSize(&import->memory.page_size)); 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); import->memory.page_size = WABT_DEFAULT_PAGE_SIZE; CHECK_RESULT(ParseInlineImport(import.get())); CHECK_RESULT(ParseLimitsIndex(&import->memory.page_limits)); CHECK_RESULT(ParseLimits(&import->memory.page_limits)); CHECK_RESULT(ParsePageSize(&import->memory.page_size)); auto field = std::make_unique<ImportModuleField>(std::move(import), GetLocation()); module->AppendField(std::move(field)); } else { auto field = std::make_unique<MemoryModuleField>(loc, name); field->memory.page_size = WABT_DEFAULT_PAGE_SIZE; CHECK_RESULT(ParseLimitsIndex(&field->memory.page_limits)); if (PeekMatchLpar(TokenType::PageSize)) { // this is the data abbreviation (no limits) CHECK_RESULT(ParsePageSize(&field->memory.page_size)); if (!PeekMatchLpar(TokenType::Data)) { ConsumeIfLpar(); return ErrorExpected({"inline data segment"}); } } 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 num_pages = WABT_BYTES_TO_MIN_PAGES(data_segment.data.size(), field->memory.page_size); field->memory.page_limits.initial = num_pages; field->memory.page_limits.max = num_pages; 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)); CHECK_RESULT(ParsePageSize(&field->memory.page_size)); 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(ParseLimitsIndex(&import->table.elem_limits)); 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 { auto field = std::make_unique<TableModuleField>(loc, name); auto& table = field->table; CHECK_RESULT(ParseLimitsIndex(&table.elem_limits)); 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()); auto offset = table.elem_limits.is_64 ? Const::I64(0) : Const::I32(0); elem_segment.offset.push_back(std::make_unique<ConstExpr>(offset)); 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); table.elem_limits.initial = elem_segment.elem_exprs.size(); table.elem_limits.max = elem_segment.elem_exprs.size(); table.elem_limits.has_max = true; table.elem_type = elem_type; module->AppendField(std::move(field)); module->AppendField(std::move(elem_segment_field)); } else { CHECK_RESULT(ParseLimits(&table.elem_limits)); CHECK_RESULT(ParseRefType(&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 destmemidx; Var srcmemidx; CHECK_RESULT(ParseMemidx(loc, &destmemidx)); CHECK_RESULT(ParseMemidx(loc, &srcmemidx)); out_expr->reset(new T(destmemidx, srcmemidx, 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::ThrowRef: ErrorUnlessOpcodeEnabled(Consume()); out_expr->reset(new ThrowRefExpr(loc)); 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()) { result = Result::Error; break; } 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()) { result = Result::Error; break; } 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; } case TokenType::TryTable: { ErrorUnlessOpcodeEnabled(Consume()); auto expr = std::make_unique<TryTableExpr>(loc); CHECK_RESULT(ParseLabelOpt(&expr->block.label)); CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl)); CHECK_RESULT(ParseTryTableCatches(&expr->catches)); CHECK_RESULT(ParseInstrList(&expr->block.exprs)); 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)); while (PeekMatchExpr()) { ExprList cond; CHECK_RESULT(ParseExpr(&cond)); exprs->splice(exprs->end(), cond); } EXPECT(Lpar); if (!Match(TokenType::Then)) { return ErrorExpected({"then block"}, "(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); } expr->false_end_loc = GetLocation(); 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; } case TokenType::TryTable: { Consume(); ErrorUnlessOpcodeEnabled(Consume()); auto expr = std::make_unique<TryTableExpr>(loc); CHECK_RESULT(ParseLabelOpt(&expr->block.label)); CHECK_RESULT(ParseBlockDeclaration(&expr->block.decl)); CHECK_RESULT(ParseTryTableCatches(&expr->catches)); CHECK_RESULT(ParseInstrList(&expr->block.exprs)); 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::ParseTryTableCatches(TryTableVector* catches) { WABT_TRACE(ParseTryTableCatches); while (IsTryTableCatch(PeekPair())) { Consume(); TableCatch catch_(GetLocation()); auto token = Consume(); switch (token.token_type()) { case TokenType::Catch: catch_.kind = CatchKind::Catch; break; case TokenType::CatchRef: catch_.kind = CatchKind::CatchRef; break; case TokenType::CatchAll: catch_.kind = CatchKind::CatchAll; break; case TokenType::CatchAllRef: catch_.kind = CatchKind::CatchAllRef; break; default: WABT_UNREACHABLE; } if (catch_.kind == CatchKind::Catch || catch_.kind == CatchKind::CatchRef) { CHECK_RESULT(ParseVar(&catch_.tag)); } CHECK_RESULT(ParseVar(&catch_.target)); EXPECT(Rpar); catches->push_back(std::move(catch_)); } 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) { 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: auto command = std::make_unique<ModuleCommand>(); module = &command->module; auto* qsm = cast<QuotedScriptModule>(script_module.get()); Errors errors; const char* filename = "<text>"; std::unique_ptr<Module> m; std::unique_ptr<WastLexer> lexer = WastLexer::CreateBufferLexer( filename, qsm->data.data(), qsm->data.size(), &errors); auto result = ParseWatModule(lexer.get(), &m, &errors, options_); for (const auto& error : errors) { if (error.loc.offset == kInvalidOffset) { Error(qsm->loc, "error in quoted module: %s", error.message.c_str()); } else { Error(qsm->loc, "error in quoted module: @0x%08" PRIzx ": %s", error.loc.offset, error.message.c_str()); } } if (Succeeded(result)) { *module = std::move(*m.get()); } *out_command = std::move(command); break; } // 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()) || PeekIsCustom()) { 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"); } } bool WastParser::HasError() const { return std::any_of(errors_->begin(), errors_->end(), [](const auto& x) { return x.error_level == ErrorLevel::Error; }); } void WastParser::TokenQueue::push_back(Token t) { assert(!tokens[!i]); tokens[!i] = t; if (empty()) { i = !i; } } void WastParser::TokenQueue::pop_front() { assert(tokens[i]); tokens[i].reset(); i = !i; } const Token& WastParser::TokenQueue::at(size_t n) const { assert(n <= 1); return tokens[i ^ static_cast<bool>(n)].value(); } const Token& WastParser::TokenQueue::front() const { return at(0); } bool WastParser::TokenQueue::empty() const { return !tokens[i]; } size_t WastParser::TokenQueue::size() const { return empty() ? 0 : 1 + tokens[!i].has_value(); } 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