From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- third_party/wasm2c/src/wast-parser.cc | 3616 +++++++++++++++++++++++++++++++++ 1 file changed, 3616 insertions(+) create mode 100644 third_party/wasm2c/src/wast-parser.cc (limited to 'third_party/wasm2c/src/wast-parser.cc') diff --git a/third_party/wasm2c/src/wast-parser.cc b/third_party/wasm2c/src/wast-parser.cc new file mode 100644 index 0000000000..a596f018dc --- /dev/null +++ b/third_party/wasm2c/src/wast-parser.cc @@ -0,0 +1,3616 @@ +/* + * Copyright 2017 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "wabt/wast-parser.h" + +#include "wabt/binary-reader-ir.h" +#include "wabt/binary-reader.h" +#include "wabt/cast.h" +#include "wabt/expr-visitor.h" +#include "wabt/resolve-names.h" +#include "wabt/stream.h" +#include "wabt/utf8.h" +#include "wabt/validator.h" + +#define WABT_TRACING 0 +#include "wabt/tracing.h" + +#define EXPECT(token_type) CHECK_RESULT(Expect(TokenType::token_type)) + +namespace wabt { + +namespace { + +static const size_t kMaxErrorTokenLength = 80; + +bool IsPowerOfTwo(uint32_t x) { + return x && ((x & (x - 1)) == 0); +} + +template +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(scalar_value); + } else { + if (scalar_value < 0x800) { + *dest++ = static_cast(0xc0 | (scalar_value >> 6)); + } else { + if (scalar_value < 0x10000) { + *dest++ = static_cast(0xe0 | (scalar_value >> 12)); + } else { + *dest++ = static_cast(0xf0 | (scalar_value >> 18)); + *dest++ = + static_cast(0x80 | ((scalar_value >> 12) & 0x3f)); + } + + *dest++ = + static_cast(0x80 | ((scalar_value >> 6) & 0x3f)); + } + + *dest++ = static_cast(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; + +template +void RemoveEscapes(const TextVector& texts, OutputIter out) { + for (std::string_view text : texts) + RemoveEscapes(text, out); +} + +bool IsPlainInstr(TokenType token_type) { + switch (token_type) { + case TokenType::Unreachable: + case TokenType::Nop: + case TokenType::Drop: + case TokenType::Select: + case TokenType::Br: + case TokenType::BrIf: + case TokenType::BrTable: + case TokenType::Return: + case TokenType::ReturnCall: + case TokenType::ReturnCallIndirect: + case TokenType::Call: + case TokenType::CallIndirect: + case TokenType::CallRef: + case TokenType::LocalGet: + case TokenType::LocalSet: + case TokenType::LocalTee: + case TokenType::GlobalGet: + case TokenType::GlobalSet: + case TokenType::Load: + case TokenType::Store: + case TokenType::Const: + case TokenType::Unary: + case TokenType::Binary: + case TokenType::Compare: + case TokenType::Convert: + case TokenType::MemoryCopy: + case TokenType::DataDrop: + case TokenType::MemoryFill: + case TokenType::MemoryGrow: + case TokenType::MemoryInit: + case TokenType::MemorySize: + case TokenType::TableCopy: + case TokenType::ElemDrop: + case TokenType::TableInit: + case TokenType::TableGet: + case TokenType::TableSet: + case TokenType::TableGrow: + case TokenType::TableSize: + case TokenType::TableFill: + case TokenType::Throw: + case TokenType::Rethrow: + case TokenType::RefFunc: + case TokenType::RefNull: + case TokenType::RefIsNull: + case TokenType::AtomicLoad: + case TokenType::AtomicStore: + case TokenType::AtomicRmw: + case TokenType::AtomicRmwCmpxchg: + case TokenType::AtomicNotify: + case TokenType::AtomicFence: + case TokenType::AtomicWait: + case TokenType::Ternary: + case TokenType::SimdLaneOp: + case TokenType::SimdLoadLane: + case TokenType::SimdStoreLane: + case TokenType::SimdShuffleOp: + return true; + default: + return false; + } +} + +bool IsBlockInstr(TokenType token_type) { + switch (token_type) { + case TokenType::Block: + case TokenType::Loop: + case TokenType::If: + case TokenType::Try: + return true; + default: + return false; + } +} + +bool IsPlainOrBlockInstr(TokenType token_type) { + return IsPlainInstr(token_type) || IsBlockInstr(token_type); +} + +bool IsExpr(TokenTypePair pair) { + return pair[0] == TokenType::Lpar && IsPlainOrBlockInstr(pair[1]); +} + +bool IsInstr(TokenTypePair pair) { + return IsPlainOrBlockInstr(pair[0]) || IsExpr(pair); +} + +bool IsLparAnn(TokenTypePair pair) { + return pair[0] == TokenType::LparAnn; +} + +bool IsCatch(TokenType token_type) { + return token_type == TokenType::Catch || token_type == TokenType::CatchAll; +} + +bool IsModuleField(TokenTypePair pair) { + if (pair[0] != TokenType::Lpar) { + return false; + } + + switch (pair[1]) { + case TokenType::Data: + case TokenType::Elem: + case TokenType::Tag: + case TokenType::Export: + case TokenType::Func: + case TokenType::Type: + case TokenType::Global: + case TokenType::Import: + case TokenType::Memory: + case TokenType::Start: + case TokenType::Table: + return true; + default: + return false; + } +} + +bool IsCommand(TokenTypePair pair) { + if (pair[0] != TokenType::Lpar) { + return false; + } + + switch (pair[1]) { + case TokenType::AssertException: + case TokenType::AssertExhaustion: + case TokenType::AssertInvalid: + case TokenType::AssertMalformed: + case TokenType::AssertReturn: + case TokenType::AssertTrap: + case TokenType::AssertUnlinkable: + case TokenType::Get: + case TokenType::Invoke: + case TokenType::Input: + case TokenType::Module: + case TokenType::Output: + case TokenType::Register: + return true; + default: + return false; + } +} + +bool IsEmptySignature(const FuncSignature& sig) { + return sig.result_types.empty() && sig.param_types.empty(); +} + +bool ResolveFuncTypeWithEmptySignature(const Module& module, + FuncDeclaration* decl) { + // Resolve func type variables where the signature was not specified + // explicitly, e.g.: (func (type 1) ...) + if (decl->has_func_type && IsEmptySignature(decl->sig)) { + const FuncType* func_type = module.GetFuncType(decl->type_var); + if (func_type) { + decl->sig = func_type->sig; + return true; + } + } + return false; +} + +void ResolveTypeName( + const Module& module, + Type& type, + Index index, + const std::unordered_map& 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(loc); + auto func_type = std::make_unique(); + 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(&field)) { + func = &func_field->func; + decl = &func->decl; + } else if (auto* tag_field = dyn_cast(&field)) { + decl = &tag_field->tag.decl; + } else if (auto* import_field = dyn_cast(&field)) { + if (auto* func_import = + dyn_cast(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(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(&field); + export_field->export_.var = Var(index, last_field_loc); + } + + module->AppendFields(fields); +} + +} // End of anonymous namespace + +WastParser::WastParser(WastLexer* lexer, + Errors* errors, + WastParseOptions* options) + : lexer_(lexer), errors_(errors), options_(options) {} + +void WastParser::Error(Location loc, const char* format, ...) { + WABT_SNPRINTF_ALLOCA(buffer, length, format); + errors_->emplace_back(ErrorLevel::Error, loc, buffer); +} + +Token WastParser::GetToken() { + if (tokens_.empty()) { + tokens_.push_back(lexer_->GetToken()); + } + return tokens_.front(); +} + +Location WastParser::GetLocation() { + return GetToken().loc; +} + +TokenType WastParser::Peek(size_t n) { + while (tokens_.size() <= n) { + Token cur = lexer_->GetToken(); + if (cur.token_type() != TokenType::LparAnn) { + tokens_.push_back(cur); + } else { + // Custom annotation. For now, discard until matching Rpar, unless it is + // a code metadata annotation. In that case, we know how to parse it. + if (!options_->features.annotations_enabled()) { + Error(cur.loc, "annotations not enabled: %s", cur.to_string().c_str()); + tokens_.push_back(Token(cur.loc, TokenType::Invalid)); + continue; + } + if (options_->features.code_metadata_enabled() && + cur.text().find("metadata.code.") == 0) { + tokens_.push_back(cur); + continue; + } + int indent = 1; + while (indent > 0) { + cur = lexer_->GetToken(); + switch (cur.token_type()) { + case TokenType::Lpar: + case TokenType::LparAnn: + indent++; + break; + + case TokenType::Rpar: + indent--; + break; + + case TokenType::Eof: + indent = 0; + Error(cur.loc, "unterminated annotation"); + break; + + default: + break; + } + } + } + } + return tokens_.at(n).token_type(); +} + +TokenTypePair WastParser::PeekPair() { + return TokenTypePair{{Peek(), Peek(1)}}; +} + +bool WastParser::PeekMatch(TokenType type, size_t n) { + return Peek(n) == type; +} + +bool WastParser::PeekMatchLpar(TokenType type) { + return Peek() == TokenType::Lpar && Peek(1) == type; +} + +bool WastParser::PeekMatchExpr() { + return IsExpr(PeekPair()); +} + +bool WastParser::PeekMatchRefType() { + return options_->features.function_references_enabled() && + PeekMatchLpar(TokenType::Ref); +} + +bool WastParser::Match(TokenType type) { + if (PeekMatch(type)) { + Consume(); + return true; + } + return false; +} + +bool WastParser::MatchLpar(TokenType type) { + if (PeekMatchLpar(type)) { + Consume(); + Consume(); + return true; + } + return false; +} + +Result WastParser::Expect(TokenType type) { + if (!Match(type)) { + Token token = Consume(); + Error(token.loc, "unexpected token %s, expected %s.", + token.to_string_clamp(kMaxErrorTokenLength).c_str(), + GetTokenTypeName(type)); + return Result::Error; + } + + return Result::Ok; +} + +Token WastParser::Consume() { + assert(!tokens_.empty()); + Token token = tokens_.front(); + tokens_.pop_front(); + return token; +} + +Result WastParser::Synchronize(SynchronizeFunc func) { + static const int kMaxConsumed = 10; + for (int i = 0; i < kMaxConsumed; ++i) { + if (func(PeekPair())) { + return Result::Ok; + } + + Token token = Consume(); + if (token.token_type() == TokenType::Reserved) { + Error(token.loc, "unexpected token %s.", + token.to_string_clamp(kMaxErrorTokenLength).c_str()); + } + } + + return Result::Error; +} + +void WastParser::ErrorUnlessOpcodeEnabled(const Token& token) { + Opcode opcode = token.opcode(); + if (!opcode.IsEnabled(options_->features)) { + Error(token.loc, "opcode not allowed: %s", opcode.GetName()); + } +} + +Result WastParser::ErrorExpected(const std::vector& expected, + const char* example) { + Token token = Consume(); + std::string expected_str; + if (!expected.empty()) { + expected_str = ", expected "; + for (size_t i = 0; i < expected.size(); ++i) { + if (i != 0) { + if (i == expected.size() - 1) { + expected_str += " or "; + } else { + expected_str += ", "; + } + } + + expected_str += expected[i]; + } + + if (example) { + expected_str += " (e.g. "; + expected_str += example; + expected_str += ")"; + } + } + + Error(token.loc, "unexpected token \"%s\"%s.", + token.to_string_clamp(kMaxErrorTokenLength).c_str(), + expected_str.c_str()); + return Result::Error; +} + +Result WastParser::ErrorIfLpar(const std::vector& expected, + const char* example) { + if (Match(TokenType::Lpar)) { + GetToken(); + return ErrorExpected(expected, example); + } + return Result::Ok; +} + +bool WastParser::ParseBindVarOpt(std::string* name) { + WABT_TRACE(ParseBindVarOpt); + if (!PeekMatch(TokenType::Var)) { + return false; + } + Token token = Consume(); + *name = std::string(token.text()); + return true; +} + +Result WastParser::ParseVar(Var* out_var) { + WABT_TRACE(ParseVar); + if (PeekMatch(TokenType::Nat)) { + Token token = Consume(); + std::string_view sv = token.literal().text; + uint64_t index = kInvalidIndex; + if (Failed(ParseUint64(sv, &index))) { + // Print an error, but don't fail parsing. + Error(token.loc, "invalid int \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + + *out_var = Var(index, token.loc); + return Result::Ok; + } else if (PeekMatch(TokenType::Var)) { + Token token = Consume(); + *out_var = Var(token.text(), token.loc); + return Result::Ok; + } else { + return ErrorExpected({"a numeric index", "a name"}, "12 or $foo"); + } +} + +bool WastParser::ParseVarOpt(Var* out_var, Var default_var) { + WABT_TRACE(ParseVarOpt); + if (PeekMatch(TokenType::Nat) || PeekMatch(TokenType::Var)) { + Result result = ParseVar(out_var); + // Should always succeed, the only way it could fail is if the token + // doesn't match. + assert(Succeeded(result)); + WABT_USE(result); + return true; + } else { + *out_var = default_var; + return false; + } +} + +Result WastParser::ParseOffsetExpr(ExprList* out_expr_list) { + WABT_TRACE(ParseOffsetExpr); + if (!ParseOffsetExprOpt(out_expr_list)) { + return ErrorExpected({"an offset expr"}, "(i32.const 123)"); + } + return Result::Ok; +} + +bool WastParser::ParseOffsetExprOpt(ExprList* out_expr_list) { + WABT_TRACE(ParseOffsetExprOpt); + if (MatchLpar(TokenType::Offset)) { + CHECK_RESULT(ParseTerminatingInstrList(out_expr_list)); + EXPECT(Rpar); + return true; + } else if (PeekMatchExpr()) { + CHECK_RESULT(ParseExpr(out_expr_list)); + return true; + } else { + return false; + } +} + +Result WastParser::ParseTextList(std::vector* 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* out_data) { + WABT_TRACE(ParseTextListOpt); + TextVector texts; + while (PeekMatch(TokenType::Text)) + texts.push_back(Consume().text()); + + RemoveEscapes(texts, std::back_inserter(*out_data)); + return !texts.empty(); +} + +Result WastParser::ParseVarList(VarVector* out_var_list) { + WABT_TRACE(ParseVarList); + Var var; + while (ParseVarOpt(&var)) { + out_var_list->emplace_back(var); + } + if (out_var_list->empty()) { + return ErrorExpected({"a var"}, "12 or $foo"); + } else { + return Result::Ok; + } +} + +bool WastParser::ParseElemExprOpt(ExprList* out_elem_expr) { + WABT_TRACE(ParseElemExprOpt); + bool item = MatchLpar(TokenType::Item); + ExprList exprs; + if (item) { + if (ParseTerminatingInstrList(&exprs) != Result::Ok) { + return false; + } + EXPECT(Rpar); + } else { + if (ParseExpr(&exprs) != Result::Ok) { + return false; + } + } + if (!exprs.size()) { + return false; + } + *out_elem_expr = std::move(exprs); + return true; +} + +bool WastParser::ParseElemExprListOpt(ExprListVector* out_list) { + ExprList elem_expr; + while (ParseElemExprOpt(&elem_expr)) { + out_list->push_back(std::move(elem_expr)); + } + return !out_list->empty(); +} + +bool WastParser::ParseElemExprVarListOpt(ExprListVector* out_list) { + WABT_TRACE(ParseElemExprVarListOpt); + Var var; + ExprList init_expr; + while (ParseVarOpt(&var)) { + init_expr.push_back(std::make_unique(var)); + out_list->push_back(std::move(init_expr)); + } + return !out_list->empty(); +} + +Result WastParser::ParseValueType(Var* out_type) { + WABT_TRACE(ParseValueType); + + const bool is_ref_type = PeekMatchRefType(); + const bool is_value_type = PeekMatch(TokenType::ValueType); + + if (!is_value_type && !is_ref_type) { + return ErrorExpected( + {"i32", "i64", "f32", "f64", "v128", "externref", "funcref"}); + } + + if (is_ref_type) { + EXPECT(Lpar); + EXPECT(Ref); + CHECK_RESULT(ParseVar(out_type)); + EXPECT(Rpar); + return Result::Ok; + } + + Token token = Consume(); + Type type = token.type(); + bool is_enabled; + switch (type) { + case Type::V128: + is_enabled = options_->features.simd_enabled(); + break; + case Type::FuncRef: + case Type::ExternRef: + is_enabled = options_->features.reference_types_enabled(); + break; + default: + is_enabled = true; + break; + } + + if (!is_enabled) { + Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); + return Result::Error; + } + + *out_type = Var(type, GetLocation()); + return Result::Ok; +} + +Result WastParser::ParseValueTypeList( + TypeVector* out_type_list, + std::unordered_map* type_names) { + WABT_TRACE(ParseValueTypeList); + while (true) { + if (!PeekMatchRefType() && !PeekMatch(TokenType::ValueType)) { + break; + } + + Var type; + CHECK_RESULT(ParseValueType(&type)); + + if (type.is_index()) { + out_type_list->push_back(Type(type.index())); + } else { + assert(type.is_name()); + assert(options_->features.function_references_enabled()); + type_names->emplace(out_type_list->size(), type.name()); + out_type_list->push_back(Type(Type::Reference, kInvalidIndex)); + } + } + + return Result::Ok; +} + +Result WastParser::ParseRefKind(Type* out_type) { + WABT_TRACE(ParseRefKind); + if (!IsTokenTypeRefKind(Peek())) { + return ErrorExpected({"func", "extern", "exn"}); + } + + Token token = Consume(); + Type type = token.type(); + + if ((type == Type::ExternRef && + !options_->features.reference_types_enabled()) || + ((type == Type::Struct || type == Type::Array) && + !options_->features.gc_enabled())) { + Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); + return Result::Error; + } + + *out_type = type; + return Result::Ok; +} + +Result WastParser::ParseRefType(Type* out_type) { + WABT_TRACE(ParseRefType); + if (!PeekMatch(TokenType::ValueType)) { + return ErrorExpected({"funcref", "externref"}); + } + + Token token = Consume(); + Type type = token.type(); + if (type == Type::ExternRef && + !options_->features.reference_types_enabled()) { + Error(token.loc, "value type not allowed: %s", type.GetName().c_str()); + return Result::Error; + } + + *out_type = type; + return Result::Ok; +} + +bool WastParser::ParseRefTypeOpt(Type* out_type) { + WABT_TRACE(ParseRefTypeOpt); + if (!PeekMatch(TokenType::ValueType)) { + return false; + } + + Token token = Consume(); + Type type = token.type(); + if (type == Type::ExternRef && + !options_->features.reference_types_enabled()) { + return false; + } + + *out_type = type; + return true; +} + +Result WastParser::ParseQuotedText(std::string* text, bool check_utf8) { + WABT_TRACE(ParseQuotedText); + if (!PeekMatch(TokenType::Text)) { + return ErrorExpected({"a quoted string"}, "\"foo\""); + } + + Token token = Consume(); + RemoveEscapes(token.text(), std::back_inserter(*text)); + if (check_utf8 && !IsValidUtf8(text->data(), text->length())) { + Error(token.loc, "quoted string has an invalid utf-8 encoding"); + } + return Result::Ok; +} + +bool WastParser::ParseOffsetOpt(Address* out_offset) { + WABT_TRACE(ParseOffsetOpt); + if (PeekMatch(TokenType::OffsetEqNat)) { + Token token = Consume(); + uint64_t offset64; + std::string_view sv = token.text(); + if (Failed(ParseInt64(sv, &offset64, ParseIntType::SignedAndUnsigned))) { + Error(token.loc, "invalid offset \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + // FIXME: make this depend on the current memory. + if (offset64 > UINT32_MAX) { + Error(token.loc, "offset must be less than or equal to 0xffffffff"); + } + *out_offset = offset64; + return true; + } else { + *out_offset = 0; + return false; + } +} + +bool WastParser::ParseAlignOpt(Address* out_align) { + WABT_TRACE(ParseAlignOpt); + if (PeekMatch(TokenType::AlignEqNat)) { + Token token = Consume(); + std::string_view sv = token.text(); + if (Failed(ParseInt64(sv, out_align, ParseIntType::UnsignedOnly))) { + Error(token.loc, "invalid alignment \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + + if (!IsPowerOfTwo(*out_align)) { + Error(token.loc, "alignment must be power-of-two"); + } + + return true; + } else { + *out_align = WABT_USE_NATURAL_ALIGNMENT; + return false; + } +} + +Result WastParser::ParseMemidx(Location loc, Var* out_memidx) { + WABT_TRACE(ParseMemidx); + if (PeekMatchLpar(TokenType::Memory)) { + if (!options_->features.multi_memory_enabled()) { + Error(loc, "Specifying memory variable is not allowed"); + return Result::Error; + } + EXPECT(Lpar); + EXPECT(Memory); + CHECK_RESULT(ParseVar(out_memidx)); + EXPECT(Rpar); + } else { + if (ParseVarOpt(out_memidx, Var(0, loc)) && + !options_->features.multi_memory_enabled()) { + Error(loc, "Specifying memory variable is not allowed"); + return Result::Error; + } + } + return Result::Ok; +} + +Result WastParser::ParseLimitsIndex(Limits* out_limits) { + WABT_TRACE(ParseLimitsIndex); + + if (PeekMatch(TokenType::ValueType)) { + if (GetToken().type() == Type::I64) { + Consume(); + out_limits->is_64 = true; + } else if (GetToken().type() == Type::I32) { + Consume(); + out_limits->is_64 = false; + } + } + + return Result::Ok; +} + +Result WastParser::ParseLimits(Limits* out_limits) { + WABT_TRACE(ParseLimits); + + CHECK_RESULT(ParseNat(&out_limits->initial, out_limits->is_64)); + if (PeekMatch(TokenType::Nat)) { + CHECK_RESULT(ParseNat(&out_limits->max, out_limits->is_64)); + out_limits->has_max = true; + } else { + out_limits->has_max = false; + } + + if (Match(TokenType::Shared)) { + out_limits->is_shared = true; + } + + return Result::Ok; +} + +Result WastParser::ParseNat(uint64_t* out_nat, bool is_64) { + WABT_TRACE(ParseNat); + if (!PeekMatch(TokenType::Nat)) { + return ErrorExpected({"a natural number"}, "123"); + } + + Token token = Consume(); + std::string_view sv = token.literal().text; + if (Failed(ParseUint64(sv, out_nat)) || (!is_64 && *out_nat > 0xffffffffu)) { + Error(token.loc, "invalid int \"" PRIstringview "\"", + WABT_PRINTF_STRING_VIEW_ARG(sv)); + } + + return Result::Ok; +} + +Result WastParser::ParseModule(std::unique_ptr* out_module) { + WABT_TRACE(ParseModule); + auto module = std::make_unique(); + + 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(command.get())) { + auto module_command = cast(std::move(command)); + *module = std::move(module_command->module); + } else { + assert(isa(command.get())); + auto module_command = cast(std::move(command)); + *module = std::move(module_command->module); + } + } else if (IsModuleField(PeekPair())) { + // Parse an inline module (i.e. one with no surrounding (module)). + CHECK_RESULT(ParseModuleFieldList(module.get())); + } else { + ConsumeIfLpar(); + ErrorExpected({"a module field", "a module"}); + } + + EXPECT(Eof); + if (errors_->size() == 0) { + *out_module = std::move(module); + return Result::Ok; + } else { + return Result::Error; + } +} + +Result WastParser::ParseScript(std::unique_ptr